| /********** |
| 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 1 or 2 video elementary stream into |
| // frames for: Video_Sequence_Header, GOP_Header, Picture_Header |
| // Implementation |
| |
| #include "MPEG1or2VideoStreamFramer.hh" |
| #include "MPEGVideoStreamParser.hh" |
| #include <string.h> |
| |
| ////////// MPEG1or2VideoStreamParser definition ////////// |
| |
| // An enum representing the current state of the parser: |
| enum MPEGParseState { |
| PARSING_VIDEO_SEQUENCE_HEADER, |
| PARSING_VIDEO_SEQUENCE_HEADER_SEEN_CODE, |
| PARSING_GOP_HEADER, |
| PARSING_GOP_HEADER_SEEN_CODE, |
| PARSING_PICTURE_HEADER, |
| PARSING_SLICE |
| }; |
| |
| #define VSH_MAX_SIZE 1000 |
| |
| class MPEG1or2VideoStreamParser: public MPEGVideoStreamParser { |
| public: |
| MPEG1or2VideoStreamParser(MPEG1or2VideoStreamFramer* usingSource, |
| FramedSource* inputSource, |
| Boolean iFramesOnly, double vshPeriod); |
| virtual ~MPEG1or2VideoStreamParser(); |
| |
| private: // redefined virtual functions: |
| virtual void flushInput(); |
| virtual unsigned parse(); |
| |
| private: |
| void reset(); |
| |
| MPEG1or2VideoStreamFramer* usingSource() { |
| return (MPEG1or2VideoStreamFramer*)fUsingSource; |
| } |
| void setParseState(MPEGParseState parseState); |
| |
| unsigned parseVideoSequenceHeader(Boolean haveSeenStartCode); |
| unsigned parseGOPHeader(Boolean haveSeenStartCode); |
| unsigned parsePictureHeader(); |
| unsigned parseSlice(); |
| |
| private: |
| MPEGParseState fCurrentParseState; |
| unsigned fPicturesSinceLastGOP; |
| // can be used to compute timestamp for a video_sequence_header |
| unsigned short fCurPicTemporalReference; |
| // used to compute slice timestamp |
| unsigned char fCurrentSliceNumber; // set when parsing a slice |
| |
| // A saved copy of the most recently seen 'video_sequence_header', |
| // in case we need to insert it into the stream periodically: |
| unsigned char fSavedVSHBuffer[VSH_MAX_SIZE]; |
| unsigned fSavedVSHSize; |
| double fSavedVSHTimestamp; |
| double fVSHPeriod; |
| Boolean fIFramesOnly, fSkippingCurrentPicture; |
| |
| void saveCurrentVSH(); |
| Boolean needToUseSavedVSH(); |
| unsigned useSavedVSH(); // returns the size of the saved VSH |
| }; |
| |
| |
| ////////// MPEG1or2VideoStreamFramer implementation ////////// |
| |
| MPEG1or2VideoStreamFramer::MPEG1or2VideoStreamFramer(UsageEnvironment& env, |
| FramedSource* inputSource, |
| Boolean iFramesOnly, |
| double vshPeriod, |
| Boolean createParser) |
| : MPEGVideoStreamFramer(env, inputSource) { |
| fParser = createParser |
| ? new MPEG1or2VideoStreamParser(this, inputSource, |
| iFramesOnly, vshPeriod) |
| : NULL; |
| } |
| |
| MPEG1or2VideoStreamFramer::~MPEG1or2VideoStreamFramer() { |
| } |
| |
| MPEG1or2VideoStreamFramer* |
| MPEG1or2VideoStreamFramer::createNew(UsageEnvironment& env, |
| FramedSource* inputSource, |
| Boolean iFramesOnly, |
| double vshPeriod) { |
| // Need to add source type checking here??? ##### |
| return new MPEG1or2VideoStreamFramer(env, inputSource, iFramesOnly, vshPeriod); |
| } |
| |
| double MPEG1or2VideoStreamFramer::getCurrentPTS() const { |
| return fPresentationTime.tv_sec + fPresentationTime.tv_usec/1000000.0; |
| } |
| |
| Boolean MPEG1or2VideoStreamFramer::isMPEG1or2VideoStreamFramer() const { |
| return True; |
| } |
| |
| ////////// MPEG1or2VideoStreamParser implementation ////////// |
| |
| MPEG1or2VideoStreamParser |
| ::MPEG1or2VideoStreamParser(MPEG1or2VideoStreamFramer* usingSource, |
| FramedSource* inputSource, |
| Boolean iFramesOnly, double vshPeriod) |
| : MPEGVideoStreamParser(usingSource, inputSource), |
| fCurrentParseState(PARSING_VIDEO_SEQUENCE_HEADER), |
| fVSHPeriod(vshPeriod), fIFramesOnly(iFramesOnly) { |
| reset(); |
| } |
| |
| MPEG1or2VideoStreamParser::~MPEG1or2VideoStreamParser() { |
| } |
| |
| void MPEG1or2VideoStreamParser::setParseState(MPEGParseState parseState) { |
| fCurrentParseState = parseState; |
| MPEGVideoStreamParser::setParseState(); |
| } |
| |
| void MPEG1or2VideoStreamParser::reset() { |
| fPicturesSinceLastGOP = 0; |
| fCurPicTemporalReference = 0; |
| fCurrentSliceNumber = 0; |
| fSavedVSHSize = 0; |
| fSkippingCurrentPicture = False; |
| } |
| |
| void MPEG1or2VideoStreamParser::flushInput() { |
| reset(); |
| StreamParser::flushInput(); |
| if (fCurrentParseState != PARSING_VIDEO_SEQUENCE_HEADER) { |
| setParseState(PARSING_GOP_HEADER); // start from the next GOP |
| } |
| } |
| |
| unsigned MPEG1or2VideoStreamParser::parse() { |
| try { |
| switch (fCurrentParseState) { |
| case PARSING_VIDEO_SEQUENCE_HEADER: { |
| return parseVideoSequenceHeader(False); |
| } |
| case PARSING_VIDEO_SEQUENCE_HEADER_SEEN_CODE: { |
| return parseVideoSequenceHeader(True); |
| } |
| case PARSING_GOP_HEADER: { |
| return parseGOPHeader(False); |
| } |
| case PARSING_GOP_HEADER_SEEN_CODE: { |
| return parseGOPHeader(True); |
| } |
| case PARSING_PICTURE_HEADER: { |
| return parsePictureHeader(); |
| } |
| case PARSING_SLICE: { |
| return parseSlice(); |
| } |
| default: { |
| return 0; // shouldn't happen |
| } |
| } |
| } catch (int /*e*/) { |
| #ifdef DEBUG |
| fprintf(stderr, "MPEG1or2VideoStreamParser::parse() EXCEPTION (This is normal behavior - *not* an error)\n"); |
| #endif |
| return 0; // the parsing got interrupted |
| } |
| } |
| |
| void MPEG1or2VideoStreamParser::saveCurrentVSH() { |
| unsigned frameSize = curFrameSize(); |
| if (frameSize > sizeof fSavedVSHBuffer) return; // too big to save |
| |
| memmove(fSavedVSHBuffer, fStartOfFrame, frameSize); |
| fSavedVSHSize = frameSize; |
| fSavedVSHTimestamp = usingSource()->getCurrentPTS(); |
| } |
| |
| Boolean MPEG1or2VideoStreamParser::needToUseSavedVSH() { |
| return usingSource()->getCurrentPTS() > fSavedVSHTimestamp+fVSHPeriod |
| && fSavedVSHSize > 0; |
| } |
| |
| unsigned MPEG1or2VideoStreamParser::useSavedVSH() { |
| unsigned bytesToUse = fSavedVSHSize; |
| unsigned maxBytesToUse = fLimit - fStartOfFrame; |
| if (bytesToUse > maxBytesToUse) bytesToUse = maxBytesToUse; |
| |
| memmove(fStartOfFrame, fSavedVSHBuffer, bytesToUse); |
| |
| // Also reset the saved timestamp: |
| fSavedVSHTimestamp = usingSource()->getCurrentPTS(); |
| |
| #ifdef DEBUG |
| fprintf(stderr, "used saved video_sequence_header (%d bytes)\n", bytesToUse); |
| #endif |
| return bytesToUse; |
| } |
| |
| #define VIDEO_SEQUENCE_HEADER_START_CODE 0x000001B3 |
| #define GROUP_START_CODE 0x000001B8 |
| #define PICTURE_START_CODE 0x00000100 |
| #define SEQUENCE_END_CODE 0x000001B7 |
| |
| static double const frameRateFromCode[] = { |
| 0.0, // forbidden |
| 24000/1001.0, // approx 23.976 |
| 24.0, |
| 25.0, |
| 30000/1001.0, // approx 29.97 |
| 30.0, |
| 50.0, |
| 60000/1001.0, // approx 59.94 |
| 60.0, |
| 0.0, // reserved |
| 0.0, // reserved |
| 0.0, // reserved |
| 0.0, // reserved |
| 0.0, // reserved |
| 0.0, // reserved |
| 0.0 // reserved |
| }; |
| |
| unsigned MPEG1or2VideoStreamParser |
| ::parseVideoSequenceHeader(Boolean haveSeenStartCode) { |
| #ifdef DEBUG |
| fprintf(stderr, "parsing video sequence header\n"); |
| #endif |
| unsigned first4Bytes; |
| if (!haveSeenStartCode) { |
| while ((first4Bytes = test4Bytes()) != VIDEO_SEQUENCE_HEADER_START_CODE) { |
| #ifdef DEBUG |
| fprintf(stderr, "ignoring non video sequence header: 0x%08x\n", first4Bytes); |
| #endif |
| get1Byte(); setParseState(PARSING_VIDEO_SEQUENCE_HEADER); |
| // ensures we progress over bad data |
| } |
| first4Bytes = get4Bytes(); |
| } else { |
| // We've already seen the start code |
| first4Bytes = VIDEO_SEQUENCE_HEADER_START_CODE; |
| } |
| save4Bytes(first4Bytes); |
| |
| // Next, extract the size and rate parameters from the next 8 bytes |
| unsigned paramWord1 = get4Bytes(); |
| save4Bytes(paramWord1); |
| unsigned next4Bytes = get4Bytes(); |
| #ifdef DEBUG |
| unsigned short horizontal_size_value = (paramWord1&0xFFF00000)>>(32-12); |
| unsigned short vertical_size_value = (paramWord1&0x000FFF00)>>8; |
| unsigned char aspect_ratio_information = (paramWord1&0x000000F0)>>4; |
| #endif |
| unsigned char frame_rate_code = (paramWord1&0x0000000F); |
| usingSource()->fFrameRate = frameRateFromCode[frame_rate_code]; |
| #ifdef DEBUG |
| unsigned bit_rate_value = (next4Bytes&0xFFFFC000)>>(32-18); |
| unsigned vbv_buffer_size_value = (next4Bytes&0x00001FF8)>>3; |
| fprintf(stderr, "horizontal_size_value: %d, vertical_size_value: %d, aspect_ratio_information: %d, frame_rate_code: %d (=>%f fps), bit_rate_value: %d (=>%d bps), vbv_buffer_size_value: %d\n", horizontal_size_value, vertical_size_value, aspect_ratio_information, frame_rate_code, usingSource()->fFrameRate, bit_rate_value, bit_rate_value*400, vbv_buffer_size_value); |
| #endif |
| |
| // Now, copy all bytes that we see, up until we reach a GROUP_START_CODE |
| // or a PICTURE_START_CODE: |
| do { |
| saveToNextCode(next4Bytes); |
| } while (next4Bytes != GROUP_START_CODE && next4Bytes != PICTURE_START_CODE); |
| |
| setParseState((next4Bytes == GROUP_START_CODE) |
| ? PARSING_GOP_HEADER_SEEN_CODE : PARSING_PICTURE_HEADER); |
| |
| // Compute this frame's timestamp by noting how many pictures we've seen |
| // since the last GOP header: |
| usingSource()->computePresentationTime(fPicturesSinceLastGOP); |
| |
| // Save this video_sequence_header, in case we need to insert a copy |
| // into the stream later: |
| saveCurrentVSH(); |
| |
| return curFrameSize(); |
| } |
| |
| unsigned MPEG1or2VideoStreamParser::parseGOPHeader(Boolean haveSeenStartCode) { |
| // First check whether we should insert a previously-saved |
| // 'video_sequence_header' here: |
| if (needToUseSavedVSH()) return useSavedVSH(); |
| |
| #ifdef DEBUG |
| fprintf(stderr, "parsing GOP header\n"); |
| #endif |
| unsigned first4Bytes; |
| if (!haveSeenStartCode) { |
| while ((first4Bytes = test4Bytes()) != GROUP_START_CODE) { |
| #ifdef DEBUG |
| fprintf(stderr, "ignoring non GOP start code: 0x%08x\n", first4Bytes); |
| #endif |
| get1Byte(); setParseState(PARSING_GOP_HEADER); |
| // ensures we progress over bad data |
| } |
| first4Bytes = get4Bytes(); |
| } else { |
| // We've already seen the GROUP_START_CODE |
| first4Bytes = GROUP_START_CODE; |
| } |
| save4Bytes(first4Bytes); |
| |
| // Next, extract the (25-bit) time code from the next 4 bytes: |
| unsigned next4Bytes = get4Bytes(); |
| unsigned time_code = (next4Bytes&0xFFFFFF80)>>(32-25); |
| #if defined(DEBUG) || defined(DEBUG_TIMESTAMPS) |
| Boolean drop_frame_flag = (time_code&0x01000000) != 0; |
| #endif |
| unsigned time_code_hours = (time_code&0x00F80000)>>19; |
| unsigned time_code_minutes = (time_code&0x0007E000)>>13; |
| unsigned time_code_seconds = (time_code&0x00000FC0)>>6; |
| unsigned time_code_pictures = (time_code&0x0000003F); |
| #if defined(DEBUG) || defined(DEBUG_TIMESTAMPS) |
| fprintf(stderr, "time_code: 0x%07x, drop_frame %d, hours %d, minutes %d, seconds %d, pictures %d\n", time_code, drop_frame_flag, time_code_hours, time_code_minutes, time_code_seconds, time_code_pictures); |
| #endif |
| #ifdef DEBUG |
| Boolean closed_gop = (next4Bytes&0x00000040) != 0; |
| Boolean broken_link = (next4Bytes&0x00000020) != 0; |
| fprintf(stderr, "closed_gop: %d, broken_link: %d\n", closed_gop, broken_link); |
| #endif |
| |
| // Now, copy all bytes that we see, up until we reach a PICTURE_START_CODE: |
| do { |
| saveToNextCode(next4Bytes); |
| } while (next4Bytes != PICTURE_START_CODE); |
| |
| // Record the time code: |
| usingSource()->setTimeCode(time_code_hours, time_code_minutes, |
| time_code_seconds, time_code_pictures, |
| fPicturesSinceLastGOP); |
| |
| fPicturesSinceLastGOP = 0; |
| |
| // Compute this frame's timestamp: |
| usingSource()->computePresentationTime(0); |
| |
| setParseState(PARSING_PICTURE_HEADER); |
| |
| return curFrameSize(); |
| } |
| |
| inline Boolean isSliceStartCode(unsigned fourBytes) { |
| if ((fourBytes&0xFFFFFF00) != 0x00000100) return False; |
| |
| unsigned char lastByte = fourBytes&0xFF; |
| return lastByte <= 0xAF && lastByte >= 1; |
| } |
| |
| unsigned MPEG1or2VideoStreamParser::parsePictureHeader() { |
| #ifdef DEBUG |
| fprintf(stderr, "parsing picture header\n"); |
| #endif |
| // Note that we've already read the PICTURE_START_CODE |
| // Next, extract the temporal reference from the next 4 bytes: |
| unsigned next4Bytes = get4Bytes(); |
| unsigned short temporal_reference = (next4Bytes&0xFFC00000)>>(32-10); |
| unsigned char picture_coding_type = (next4Bytes&0x00380000)>>19; |
| #ifdef DEBUG |
| unsigned short vbv_delay = (next4Bytes&0x0007FFF8)>>3; |
| fprintf(stderr, "temporal_reference: %d, picture_coding_type: %d, vbv_delay: %d\n", temporal_reference, picture_coding_type, vbv_delay); |
| #endif |
| |
| fSkippingCurrentPicture = fIFramesOnly && picture_coding_type != 1; |
| if (fSkippingCurrentPicture) { |
| // Skip all bytes that we see, up until we reach a slice_start_code: |
| do { |
| skipToNextCode(next4Bytes); |
| } while (!isSliceStartCode(next4Bytes)); |
| } else { |
| // Save the PICTURE_START_CODE that we've already read: |
| save4Bytes(PICTURE_START_CODE); |
| |
| // Copy all bytes that we see, up until we reach a slice_start_code: |
| do { |
| saveToNextCode(next4Bytes); |
| } while (!isSliceStartCode(next4Bytes)); |
| } |
| |
| setParseState(PARSING_SLICE); |
| |
| fCurrentSliceNumber = next4Bytes&0xFF; |
| |
| // Record the temporal reference: |
| fCurPicTemporalReference = temporal_reference; |
| |
| // Compute this frame's timestamp: |
| usingSource()->computePresentationTime(fCurPicTemporalReference); |
| |
| if (fSkippingCurrentPicture) { |
| return parse(); // try again, until we get a non-skipped frame |
| } else { |
| return curFrameSize(); |
| } |
| } |
| |
| unsigned MPEG1or2VideoStreamParser::parseSlice() { |
| // Note that we've already read the slice_start_code: |
| unsigned next4Bytes = PICTURE_START_CODE|fCurrentSliceNumber; |
| #ifdef DEBUG_SLICE |
| fprintf(stderr, "parsing slice: 0x%08x\n", next4Bytes); |
| #endif |
| |
| if (fSkippingCurrentPicture) { |
| // Skip all bytes that we see, up until we reach a code of some sort: |
| skipToNextCode(next4Bytes); |
| } else { |
| // Copy all bytes that we see, up until we reach a code of some sort: |
| saveToNextCode(next4Bytes); |
| } |
| |
| // The next thing to parse depends on the code that we just saw: |
| if (isSliceStartCode(next4Bytes)) { // common case |
| setParseState(PARSING_SLICE); |
| fCurrentSliceNumber = next4Bytes&0xFF; |
| } else { |
| // Because we don't see any more slices, we are assumed to have ended |
| // the current picture: |
| ++fPicturesSinceLastGOP; |
| ++usingSource()->fPictureCount; |
| usingSource()->fPictureEndMarker = True; // HACK ##### |
| |
| switch (next4Bytes) { |
| case SEQUENCE_END_CODE: { |
| setParseState(PARSING_VIDEO_SEQUENCE_HEADER); |
| break; |
| } |
| case VIDEO_SEQUENCE_HEADER_START_CODE: { |
| setParseState(PARSING_VIDEO_SEQUENCE_HEADER_SEEN_CODE); |
| break; |
| } |
| case GROUP_START_CODE: { |
| setParseState(PARSING_GOP_HEADER_SEEN_CODE); |
| break; |
| } |
| case PICTURE_START_CODE: { |
| setParseState(PARSING_PICTURE_HEADER); |
| break; |
| } |
| default: { |
| usingSource()->envir() << "MPEG1or2VideoStreamParser::parseSlice(): Saw unexpected code " |
| << (void*)next4Bytes << "\n"; |
| setParseState(PARSING_SLICE); // the safest way to recover... |
| break; |
| } |
| } |
| } |
| |
| // Compute this frame's timestamp: |
| usingSource()->computePresentationTime(fCurPicTemporalReference); |
| |
| if (fSkippingCurrentPicture) { |
| return parse(); // try again, until we get a non-skipped frame |
| } else { |
| return curFrameSize(); |
| } |
| } |