682 lines
23 KiB
C++
Executable File
682 lines
23 KiB
C++
Executable File
/**********
|
|
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 2.1 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-2016 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();
|
|
}
|