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(); }
void H264or5VideoStreamParser ::analyze_sei_payload(unsigned payloadType, unsigned payloadSize, u_int8_t* payload) { if (payloadType == 1/* pic_timing, for both H.264 and H.265 */) { BitVector bv(payload, 0, 8*payloadSize); DEBUG_TAB; if (CpbDpbDelaysPresentFlag) { unsigned cpb_removal_delay = bv.getBits(cpb_removal_delay_length_minus1 + 1); DEBUG_PRINT(cpb_removal_delay); unsigned dpb_output_delay = bv.getBits(dpb_output_delay_length_minus1 + 1); DEBUG_PRINT(dpb_output_delay); } if (pic_struct_present_flag) { unsigned pic_struct = bv.getBits(4); DEBUG_PRINT(pic_struct); // Use this to set "DeltaTfiDivisor" (which is used to compute the frame rate): double prevDeltaTfiDivisor = DeltaTfiDivisor; if (fHNumber == 264) { DeltaTfiDivisor = pic_struct == 0 ? 2.0 : pic_struct <= 2 ? 1.0 : pic_struct <= 4 ? 2.0 : pic_struct <= 6 ? 3.0 : pic_struct == 7 ? 4.0 : pic_struct == 8 ? 6.0 : 2.0; } else { // H.265 DeltaTfiDivisor = pic_struct == 0 ? 2.0 : pic_struct <= 2 ? 1.0 : pic_struct <= 4 ? 2.0 : pic_struct <= 6 ? 3.0 : pic_struct == 7 ? 2.0 : pic_struct == 8 ? 3.0 : pic_struct <= 12 ? 1.0 : 2.0; } // If "DeltaTfiDivisor" has changed, and we've already computed the frame rate, then // adjust it, based on the new value of "DeltaTfiDivisor": if (DeltaTfiDivisor != prevDeltaTfiDivisor && fParsedFrameRate != 0.0) { usingSource()->fFrameRate = fParsedFrameRate = fParsedFrameRate*(prevDeltaTfiDivisor/DeltaTfiDivisor); #ifdef DEBUG fprintf(stderr, "Changed frame rate to %f fps\n", usingSource()->fFrameRate); #endif } } // Ignore the rest of the payload (timestamps) for now... ##### } }
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(); }
void MPEG1or2VideoStreamParser::saveCurrentVSH() { unsigned frameSize = curFrameSize(); if (frameSize > sizeof fSavedVSHBuffer) return; // too big to save memmove(fSavedVSHBuffer, fStartOfFrame, frameSize); fSavedVSHSize = frameSize; fSavedVSHTimestamp = usingSource()->getCurrentPTS(); }
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::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; }
Boolean isVCL(u_int8_t nal_unit_type) { return usingSource()->isVCL(nal_unit_type); }
unsigned H264VideoStreamParser::parse() { try { // The stream must start with a 0x00000001: if (!fHaveSeenFirstStartCode) { // Skip over any input bytes that precede the first 0x00000001: u_int32_t first4Bytes; while ((first4Bytes = test4Bytes()) != 0x00000001) { get1Byte(); setParseState(); // ensures that we progress over bad data } skipBytes(4); // skip this initial code setParseState(); fHaveSeenFirstStartCode = True; // from now on } if (fOutputStartCodeSize > 0) { // Include a start code in the output: save4Bytes(0x00000001); } // Then save everything up until the next 0x00000001 (4 bytes) or 0x000001 (3 bytes), or we hit EOF. // Also make note of the first byte, because it contains the "nal_unit_type": if (haveSeenEOF()) { // We hit EOF the last time that we tried to parse this data, so we know that any remaining unparsed data // forms a complete NAL unit, and that there's no 'start code' at the end: unsigned remainingDataSize = totNumValidBytes() - curOffset(); while (remainingDataSize > 0) { saveByte(get1Byte()); --remainingDataSize; } if (!fHaveSeenFirstByteOfNALUnit) { // There's no remaining NAL unit. (void)get1Byte(); // forces another read, which will cause EOF to get handled for real this time return 0; } #ifdef DEBUG fprintf(stderr, "This NAL unit (%d bytes) ends with EOF\n", curFrameSize()-fOutputStartCodeSize); #endif } else { u_int32_t next4Bytes = test4Bytes(); if (!fHaveSeenFirstByteOfNALUnit) { fFirstByteOfNALUnit = next4Bytes>>24; fHaveSeenFirstByteOfNALUnit = True; } while (next4Bytes != 0x00000001 && (next4Bytes&0xFFFFFF00) != 0x00000100) { // We save at least some of "next4Bytes". if ((unsigned)(next4Bytes&0xFF) > 1) { // Common case: 0x00000001 or 0x000001 definitely doesn't begin anywhere in "next4Bytes", so we save all of it: save4Bytes(next4Bytes); skipBytes(4); } else { // Save the first byte, and continue testing the rest: saveByte(next4Bytes>>24); skipBytes(1); } setParseState(); // ensures forward progress next4Bytes = test4Bytes(); } // Assert: next4Bytes starts with 0x00000001 or 0x000001, and we've saved all previous bytes (forming a complete NAL unit). // Skip over these remaining bytes, up until the start of the next NAL unit: if (next4Bytes == 0x00000001) { skipBytes(4); } else { skipBytes(3); } } u_int8_t nal_ref_idc = (fFirstByteOfNALUnit&0x60)>>5; u_int8_t nal_unit_type = fFirstByteOfNALUnit&0x1F; fHaveSeenFirstByteOfNALUnit = False; // for the next NAL unit that we parse #ifdef DEBUG fprintf(stderr, "Parsed %d-byte NAL-unit (nal_ref_idc: %d, nal_unit_type: %d (\"%s\"))\n", curFrameSize()-fOutputStartCodeSize, nal_ref_idc, nal_unit_type, nal_unit_type_description[nal_unit_type]); #endif switch (nal_unit_type) { case 6: { // Supplemental enhancement information (SEI) analyze_sei_data(); // Later, perhaps adjust "fPresentationTime" if we saw a "pic_timing" SEI payload??? ##### break; } case 7: { // Sequence parameter set // First, save a copy of this NAL unit, in case the downstream object wants to see it: usingSource()->saveCopyOfSPS(fStartOfFrame + fOutputStartCodeSize, fTo - fStartOfFrame - fOutputStartCodeSize); // Parse this NAL unit to check whether frame rate information is present: unsigned num_units_in_tick, time_scale, fixed_frame_rate_flag; analyze_seq_parameter_set_data(num_units_in_tick, time_scale, fixed_frame_rate_flag); if (time_scale > 0 && num_units_in_tick > 0) { usingSource()->fFrameRate = time_scale/(2.0*num_units_in_tick); #ifdef DEBUG fprintf(stderr, "Set frame rate to %f fps\n", usingSource()->fFrameRate); if (fixed_frame_rate_flag == 0) { fprintf(stderr, "\tWARNING: \"fixed_frame_rate_flag\" was not set\n"); } #endif } else { #ifdef DEBUG fprintf(stderr, "\tThis \"Sequence Parameter Set\" NAL unit contained no frame rate information, so we use a default frame rate of %f fps\n", usingSource()->fFrameRate); #endif } break; } case 8: { // Picture parameter set // Save a copy of this NAL unit, in case the downstream object wants to see it: usingSource()->saveCopyOfPPS(fStartOfFrame + fOutputStartCodeSize, fTo - fStartOfFrame - fOutputStartCodeSize); } } usingSource()->setPresentationTime(); #ifdef DEBUG unsigned long secs = (unsigned long)usingSource()->fPresentationTime.tv_sec; unsigned uSecs = (unsigned)usingSource()->fPresentationTime.tv_usec; fprintf(stderr, "\tPresentation time: %lu.%06u\n", secs, uSecs); #endif // If this NAL unit is a VCL NAL unit, we also scan the start of the next NAL unit, to determine whether this NAL unit // ends the current 'access unit'. We need this information to figure out when to increment "fPresentationTime". // (RTP streamers also need to know this in order to figure out whether or not to set the "M" bit.) Boolean thisNALUnitEndsAccessUnit = False; // until we learn otherwise if (haveSeenEOF()) { // There is no next NAL unit, so we assume that this one ends the current 'access unit': thisNALUnitEndsAccessUnit = True; } else { Boolean const isVCL = nal_unit_type <= 5 && nal_unit_type > 0; // Would need to include type 20 for SVC and MVC ##### if (isVCL) { u_int32_t first4BytesOfNextNALUnit = test4Bytes(); u_int8_t firstByteOfNextNALUnit = first4BytesOfNextNALUnit>>24; u_int8_t next_nal_ref_idc = (firstByteOfNextNALUnit&0x60)>>5; u_int8_t next_nal_unit_type = firstByteOfNextNALUnit&0x1F; if (next_nal_unit_type >= 6) { // The next NAL unit is not a VCL; therefore, we assume that this NAL unit ends the current 'access unit': #ifdef DEBUG fprintf(stderr, "\t(The next NAL unit is not a VCL)\n"); #endif thisNALUnitEndsAccessUnit = True; } else { // The next NAL unit is also a VCL. We need to examine it a little to figure out if it's a different 'access unit'. // (We use many of the criteria described in section 7.4.1.2.4 of the H.264 specification.) Boolean IdrPicFlag = nal_unit_type == 5; Boolean next_IdrPicFlag = next_nal_unit_type == 5; if (next_IdrPicFlag != IdrPicFlag) { // IdrPicFlag differs in value #ifdef DEBUG fprintf(stderr, "\t(IdrPicFlag differs in value)\n"); #endif thisNALUnitEndsAccessUnit = True; } else if (next_nal_ref_idc != nal_ref_idc && next_nal_ref_idc*nal_ref_idc == 0) { // nal_ref_idc differs in value with one of the nal_ref_idc values being equal to 0 #ifdef DEBUG fprintf(stderr, "\t(nal_ref_idc differs in value with one of the nal_ref_idc values being equal to 0)\n"); #endif thisNALUnitEndsAccessUnit = True; } else if ((nal_unit_type == 1 || nal_unit_type == 2 || nal_unit_type == 5) && (next_nal_unit_type == 1 || next_nal_unit_type == 2 || next_nal_unit_type == 5)) { // Both this and the next NAL units begin with a "slice_header". // Parse this (for each), to get parameters that we can compare: // Current NAL unit's "slice_header": unsigned frame_num, pic_parameter_set_id, idr_pic_id; Boolean field_pic_flag, bottom_field_flag; analyze_slice_header(fStartOfFrame + fOutputStartCodeSize, fTo, nal_unit_type, frame_num, pic_parameter_set_id, idr_pic_id, field_pic_flag, bottom_field_flag); // Next NAL unit's "slice_header": #ifdef DEBUG fprintf(stderr, " Next NAL unit's slice_header:\n"); #endif u_int8_t next_slice_header[NUM_NEXT_SLICE_HEADER_BYTES_TO_ANALYZE]; testBytes(next_slice_header, sizeof next_slice_header); unsigned next_frame_num, next_pic_parameter_set_id, next_idr_pic_id; Boolean next_field_pic_flag, next_bottom_field_flag; analyze_slice_header(next_slice_header, &next_slice_header[sizeof next_slice_header], next_nal_unit_type, next_frame_num, next_pic_parameter_set_id, next_idr_pic_id, next_field_pic_flag, next_bottom_field_flag); if (next_frame_num != frame_num) { // frame_num differs in value #ifdef DEBUG fprintf(stderr, "\t(frame_num differs in value)\n"); #endif thisNALUnitEndsAccessUnit = True; } else if (next_pic_parameter_set_id != pic_parameter_set_id) { // pic_parameter_set_id differs in value #ifdef DEBUG fprintf(stderr, "\t(pic_parameter_set_id differs in value)\n"); #endif thisNALUnitEndsAccessUnit = True; } else if (next_field_pic_flag != field_pic_flag) { // field_pic_flag differs in value #ifdef DEBUG fprintf(stderr, "\t(field_pic_flag differs in value)\n"); #endif thisNALUnitEndsAccessUnit = True; } else if (next_bottom_field_flag != bottom_field_flag) { // bottom_field_flag differs in value #ifdef DEBUG fprintf(stderr, "\t(bottom_field_flag differs in value)\n"); #endif thisNALUnitEndsAccessUnit = True; } else if (next_IdrPicFlag == 1 && next_idr_pic_id != idr_pic_id) { // IdrPicFlag is equal to 1 for both and idr_pic_id differs in value // Note: We already know that IdrPicFlag is the same for both. #ifdef DEBUG fprintf(stderr, "\t(IdrPicFlag is equal to 1 for both and idr_pic_id differs in value)\n"); #endif thisNALUnitEndsAccessUnit = True; } } } } } if (thisNALUnitEndsAccessUnit) { #ifdef DEBUG fprintf(stderr, "*****This NAL unit ends the current access unit*****\n"); #endif usingSource()->fPictureEndMarker = True; ++usingSource()->fPictureCount; // Note that the presentation time for the next NAL unit will be different: struct timeval& nextPT = usingSource()->fNextPresentationTime; // alias nextPT = usingSource()->fPresentationTime; double nextFraction = nextPT.tv_usec/1000000.0 + 1/usingSource()->fFrameRate; unsigned nextSecsIncrement = (long)nextFraction; nextPT.tv_sec += (long)nextSecsIncrement; nextPT.tv_usec = (long)((nextFraction - nextSecsIncrement)*1000000); } setParseState(); return curFrameSize(); } catch (int /*e*/) {
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(); } }
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(); }
Boolean MPEG1or2VideoStreamParser::needToUseSavedVSH() { return usingSource()->getCurrentPTS() > fSavedVSHTimestamp + fVSHPeriod && fSavedVSHSize > 0; }