| /********** |
| This library is free software; you can redistribute it and/or modify it under |
| the terms of the GNU Lesser General Public License as published by the |
| Free Software Foundation; either version 3 of the License, or (at your |
| option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.) |
| |
| This library is distributed in the hope that it will be useful, but WITHOUT |
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for |
| more details. |
| |
| You should have received a copy of the GNU Lesser General Public License |
| along with this library; if not, write to the Free Software Foundation, Inc., |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| **********/ |
| // "liveMedia" |
| // Copyright (c) 1996-2020 Live Networks, Inc. All rights reserved. |
| // A simplified version of "MPEG4VideoStreamFramer" that takes only complete, |
| // discrete frames (rather than an arbitrary byte stream) as input. |
| // This avoids the parsing and data copying overhead of the full |
| // "MPEG4VideoStreamFramer". |
| // Implementation |
| |
| #include "MPEG4VideoStreamDiscreteFramer.hh" |
| |
| MPEG4VideoStreamDiscreteFramer* |
| MPEG4VideoStreamDiscreteFramer::createNew(UsageEnvironment& env, |
| FramedSource* inputSource, Boolean leavePresentationTimesUnmodified) { |
| // Need to add source type checking here??? ##### |
| return new MPEG4VideoStreamDiscreteFramer(env, inputSource, leavePresentationTimesUnmodified); |
| } |
| |
| MPEG4VideoStreamDiscreteFramer |
| ::MPEG4VideoStreamDiscreteFramer(UsageEnvironment& env, |
| FramedSource* inputSource, Boolean leavePresentationTimesUnmodified) |
| : MPEG4VideoStreamFramer(env, inputSource, False/*don't create a parser*/), |
| fLeavePresentationTimesUnmodified(leavePresentationTimesUnmodified), vop_time_increment_resolution(0), fNumVTIRBits(0), |
| fLastNonBFrameVop_time_increment(0) { |
| fLastNonBFramePresentationTime.tv_sec = 0; |
| fLastNonBFramePresentationTime.tv_usec = 0; |
| } |
| |
| MPEG4VideoStreamDiscreteFramer::~MPEG4VideoStreamDiscreteFramer() { |
| } |
| |
| void MPEG4VideoStreamDiscreteFramer::doGetNextFrame() { |
| // Arrange to read data (which should be a complete MPEG-4 video frame) |
| // from our data source, directly into the client's input buffer. |
| // After reading this, we'll do some parsing on the frame. |
| fInputSource->getNextFrame(fTo, fMaxSize, |
| afterGettingFrame, this, |
| FramedSource::handleClosure, this); |
| } |
| |
| void MPEG4VideoStreamDiscreteFramer |
| ::afterGettingFrame(void* clientData, unsigned frameSize, |
| unsigned numTruncatedBytes, |
| struct timeval presentationTime, |
| unsigned durationInMicroseconds) { |
| MPEG4VideoStreamDiscreteFramer* source = (MPEG4VideoStreamDiscreteFramer*)clientData; |
| source->afterGettingFrame1(frameSize, numTruncatedBytes, |
| presentationTime, durationInMicroseconds); |
| } |
| |
| void MPEG4VideoStreamDiscreteFramer |
| ::afterGettingFrame1(unsigned frameSize, unsigned numTruncatedBytes, |
| struct timeval presentationTime, |
| unsigned durationInMicroseconds) { |
| // Check that the first 4 bytes are a system code: |
| if (frameSize >= 4 && fTo[0] == 0 && fTo[1] == 0 && fTo[2] == 1) { |
| fPictureEndMarker = True; // Assume that we have a complete 'picture' here |
| unsigned i = 3; |
| if (fTo[i] == 0xB0) { // VISUAL_OBJECT_SEQUENCE_START_CODE |
| // The next byte is the "profile_and_level_indication": |
| if (frameSize >= 5) fProfileAndLevelIndication = fTo[4]; |
| |
| // The start of this frame - up to the first GROUP_VOP_START_CODE |
| // or VOP_START_CODE - is stream configuration information. Save this: |
| for (i = 7; i < frameSize; ++i) { |
| if ((fTo[i] == 0xB3 /*GROUP_VOP_START_CODE*/ || |
| fTo[i] == 0xB6 /*VOP_START_CODE*/) |
| && fTo[i-1] == 1 && fTo[i-2] == 0 && fTo[i-3] == 0) { |
| break; // The configuration information ends here |
| } |
| } |
| fNumConfigBytes = i < frameSize ? i-3 : frameSize; |
| delete[] fConfigBytes; fConfigBytes = new unsigned char[fNumConfigBytes]; |
| for (unsigned j = 0; j < fNumConfigBytes; ++j) fConfigBytes[j] = fTo[j]; |
| |
| // This information (should) also contain a VOL header, which we need |
| // to analyze, to get "vop_time_increment_resolution" (which we need |
| // - along with "vop_time_increment" - in order to generate accurate |
| // presentation times for "B" frames). |
| analyzeVOLHeader(); |
| } |
| |
| if (i < frameSize) { |
| u_int8_t nextCode = fTo[i]; |
| |
| if (nextCode == 0xB3 /*GROUP_VOP_START_CODE*/) { |
| // Skip to the following VOP_START_CODE (if any): |
| for (i += 4; i < frameSize; ++i) { |
| if (fTo[i] == 0xB6 /*VOP_START_CODE*/ |
| && fTo[i-1] == 1 && fTo[i-2] == 0 && fTo[i-3] == 0) { |
| nextCode = fTo[i]; |
| break; |
| } |
| } |
| } |
| |
| if (nextCode == 0xB6 /*VOP_START_CODE*/ && i+5 < frameSize) { |
| ++i; |
| |
| // Get the "vop_coding_type" from the next byte: |
| u_int8_t nextByte = fTo[i++]; |
| u_int8_t vop_coding_type = nextByte>>6; |
| |
| // Next, get the "modulo_time_base" by counting the '1' bits that |
| // follow. We look at the next 32-bits only. |
| // This should be enough in most cases. |
| u_int32_t next4Bytes |
| = (fTo[i]<<24)|(fTo[i+1]<<16)|(fTo[i+2]<<8)|fTo[i+3]; |
| i += 4; |
| u_int32_t timeInfo = (nextByte<<(32-6))|(next4Bytes>>6); |
| unsigned modulo_time_base = 0; |
| u_int32_t mask = 0x80000000; |
| while ((timeInfo&mask) != 0) { |
| ++modulo_time_base; |
| mask >>= 1; |
| } |
| mask >>= 2; |
| |
| // Then, get the "vop_time_increment". |
| unsigned vop_time_increment = 0; |
| // First, make sure we have enough bits left for this: |
| if ((mask>>(fNumVTIRBits-1)) != 0) { |
| for (unsigned i = 0; i < fNumVTIRBits; ++i) { |
| vop_time_increment |= timeInfo&mask; |
| mask >>= 1; |
| } |
| while (mask != 0) { |
| vop_time_increment >>= 1; |
| mask >>= 1; |
| } |
| } |
| |
| // If this is a "B" frame, then we have to tweak "presentationTime": |
| if (!fLeavePresentationTimesUnmodified && vop_coding_type == 2/*B*/ |
| && (fLastNonBFramePresentationTime.tv_usec > 0 || |
| fLastNonBFramePresentationTime.tv_sec > 0)) { |
| int timeIncrement |
| = fLastNonBFrameVop_time_increment - vop_time_increment; |
| if (timeIncrement<0) timeIncrement += vop_time_increment_resolution; |
| unsigned const MILLION = 1000000; |
| double usIncrement = vop_time_increment_resolution == 0 ? 0.0 |
| : ((double)timeIncrement*MILLION)/vop_time_increment_resolution; |
| unsigned secondsToSubtract = (unsigned)(usIncrement/MILLION); |
| unsigned uSecondsToSubtract = ((unsigned)usIncrement)%MILLION; |
| |
| presentationTime = fLastNonBFramePresentationTime; |
| if ((unsigned)presentationTime.tv_usec < uSecondsToSubtract) { |
| presentationTime.tv_usec += MILLION; |
| if (presentationTime.tv_sec > 0) --presentationTime.tv_sec; |
| } |
| presentationTime.tv_usec -= uSecondsToSubtract; |
| if ((unsigned)presentationTime.tv_sec > secondsToSubtract) { |
| presentationTime.tv_sec -= secondsToSubtract; |
| } else { |
| presentationTime.tv_sec = presentationTime.tv_usec = 0; |
| } |
| } else { |
| fLastNonBFramePresentationTime = presentationTime; |
| fLastNonBFrameVop_time_increment = vop_time_increment; |
| } |
| } |
| } |
| } |
| |
| // Complete delivery to the client: |
| fFrameSize = frameSize; |
| fNumTruncatedBytes = numTruncatedBytes; |
| fPresentationTime = presentationTime; |
| fDurationInMicroseconds = durationInMicroseconds; |
| afterGetting(this); |
| } |
| |
| Boolean MPEG4VideoStreamDiscreteFramer::getNextFrameBit(u_int8_t& result) { |
| if (fNumBitsSeenSoFar/8 >= fNumConfigBytes) return False; |
| |
| u_int8_t nextByte = fConfigBytes[fNumBitsSeenSoFar/8]; |
| result = (nextByte>>(7-fNumBitsSeenSoFar%8))&1; |
| ++fNumBitsSeenSoFar; |
| return True; |
| } |
| |
| Boolean MPEG4VideoStreamDiscreteFramer::getNextFrameBits(unsigned numBits, |
| u_int32_t& result) { |
| result = 0; |
| for (unsigned i = 0; i < numBits; ++i) { |
| u_int8_t nextBit; |
| if (!getNextFrameBit(nextBit)) return False; |
| result = (result<<1)|nextBit; |
| } |
| return True; |
| } |
| |
| void MPEG4VideoStreamDiscreteFramer::analyzeVOLHeader() { |
| // Begin by moving to the VOL header: |
| unsigned i; |
| for (i = 3; i < fNumConfigBytes; ++i) { |
| if (fConfigBytes[i] >= 0x20 && fConfigBytes[i] <= 0x2F |
| && fConfigBytes[i-1] == 1 |
| && fConfigBytes[i-2] == 0 && fConfigBytes[i-3] == 0) { |
| ++i; |
| break; |
| } |
| } |
| |
| fNumBitsSeenSoFar = 8*i + 9; |
| do { |
| u_int8_t is_object_layer_identifier; |
| if (!getNextFrameBit(is_object_layer_identifier)) break; |
| if (is_object_layer_identifier) fNumBitsSeenSoFar += 7; |
| |
| u_int32_t aspect_ratio_info; |
| if (!getNextFrameBits(4, aspect_ratio_info)) break; |
| if (aspect_ratio_info == 15 /*extended_PAR*/) fNumBitsSeenSoFar += 16; |
| |
| u_int8_t vol_control_parameters; |
| if (!getNextFrameBit(vol_control_parameters)) break; |
| if (vol_control_parameters) { |
| fNumBitsSeenSoFar += 3; // chroma_format; low_delay |
| u_int8_t vbw_parameters; |
| if (!getNextFrameBit(vbw_parameters)) break; |
| if (vbw_parameters) fNumBitsSeenSoFar += 79; |
| } |
| |
| fNumBitsSeenSoFar += 2; // video_object_layer_shape |
| u_int8_t marker_bit; |
| if (!getNextFrameBit(marker_bit)) break; |
| if (marker_bit != 1) break; // sanity check |
| |
| if (!getNextFrameBits(16, vop_time_increment_resolution)) break; |
| if (vop_time_increment_resolution == 0) break; // shouldn't happen |
| |
| // Compute how many bits are necessary to represent this: |
| fNumVTIRBits = 0; |
| for (unsigned test = vop_time_increment_resolution; test>0; test /= 2) { |
| ++fNumVTIRBits; |
| } |
| } while (0); |
| } |