| /********** |
| 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 filter that breaks up an MPEG-4 video elementary stream into |
| // frames for: |
| // - Visual Object Sequence (VS) Header + Visual Object (VO) Header |
| // + Video Object Layer (VOL) Header |
| // - Group of VOP (GOV) Header |
| // - VOP frame |
| // Implementation |
| |
| #include "MPEG4VideoStreamFramer.hh" |
| #include "MPEGVideoStreamParser.hh" |
| #include "MPEG4LATMAudioRTPSource.hh" // for "parseGeneralConfigStr()" |
| #include <string.h> |
| |
| ////////// MPEG4VideoStreamParser definition ////////// |
| |
| // An enum representing the current state of the parser: |
| enum MPEGParseState { |
| PARSING_VISUAL_OBJECT_SEQUENCE, |
| PARSING_VISUAL_OBJECT_SEQUENCE_SEEN_CODE, |
| PARSING_VISUAL_OBJECT, |
| PARSING_VIDEO_OBJECT_LAYER, |
| PARSING_GROUP_OF_VIDEO_OBJECT_PLANE, |
| PARSING_VIDEO_OBJECT_PLANE, |
| PARSING_VISUAL_OBJECT_SEQUENCE_END_CODE |
| }; |
| |
| class MPEG4VideoStreamParser: public MPEGVideoStreamParser { |
| public: |
| MPEG4VideoStreamParser(MPEG4VideoStreamFramer* usingSource, |
| FramedSource* inputSource); |
| virtual ~MPEG4VideoStreamParser(); |
| |
| private: // redefined virtual functions: |
| virtual void flushInput(); |
| virtual unsigned parse(); |
| |
| private: |
| MPEG4VideoStreamFramer* usingSource() { |
| return (MPEG4VideoStreamFramer*)fUsingSource; |
| } |
| void setParseState(MPEGParseState parseState); |
| |
| unsigned parseVisualObjectSequence(Boolean haveSeenStartCode = False); |
| unsigned parseVisualObject(); |
| unsigned parseVideoObjectLayer(); |
| unsigned parseGroupOfVideoObjectPlane(); |
| unsigned parseVideoObjectPlane(); |
| unsigned parseVisualObjectSequenceEndCode(); |
| |
| // These are used for parsing within an already-read frame: |
| Boolean getNextFrameBit(u_int8_t& result); |
| Boolean getNextFrameBits(unsigned numBits, u_int32_t& result); |
| |
| // Which are used by: |
| void analyzeVOLHeader(); |
| |
| private: |
| MPEGParseState fCurrentParseState; |
| unsigned fNumBitsSeenSoFar; // used by the getNextFrameBit*() routines |
| u_int32_t vop_time_increment_resolution; |
| unsigned fNumVTIRBits; |
| // # of bits needed to count to "vop_time_increment_resolution" |
| u_int8_t fixed_vop_rate; |
| unsigned fixed_vop_time_increment; // used if 'fixed_vop_rate' is set |
| unsigned fSecondsSinceLastTimeCode, fTotalTicksSinceLastTimeCode, fPrevNewTotalTicks; |
| unsigned fPrevPictureCountDelta; |
| Boolean fJustSawTimeCode; |
| }; |
| |
| |
| ////////// MPEG4VideoStreamFramer implementation ////////// |
| |
| MPEG4VideoStreamFramer* |
| MPEG4VideoStreamFramer::createNew(UsageEnvironment& env, |
| FramedSource* inputSource) { |
| // Need to add source type checking here??? ##### |
| return new MPEG4VideoStreamFramer(env, inputSource); |
| } |
| |
| unsigned char* MPEG4VideoStreamFramer |
| ::getConfigBytes(unsigned& numBytes) const { |
| numBytes = fNumConfigBytes; |
| return fConfigBytes; |
| } |
| |
| void MPEG4VideoStreamFramer |
| ::setConfigInfo(u_int8_t profileAndLevelIndication, char const* configStr) { |
| fProfileAndLevelIndication = profileAndLevelIndication; |
| |
| delete[] fConfigBytes; |
| fConfigBytes = parseGeneralConfigStr(configStr, fNumConfigBytes); |
| } |
| |
| MPEG4VideoStreamFramer::MPEG4VideoStreamFramer(UsageEnvironment& env, |
| FramedSource* inputSource, |
| Boolean createParser) |
| : MPEGVideoStreamFramer(env, inputSource), |
| fProfileAndLevelIndication(0), |
| fConfigBytes(NULL), fNumConfigBytes(0), |
| fNewConfigBytes(NULL), fNumNewConfigBytes(0) { |
| fParser = createParser |
| ? new MPEG4VideoStreamParser(this, inputSource) |
| : NULL; |
| } |
| |
| MPEG4VideoStreamFramer::~MPEG4VideoStreamFramer() { |
| delete[] fConfigBytes; delete[] fNewConfigBytes; |
| } |
| |
| void MPEG4VideoStreamFramer::startNewConfig() { |
| delete[] fNewConfigBytes; fNewConfigBytes = NULL; |
| fNumNewConfigBytes = 0; |
| } |
| |
| void MPEG4VideoStreamFramer |
| ::appendToNewConfig(unsigned char* newConfigBytes, unsigned numNewBytes) { |
| // Allocate a new block of memory for the new config bytes: |
| unsigned char* configNew |
| = new unsigned char[fNumNewConfigBytes + numNewBytes]; |
| |
| // Copy the old, then the new, config bytes there: |
| memmove(configNew, fNewConfigBytes, fNumNewConfigBytes); |
| memmove(&configNew[fNumNewConfigBytes], newConfigBytes, numNewBytes); |
| |
| delete[] fNewConfigBytes; fNewConfigBytes = configNew; |
| fNumNewConfigBytes += numNewBytes; |
| } |
| |
| void MPEG4VideoStreamFramer::completeNewConfig() { |
| delete[] fConfigBytes; fConfigBytes = fNewConfigBytes; |
| fNewConfigBytes = NULL; |
| fNumConfigBytes = fNumNewConfigBytes; |
| fNumNewConfigBytes = 0; |
| } |
| |
| Boolean MPEG4VideoStreamFramer::isMPEG4VideoStreamFramer() const { |
| return True; |
| } |
| |
| ////////// MPEG4VideoStreamParser implementation ////////// |
| |
| MPEG4VideoStreamParser |
| ::MPEG4VideoStreamParser(MPEG4VideoStreamFramer* usingSource, |
| FramedSource* inputSource) |
| : MPEGVideoStreamParser(usingSource, inputSource), |
| fCurrentParseState(PARSING_VISUAL_OBJECT_SEQUENCE), |
| vop_time_increment_resolution(0), fNumVTIRBits(0), |
| fixed_vop_rate(0), fixed_vop_time_increment(0), |
| fSecondsSinceLastTimeCode(0), fTotalTicksSinceLastTimeCode(0), |
| fPrevNewTotalTicks(0), fPrevPictureCountDelta(1), fJustSawTimeCode(False) { |
| } |
| |
| MPEG4VideoStreamParser::~MPEG4VideoStreamParser() { |
| } |
| |
| void MPEG4VideoStreamParser::setParseState(MPEGParseState parseState) { |
| fCurrentParseState = parseState; |
| MPEGVideoStreamParser::setParseState(); |
| } |
| |
| void MPEG4VideoStreamParser::flushInput() { |
| fSecondsSinceLastTimeCode = 0; |
| fTotalTicksSinceLastTimeCode = 0; |
| fPrevNewTotalTicks = 0; |
| fPrevPictureCountDelta = 1; |
| |
| StreamParser::flushInput(); |
| if (fCurrentParseState != PARSING_VISUAL_OBJECT_SEQUENCE) { |
| setParseState(PARSING_VISUAL_OBJECT_SEQUENCE); // later, change to GOV or VOP? ##### |
| } |
| } |
| |
| |
| unsigned MPEG4VideoStreamParser::parse() { |
| try { |
| switch (fCurrentParseState) { |
| case PARSING_VISUAL_OBJECT_SEQUENCE: { |
| return parseVisualObjectSequence(); |
| } |
| case PARSING_VISUAL_OBJECT_SEQUENCE_SEEN_CODE: { |
| return parseVisualObjectSequence(True); |
| } |
| case PARSING_VISUAL_OBJECT: { |
| return parseVisualObject(); |
| } |
| case PARSING_VIDEO_OBJECT_LAYER: { |
| return parseVideoObjectLayer(); |
| } |
| case PARSING_GROUP_OF_VIDEO_OBJECT_PLANE: { |
| return parseGroupOfVideoObjectPlane(); |
| } |
| case PARSING_VIDEO_OBJECT_PLANE: { |
| return parseVideoObjectPlane(); |
| } |
| case PARSING_VISUAL_OBJECT_SEQUENCE_END_CODE: { |
| return parseVisualObjectSequenceEndCode(); |
| } |
| default: { |
| return 0; // shouldn't happen |
| } |
| } |
| } catch (int /*e*/) { |
| #ifdef DEBUG |
| fprintf(stderr, "MPEG4VideoStreamParser::parse() EXCEPTION (This is normal behavior - *not* an error)\n"); |
| #endif |
| return 0; // the parsing got interrupted |
| } |
| } |
| |
| #define VISUAL_OBJECT_SEQUENCE_START_CODE 0x000001B0 |
| #define VISUAL_OBJECT_SEQUENCE_END_CODE 0x000001B1 |
| #define GROUP_VOP_START_CODE 0x000001B3 |
| #define VISUAL_OBJECT_START_CODE 0x000001B5 |
| #define VOP_START_CODE 0x000001B6 |
| |
| unsigned MPEG4VideoStreamParser |
| ::parseVisualObjectSequence(Boolean haveSeenStartCode) { |
| #ifdef DEBUG |
| fprintf(stderr, "parsing VisualObjectSequence\n"); |
| #endif |
| usingSource()->startNewConfig(); |
| u_int32_t first4Bytes; |
| if (!haveSeenStartCode) { |
| while ((first4Bytes = test4Bytes()) != VISUAL_OBJECT_SEQUENCE_START_CODE) { |
| #ifdef DEBUG |
| fprintf(stderr, "ignoring non VS header: 0x%08x\n", first4Bytes); |
| #endif |
| get1Byte(); setParseState(PARSING_VISUAL_OBJECT_SEQUENCE); |
| // ensures we progress over bad data |
| } |
| first4Bytes = get4Bytes(); |
| } else { |
| // We've already seen the start code |
| first4Bytes = VISUAL_OBJECT_SEQUENCE_START_CODE; |
| } |
| save4Bytes(first4Bytes); |
| |
| // The next byte is the "profile_and_level_indication": |
| u_int8_t pali = get1Byte(); |
| #ifdef DEBUG |
| fprintf(stderr, "profile_and_level_indication: %02x\n", pali); |
| #endif |
| saveByte(pali); |
| usingSource()->fProfileAndLevelIndication = pali; |
| |
| // Now, copy all bytes that we see, up until we reach |
| // a VISUAL_OBJECT_START_CODE: |
| u_int32_t next4Bytes = get4Bytes(); |
| while (next4Bytes != VISUAL_OBJECT_START_CODE) { |
| saveToNextCode(next4Bytes); |
| } |
| |
| setParseState(PARSING_VISUAL_OBJECT); |
| |
| // Compute this frame's presentation time: |
| usingSource()->computePresentationTime(fTotalTicksSinceLastTimeCode); |
| |
| // This header forms part of the 'configuration' information: |
| usingSource()->appendToNewConfig(fStartOfFrame, curFrameSize()); |
| |
| return curFrameSize(); |
| } |
| |
| static inline Boolean isVideoObjectStartCode(u_int32_t code) { |
| return code >= 0x00000100 && code <= 0x0000011F; |
| } |
| |
| unsigned MPEG4VideoStreamParser::parseVisualObject() { |
| #ifdef DEBUG |
| fprintf(stderr, "parsing VisualObject\n"); |
| #endif |
| // Note that we've already read the VISUAL_OBJECT_START_CODE |
| save4Bytes(VISUAL_OBJECT_START_CODE); |
| |
| // Next, extract the "visual_object_type" from the next 1 or 2 bytes: |
| u_int8_t nextByte = get1Byte(); saveByte(nextByte); |
| Boolean is_visual_object_identifier = (nextByte&0x80) != 0; |
| u_int8_t visual_object_type; |
| if (is_visual_object_identifier) { |
| #ifdef DEBUG |
| fprintf(stderr, "visual_object_verid: 0x%x; visual_object_priority: 0x%x\n", (nextByte&0x78)>>3, (nextByte&0x07)); |
| #endif |
| nextByte = get1Byte(); saveByte(nextByte); |
| visual_object_type = (nextByte&0xF0)>>4; |
| } else { |
| visual_object_type = (nextByte&0x78)>>3; |
| } |
| #ifdef DEBUG |
| fprintf(stderr, "visual_object_type: 0x%x\n", visual_object_type); |
| #endif |
| // At present, we support only the "Video ID" "visual_object_type" (1) |
| if (visual_object_type != 1) { |
| usingSource()->envir() << "MPEG4VideoStreamParser::parseVisualObject(): Warning: We don't handle visual_object_type " << visual_object_type << "\n"; |
| } |
| |
| // Now, copy all bytes that we see, up until we reach |
| // a video_object_start_code |
| u_int32_t next4Bytes = get4Bytes(); |
| while (!isVideoObjectStartCode(next4Bytes)) { |
| saveToNextCode(next4Bytes); |
| } |
| save4Bytes(next4Bytes); |
| #ifdef DEBUG |
| fprintf(stderr, "saw a video_object_start_code: 0x%08x\n", next4Bytes); |
| #endif |
| |
| setParseState(PARSING_VIDEO_OBJECT_LAYER); |
| |
| // Compute this frame's presentation time: |
| usingSource()->computePresentationTime(fTotalTicksSinceLastTimeCode); |
| |
| // This header forms part of the 'configuration' information: |
| usingSource()->appendToNewConfig(fStartOfFrame, curFrameSize()); |
| |
| return curFrameSize(); |
| } |
| |
| static inline Boolean isVideoObjectLayerStartCode(u_int32_t code) { |
| return code >= 0x00000120 && code <= 0x0000012F; |
| } |
| |
| Boolean MPEG4VideoStreamParser::getNextFrameBit(u_int8_t& result) { |
| if (fNumBitsSeenSoFar/8 >= curFrameSize()) return False; |
| |
| u_int8_t nextByte = fStartOfFrame[fNumBitsSeenSoFar/8]; |
| result = (nextByte>>(7-fNumBitsSeenSoFar%8))&1; |
| ++fNumBitsSeenSoFar; |
| return True; |
| } |
| |
| Boolean MPEG4VideoStreamParser::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 MPEG4VideoStreamParser::analyzeVOLHeader() { |
| // Extract timing information (in particular, |
| // "vop_time_increment_resolution") from the VOL Header: |
| fNumBitsSeenSoFar = 41; |
| 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) { // sanity check |
| usingSource()->envir() << "MPEG4VideoStreamParser::analyzeVOLHeader(): marker_bit 1 not set!\n"; |
| break; |
| } |
| |
| if (!getNextFrameBits(16, vop_time_increment_resolution)) break; |
| #ifdef DEBUG |
| fprintf(stderr, "vop_time_increment_resolution: %d\n", vop_time_increment_resolution); |
| #endif |
| if (vop_time_increment_resolution == 0) { |
| usingSource()->envir() << "MPEG4VideoStreamParser::analyzeVOLHeader(): vop_time_increment_resolution is zero!\n"; |
| break; |
| } |
| // Compute how many bits are necessary to represent this: |
| fNumVTIRBits = 0; |
| for (unsigned test = vop_time_increment_resolution; test>0; test /= 2) { |
| ++fNumVTIRBits; |
| } |
| |
| if (!getNextFrameBit(marker_bit)) break; |
| if (marker_bit != 1) { // sanity check |
| usingSource()->envir() << "MPEG4VideoStreamParser::analyzeVOLHeader(): marker_bit 2 not set!\n"; |
| break; |
| } |
| |
| if (!getNextFrameBit(fixed_vop_rate)) break; |
| if (fixed_vop_rate) { |
| // Get the following "fixed_vop_time_increment": |
| if (!getNextFrameBits(fNumVTIRBits, fixed_vop_time_increment)) break; |
| #ifdef DEBUG |
| fprintf(stderr, "fixed_vop_time_increment: %d\n", fixed_vop_time_increment); |
| if (fixed_vop_time_increment == 0) { |
| usingSource()->envir() << "MPEG4VideoStreamParser::analyzeVOLHeader(): fixed_vop_time_increment is zero!\n"; |
| } |
| #endif |
| } |
| // Use "vop_time_increment_resolution" as the 'frame rate' |
| // (really, 'tick rate'): |
| usingSource()->fFrameRate = (double)vop_time_increment_resolution; |
| #ifdef DEBUG |
| fprintf(stderr, "fixed_vop_rate: %d; 'frame' (really tick) rate: %f\n", fixed_vop_rate, usingSource()->fFrameRate); |
| #endif |
| |
| return; |
| } while (0); |
| |
| if (fNumBitsSeenSoFar/8 >= curFrameSize()) { |
| char errMsg[200]; |
| sprintf(errMsg, "Not enough bits in VOL header: %d/8 >= %d\n", fNumBitsSeenSoFar, curFrameSize()); |
| usingSource()->envir() << errMsg; |
| } |
| } |
| |
| unsigned MPEG4VideoStreamParser::parseVideoObjectLayer() { |
| #ifdef DEBUG |
| fprintf(stderr, "parsing VideoObjectLayer\n"); |
| #endif |
| // The first 4 bytes must be a "video_object_layer_start_code". |
| // If not, this is a 'short video header', which we currently |
| // don't support: |
| u_int32_t next4Bytes = get4Bytes(); |
| if (!isVideoObjectLayerStartCode(next4Bytes)) { |
| usingSource()->envir() << "MPEG4VideoStreamParser::parseVideoObjectLayer(): This appears to be a 'short video header', which we currently don't support\n"; |
| } |
| |
| // Now, copy all bytes that we see, up until we reach |
| // a GROUP_VOP_START_CODE or a VOP_START_CODE: |
| do { |
| saveToNextCode(next4Bytes); |
| } while (next4Bytes != GROUP_VOP_START_CODE |
| && next4Bytes != VOP_START_CODE); |
| |
| analyzeVOLHeader(); |
| |
| setParseState((next4Bytes == GROUP_VOP_START_CODE) |
| ? PARSING_GROUP_OF_VIDEO_OBJECT_PLANE |
| : PARSING_VIDEO_OBJECT_PLANE); |
| |
| // Compute this frame's presentation time: |
| usingSource()->computePresentationTime(fTotalTicksSinceLastTimeCode); |
| |
| // This header ends the 'configuration' information: |
| usingSource()->appendToNewConfig(fStartOfFrame, curFrameSize()); |
| usingSource()->completeNewConfig(); |
| |
| return curFrameSize(); |
| } |
| |
| unsigned MPEG4VideoStreamParser::parseGroupOfVideoObjectPlane() { |
| #ifdef DEBUG |
| fprintf(stderr, "parsing GroupOfVideoObjectPlane\n"); |
| #endif |
| // Note that we've already read the GROUP_VOP_START_CODE |
| save4Bytes(GROUP_VOP_START_CODE); |
| |
| // Next, extract the (18-bit) time code from the next 3 bytes: |
| u_int8_t next3Bytes[3]; |
| getBytes(next3Bytes, 3); |
| saveByte(next3Bytes[0]);saveByte(next3Bytes[1]);saveByte(next3Bytes[2]); |
| unsigned time_code |
| = (next3Bytes[0]<<10)|(next3Bytes[1]<<2)|(next3Bytes[2]>>6); |
| unsigned time_code_hours = (time_code&0x0003E000)>>13; |
| unsigned time_code_minutes = (time_code&0x00001F80)>>7; |
| #if defined(DEBUG) || defined(DEBUG_TIMESTAMPS) |
| Boolean marker_bit = (time_code&0x00000040) != 0; |
| #endif |
| unsigned time_code_seconds = (time_code&0x0000003F); |
| #if defined(DEBUG) || defined(DEBUG_TIMESTAMPS) |
| fprintf(stderr, "time_code: 0x%05x, hours %d, minutes %d, marker_bit %d, seconds %d\n", time_code, time_code_hours, time_code_minutes, marker_bit, time_code_seconds); |
| #endif |
| fJustSawTimeCode = True; |
| |
| // Now, copy all bytes that we see, up until we reach a VOP_START_CODE: |
| u_int32_t next4Bytes = get4Bytes(); |
| while (next4Bytes != VOP_START_CODE) { |
| saveToNextCode(next4Bytes); |
| } |
| |
| // Compute this frame's presentation time: |
| usingSource()->computePresentationTime(fTotalTicksSinceLastTimeCode); |
| |
| // Record the time code: |
| usingSource()->setTimeCode(time_code_hours, time_code_minutes, |
| time_code_seconds, 0, 0); |
| // Note: Because the GOV header can appear anywhere (not just at a 1s point), we |
| // don't pass "fTotalTicksSinceLastTimeCode" as the "picturesSinceLastGOP" parameter. |
| fSecondsSinceLastTimeCode = 0; |
| if (fixed_vop_rate) fTotalTicksSinceLastTimeCode = 0; |
| |
| setParseState(PARSING_VIDEO_OBJECT_PLANE); |
| |
| return curFrameSize(); |
| } |
| |
| unsigned MPEG4VideoStreamParser::parseVideoObjectPlane() { |
| #ifdef DEBUG |
| fprintf(stderr, "#parsing VideoObjectPlane\n"); |
| #endif |
| // Note that we've already read the VOP_START_CODE |
| save4Bytes(VOP_START_CODE); |
| |
| // Get the "vop_coding_type" from the next byte: |
| u_int8_t nextByte = get1Byte(); saveByte(nextByte); |
| 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 = get4Bytes(); |
| 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 >>= 1; |
| |
| // Check the following marker bit: |
| if ((timeInfo&mask) == 0) { |
| usingSource()->envir() << "MPEG4VideoStreamParser::parseVideoObjectPlane(): marker bit not set!\n"; |
| } |
| mask >>= 1; |
| |
| // Then, get the "vop_time_increment". |
| // First, make sure we have enough bits left for this: |
| if ((mask>>(fNumVTIRBits-1)) == 0) { |
| usingSource()->envir() << "MPEG4VideoStreamParser::parseVideoObjectPlane(): 32-bits are not enough to get \"vop_time_increment\"!\n"; |
| } |
| unsigned vop_time_increment = 0; |
| for (unsigned i = 0; i < fNumVTIRBits; ++i) { |
| vop_time_increment |= timeInfo&mask; |
| mask >>= 1; |
| } |
| while (mask != 0) { |
| vop_time_increment >>= 1; |
| mask >>= 1; |
| } |
| #ifdef DEBUG |
| fprintf(stderr, "vop_coding_type: %d(%c), modulo_time_base: %d, vop_time_increment: %d\n", vop_coding_type, "IPBS"[vop_coding_type], modulo_time_base, vop_time_increment); |
| #endif |
| |
| // Now, copy all bytes that we see, up until we reach a code of some sort: |
| saveToNextCode(next4Bytes); |
| |
| // Update our counters based on the frame timing information that we saw: |
| if (fixed_vop_time_increment > 0) { |
| // This is a 'fixed_vop_rate' stream. Use 'fixed_vop_time_increment': |
| usingSource()->fPictureCount += fixed_vop_time_increment; |
| if (vop_time_increment > 0 || modulo_time_base > 0) { |
| fTotalTicksSinceLastTimeCode += fixed_vop_time_increment; |
| // Note: "fSecondsSinceLastTimeCode" and "fPrevNewTotalTicks" are not used. |
| } |
| } else { |
| // Use 'vop_time_increment': |
| unsigned newTotalTicks |
| = (fSecondsSinceLastTimeCode + modulo_time_base)*vop_time_increment_resolution |
| + vop_time_increment; |
| if (newTotalTicks == fPrevNewTotalTicks && fPrevNewTotalTicks > 0) { |
| // This is apparently a buggy MPEG-4 video stream, because |
| // "vop_time_increment" did not change. Overcome this error, |
| // by pretending that it did change. |
| #ifdef DEBUG |
| fprintf(stderr, "Buggy MPEG-4 video stream: \"vop_time_increment\" did not change!\n"); |
| #endif |
| // The following assumes that we don't have 'B' frames. If we do, then TARFU! |
| usingSource()->fPictureCount += vop_time_increment; |
| fTotalTicksSinceLastTimeCode += vop_time_increment; |
| fSecondsSinceLastTimeCode += modulo_time_base; |
| } else { |
| if (newTotalTicks < fPrevNewTotalTicks && vop_coding_type != 2/*B*/ |
| && modulo_time_base == 0 && vop_time_increment == 0 && !fJustSawTimeCode) { |
| // This is another kind of buggy MPEG-4 video stream, in which |
| // "vop_time_increment" wraps around, but without |
| // "modulo_time_base" changing (or just having had a new time code). |
| // Overcome this by pretending that "vop_time_increment" *did* wrap around: |
| #ifdef DEBUG |
| fprintf(stderr, "Buggy MPEG-4 video stream: \"vop_time_increment\" wrapped around, but without \"modulo_time_base\" changing!\n"); |
| #endif |
| ++fSecondsSinceLastTimeCode; |
| newTotalTicks += vop_time_increment_resolution; |
| } |
| fPrevNewTotalTicks = newTotalTicks; |
| if (vop_coding_type != 2/*B*/) { |
| int pictureCountDelta = newTotalTicks - fTotalTicksSinceLastTimeCode; |
| if (pictureCountDelta <= 0) pictureCountDelta = fPrevPictureCountDelta; |
| // ensures that the picture count is always increasing |
| usingSource()->fPictureCount += pictureCountDelta; |
| fPrevPictureCountDelta = pictureCountDelta; |
| fTotalTicksSinceLastTimeCode = newTotalTicks; |
| fSecondsSinceLastTimeCode += modulo_time_base; |
| } |
| } |
| } |
| fJustSawTimeCode = False; // for next time |
| |
| // The next thing to parse depends on the code that we just saw, |
| // but we are assumed to have ended the current picture: |
| usingSource()->fPictureEndMarker = True; // HACK ##### |
| switch (next4Bytes) { |
| case VISUAL_OBJECT_SEQUENCE_END_CODE: { |
| setParseState(PARSING_VISUAL_OBJECT_SEQUENCE_END_CODE); |
| break; |
| } |
| case VISUAL_OBJECT_SEQUENCE_START_CODE: { |
| setParseState(PARSING_VISUAL_OBJECT_SEQUENCE_SEEN_CODE); |
| break; |
| } |
| case VISUAL_OBJECT_START_CODE: { |
| setParseState(PARSING_VISUAL_OBJECT); |
| break; |
| } |
| case GROUP_VOP_START_CODE: { |
| setParseState(PARSING_GROUP_OF_VIDEO_OBJECT_PLANE); |
| break; |
| } |
| case VOP_START_CODE: { |
| setParseState(PARSING_VIDEO_OBJECT_PLANE); |
| break; |
| } |
| default: { |
| if (isVideoObjectStartCode(next4Bytes)) { |
| setParseState(PARSING_VIDEO_OBJECT_LAYER); |
| } else if (isVideoObjectLayerStartCode(next4Bytes)){ |
| // copy all bytes that we see, up until we reach a VOP_START_CODE: |
| u_int32_t next4Bytes = get4Bytes(); |
| while (next4Bytes != VOP_START_CODE) { |
| saveToNextCode(next4Bytes); |
| } |
| setParseState(PARSING_VIDEO_OBJECT_PLANE); |
| } else { |
| usingSource()->envir() << "MPEG4VideoStreamParser::parseVideoObjectPlane(): Saw unexpected code " |
| << (void*)next4Bytes << "\n"; |
| setParseState(PARSING_VIDEO_OBJECT_PLANE); // the safest way to recover... |
| } |
| break; |
| } |
| } |
| |
| // Compute this frame's presentation time: |
| usingSource()->computePresentationTime(fTotalTicksSinceLastTimeCode); |
| |
| return curFrameSize(); |
| } |
| |
| unsigned MPEG4VideoStreamParser::parseVisualObjectSequenceEndCode() { |
| #ifdef DEBUG |
| fprintf(stderr, "parsing VISUAL_OBJECT_SEQUENCE_END_CODE\n"); |
| #endif |
| // Note that we've already read the VISUAL_OBJECT_SEQUENCE_END_CODE |
| save4Bytes(VISUAL_OBJECT_SEQUENCE_END_CODE); |
| |
| setParseState(PARSING_VISUAL_OBJECT_SEQUENCE); |
| |
| // Treat this as if we had ended a picture: |
| usingSource()->fPictureEndMarker = True; // HACK ##### |
| |
| return curFrameSize(); |
| } |