| 1 | /********** |
| 2 | This library is free software; you can redistribute it and/or modify it under |
| 3 | the terms of the GNU Lesser General Public License as published by the |
| 4 | Free Software Foundation; either version 3 of the License, or (at your |
| 5 | option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.) |
| 6 | |
| 7 | This library is distributed in the hope that it will be useful, but WITHOUT |
| 8 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 9 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for |
| 10 | more details. |
| 11 | |
| 12 | You should have received a copy of the GNU Lesser General Public License |
| 13 | along with this library; if not, write to the Free Software Foundation, Inc., |
| 14 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 15 | **********/ |
| 16 | // "liveMedia" |
| 17 | // Copyright (c) 1996-2020 Live Networks, Inc. All rights reserved. |
| 18 | // A filter that breaks up an MPEG 1 or 2 video elementary stream into |
| 19 | // frames for: Video_Sequence_Header, GOP_Header, Picture_Header |
| 20 | // Implementation |
| 21 | |
| 22 | #include "MPEG1or2VideoStreamFramer.hh" |
| 23 | #include "MPEGVideoStreamParser.hh" |
| 24 | #include <string.h> |
| 25 | |
| 26 | ////////// MPEG1or2VideoStreamParser definition ////////// |
| 27 | |
| 28 | // An enum representing the current state of the parser: |
| 29 | enum MPEGParseState { |
| 30 | , |
| 31 | , |
| 32 | , |
| 33 | , |
| 34 | , |
| 35 | PARSING_SLICE |
| 36 | }; |
| 37 | |
| 38 | #define VSH_MAX_SIZE 1000 |
| 39 | |
| 40 | class MPEG1or2VideoStreamParser: public MPEGVideoStreamParser { |
| 41 | public: |
| 42 | MPEG1or2VideoStreamParser(MPEG1or2VideoStreamFramer* usingSource, |
| 43 | FramedSource* inputSource, |
| 44 | Boolean iFramesOnly, double vshPeriod); |
| 45 | virtual ~MPEG1or2VideoStreamParser(); |
| 46 | |
| 47 | private: // redefined virtual functions: |
| 48 | virtual void flushInput(); |
| 49 | virtual unsigned parse(); |
| 50 | |
| 51 | private: |
| 52 | void reset(); |
| 53 | |
| 54 | MPEG1or2VideoStreamFramer* usingSource() { |
| 55 | return (MPEG1or2VideoStreamFramer*)fUsingSource; |
| 56 | } |
| 57 | void setParseState(MPEGParseState parseState); |
| 58 | |
| 59 | unsigned parseVideoSequenceHeader(Boolean haveSeenStartCode); |
| 60 | unsigned parseGOPHeader(Boolean haveSeenStartCode); |
| 61 | unsigned parsePictureHeader(); |
| 62 | unsigned parseSlice(); |
| 63 | |
| 64 | private: |
| 65 | MPEGParseState fCurrentParseState; |
| 66 | unsigned fPicturesSinceLastGOP; |
| 67 | // can be used to compute timestamp for a video_sequence_header |
| 68 | unsigned short fCurPicTemporalReference; |
| 69 | // used to compute slice timestamp |
| 70 | unsigned char fCurrentSliceNumber; // set when parsing a slice |
| 71 | |
| 72 | // A saved copy of the most recently seen 'video_sequence_header', |
| 73 | // in case we need to insert it into the stream periodically: |
| 74 | unsigned char fSavedVSHBuffer[VSH_MAX_SIZE]; |
| 75 | unsigned fSavedVSHSize; |
| 76 | double fSavedVSHTimestamp; |
| 77 | double fVSHPeriod; |
| 78 | Boolean fIFramesOnly, fSkippingCurrentPicture; |
| 79 | |
| 80 | void saveCurrentVSH(); |
| 81 | Boolean needToUseSavedVSH(); |
| 82 | unsigned useSavedVSH(); // returns the size of the saved VSH |
| 83 | }; |
| 84 | |
| 85 | |
| 86 | ////////// MPEG1or2VideoStreamFramer implementation ////////// |
| 87 | |
| 88 | MPEG1or2VideoStreamFramer::MPEG1or2VideoStreamFramer(UsageEnvironment& env, |
| 89 | FramedSource* inputSource, |
| 90 | Boolean iFramesOnly, |
| 91 | double vshPeriod, |
| 92 | Boolean createParser) |
| 93 | : MPEGVideoStreamFramer(env, inputSource) { |
| 94 | fParser = createParser |
| 95 | ? new MPEG1or2VideoStreamParser(this, inputSource, |
| 96 | iFramesOnly, vshPeriod) |
| 97 | : NULL; |
| 98 | } |
| 99 | |
| 100 | MPEG1or2VideoStreamFramer::~MPEG1or2VideoStreamFramer() { |
| 101 | } |
| 102 | |
| 103 | MPEG1or2VideoStreamFramer* |
| 104 | MPEG1or2VideoStreamFramer::createNew(UsageEnvironment& env, |
| 105 | FramedSource* inputSource, |
| 106 | Boolean iFramesOnly, |
| 107 | double vshPeriod) { |
| 108 | // Need to add source type checking here??? ##### |
| 109 | return new MPEG1or2VideoStreamFramer(env, inputSource, iFramesOnly, vshPeriod); |
| 110 | } |
| 111 | |
| 112 | double MPEG1or2VideoStreamFramer::getCurrentPTS() const { |
| 113 | return fPresentationTime.tv_sec + fPresentationTime.tv_usec/1000000.0; |
| 114 | } |
| 115 | |
| 116 | Boolean MPEG1or2VideoStreamFramer::isMPEG1or2VideoStreamFramer() const { |
| 117 | return True; |
| 118 | } |
| 119 | |
| 120 | ////////// MPEG1or2VideoStreamParser implementation ////////// |
| 121 | |
| 122 | MPEG1or2VideoStreamParser |
| 123 | ::MPEG1or2VideoStreamParser(MPEG1or2VideoStreamFramer* usingSource, |
| 124 | FramedSource* inputSource, |
| 125 | Boolean iFramesOnly, double vshPeriod) |
| 126 | : MPEGVideoStreamParser(usingSource, inputSource), |
| 127 | fCurrentParseState(PARSING_VIDEO_SEQUENCE_HEADER), |
| 128 | fVSHPeriod(vshPeriod), fIFramesOnly(iFramesOnly) { |
| 129 | reset(); |
| 130 | } |
| 131 | |
| 132 | MPEG1or2VideoStreamParser::~MPEG1or2VideoStreamParser() { |
| 133 | } |
| 134 | |
| 135 | void MPEG1or2VideoStreamParser::setParseState(MPEGParseState parseState) { |
| 136 | fCurrentParseState = parseState; |
| 137 | MPEGVideoStreamParser::setParseState(); |
| 138 | } |
| 139 | |
| 140 | void MPEG1or2VideoStreamParser::reset() { |
| 141 | fPicturesSinceLastGOP = 0; |
| 142 | fCurPicTemporalReference = 0; |
| 143 | fCurrentSliceNumber = 0; |
| 144 | fSavedVSHSize = 0; |
| 145 | fSkippingCurrentPicture = False; |
| 146 | } |
| 147 | |
| 148 | void MPEG1or2VideoStreamParser::flushInput() { |
| 149 | reset(); |
| 150 | StreamParser::flushInput(); |
| 151 | if (fCurrentParseState != PARSING_VIDEO_SEQUENCE_HEADER) { |
| 152 | setParseState(PARSING_GOP_HEADER); // start from the next GOP |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | unsigned MPEG1or2VideoStreamParser::parse() { |
| 157 | try { |
| 158 | switch (fCurrentParseState) { |
| 159 | case PARSING_VIDEO_SEQUENCE_HEADER: { |
| 160 | return parseVideoSequenceHeader(False); |
| 161 | } |
| 162 | case PARSING_VIDEO_SEQUENCE_HEADER_SEEN_CODE: { |
| 163 | return parseVideoSequenceHeader(True); |
| 164 | } |
| 165 | case PARSING_GOP_HEADER: { |
| 166 | return parseGOPHeader(False); |
| 167 | } |
| 168 | case PARSING_GOP_HEADER_SEEN_CODE: { |
| 169 | return parseGOPHeader(True); |
| 170 | } |
| 171 | case PARSING_PICTURE_HEADER: { |
| 172 | return parsePictureHeader(); |
| 173 | } |
| 174 | case PARSING_SLICE: { |
| 175 | return parseSlice(); |
| 176 | } |
| 177 | default: { |
| 178 | return 0; // shouldn't happen |
| 179 | } |
| 180 | } |
| 181 | } catch (int /*e*/) { |
| 182 | #ifdef DEBUG |
| 183 | fprintf(stderr, "MPEG1or2VideoStreamParser::parse() EXCEPTION (This is normal behavior - *not* an error)\n" ); |
| 184 | #endif |
| 185 | return 0; // the parsing got interrupted |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | void MPEG1or2VideoStreamParser::saveCurrentVSH() { |
| 190 | unsigned frameSize = curFrameSize(); |
| 191 | if (frameSize > sizeof fSavedVSHBuffer) return; // too big to save |
| 192 | |
| 193 | memmove(fSavedVSHBuffer, fStartOfFrame, frameSize); |
| 194 | fSavedVSHSize = frameSize; |
| 195 | fSavedVSHTimestamp = usingSource()->getCurrentPTS(); |
| 196 | } |
| 197 | |
| 198 | Boolean MPEG1or2VideoStreamParser::needToUseSavedVSH() { |
| 199 | return usingSource()->getCurrentPTS() > fSavedVSHTimestamp+fVSHPeriod |
| 200 | && fSavedVSHSize > 0; |
| 201 | } |
| 202 | |
| 203 | unsigned MPEG1or2VideoStreamParser::useSavedVSH() { |
| 204 | unsigned bytesToUse = fSavedVSHSize; |
| 205 | unsigned maxBytesToUse = fLimit - fStartOfFrame; |
| 206 | if (bytesToUse > maxBytesToUse) bytesToUse = maxBytesToUse; |
| 207 | |
| 208 | memmove(fStartOfFrame, fSavedVSHBuffer, bytesToUse); |
| 209 | |
| 210 | // Also reset the saved timestamp: |
| 211 | fSavedVSHTimestamp = usingSource()->getCurrentPTS(); |
| 212 | |
| 213 | #ifdef DEBUG |
| 214 | fprintf(stderr, "used saved video_sequence_header (%d bytes)\n" , bytesToUse); |
| 215 | #endif |
| 216 | return bytesToUse; |
| 217 | } |
| 218 | |
| 219 | #define 0x000001B3 |
| 220 | #define GROUP_START_CODE 0x000001B8 |
| 221 | #define PICTURE_START_CODE 0x00000100 |
| 222 | #define SEQUENCE_END_CODE 0x000001B7 |
| 223 | |
| 224 | static double const frameRateFromCode[] = { |
| 225 | 0.0, // forbidden |
| 226 | 24000/1001.0, // approx 23.976 |
| 227 | 24.0, |
| 228 | 25.0, |
| 229 | 30000/1001.0, // approx 29.97 |
| 230 | 30.0, |
| 231 | 50.0, |
| 232 | 60000/1001.0, // approx 59.94 |
| 233 | 60.0, |
| 234 | 0.0, // reserved |
| 235 | 0.0, // reserved |
| 236 | 0.0, // reserved |
| 237 | 0.0, // reserved |
| 238 | 0.0, // reserved |
| 239 | 0.0, // reserved |
| 240 | 0.0 // reserved |
| 241 | }; |
| 242 | |
| 243 | unsigned MPEG1or2VideoStreamParser |
| 244 | ::(Boolean haveSeenStartCode) { |
| 245 | #ifdef DEBUG |
| 246 | fprintf(stderr, "parsing video sequence header\n" ); |
| 247 | #endif |
| 248 | unsigned first4Bytes; |
| 249 | if (!haveSeenStartCode) { |
| 250 | while ((first4Bytes = test4Bytes()) != VIDEO_SEQUENCE_HEADER_START_CODE) { |
| 251 | #ifdef DEBUG |
| 252 | fprintf(stderr, "ignoring non video sequence header: 0x%08x\n" , first4Bytes); |
| 253 | #endif |
| 254 | get1Byte(); setParseState(PARSING_VIDEO_SEQUENCE_HEADER); |
| 255 | // ensures we progress over bad data |
| 256 | } |
| 257 | first4Bytes = get4Bytes(); |
| 258 | } else { |
| 259 | // We've already seen the start code |
| 260 | first4Bytes = VIDEO_SEQUENCE_HEADER_START_CODE; |
| 261 | } |
| 262 | save4Bytes(first4Bytes); |
| 263 | |
| 264 | // Next, extract the size and rate parameters from the next 8 bytes |
| 265 | unsigned paramWord1 = get4Bytes(); |
| 266 | save4Bytes(paramWord1); |
| 267 | unsigned next4Bytes = get4Bytes(); |
| 268 | #ifdef DEBUG |
| 269 | unsigned short horizontal_size_value = (paramWord1&0xFFF00000)>>(32-12); |
| 270 | unsigned short vertical_size_value = (paramWord1&0x000FFF00)>>8; |
| 271 | unsigned char aspect_ratio_information = (paramWord1&0x000000F0)>>4; |
| 272 | #endif |
| 273 | unsigned char frame_rate_code = (paramWord1&0x0000000F); |
| 274 | usingSource()->fFrameRate = frameRateFromCode[frame_rate_code]; |
| 275 | #ifdef DEBUG |
| 276 | unsigned bit_rate_value = (next4Bytes&0xFFFFC000)>>(32-18); |
| 277 | unsigned vbv_buffer_size_value = (next4Bytes&0x00001FF8)>>3; |
| 278 | 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); |
| 279 | #endif |
| 280 | |
| 281 | // Now, copy all bytes that we see, up until we reach a GROUP_START_CODE |
| 282 | // or a PICTURE_START_CODE: |
| 283 | do { |
| 284 | saveToNextCode(next4Bytes); |
| 285 | } while (next4Bytes != GROUP_START_CODE && next4Bytes != PICTURE_START_CODE); |
| 286 | |
| 287 | setParseState((next4Bytes == GROUP_START_CODE) |
| 288 | ? PARSING_GOP_HEADER_SEEN_CODE : PARSING_PICTURE_HEADER); |
| 289 | |
| 290 | // Compute this frame's timestamp by noting how many pictures we've seen |
| 291 | // since the last GOP header: |
| 292 | usingSource()->computePresentationTime(fPicturesSinceLastGOP); |
| 293 | |
| 294 | // Save this video_sequence_header, in case we need to insert a copy |
| 295 | // into the stream later: |
| 296 | saveCurrentVSH(); |
| 297 | |
| 298 | return curFrameSize(); |
| 299 | } |
| 300 | |
| 301 | unsigned MPEG1or2VideoStreamParser::(Boolean haveSeenStartCode) { |
| 302 | // First check whether we should insert a previously-saved |
| 303 | // 'video_sequence_header' here: |
| 304 | if (needToUseSavedVSH()) return useSavedVSH(); |
| 305 | |
| 306 | #ifdef DEBUG |
| 307 | fprintf(stderr, "parsing GOP header\n" ); |
| 308 | #endif |
| 309 | unsigned first4Bytes; |
| 310 | if (!haveSeenStartCode) { |
| 311 | while ((first4Bytes = test4Bytes()) != GROUP_START_CODE) { |
| 312 | #ifdef DEBUG |
| 313 | fprintf(stderr, "ignoring non GOP start code: 0x%08x\n" , first4Bytes); |
| 314 | #endif |
| 315 | get1Byte(); setParseState(PARSING_GOP_HEADER); |
| 316 | // ensures we progress over bad data |
| 317 | } |
| 318 | first4Bytes = get4Bytes(); |
| 319 | } else { |
| 320 | // We've already seen the GROUP_START_CODE |
| 321 | first4Bytes = GROUP_START_CODE; |
| 322 | } |
| 323 | save4Bytes(first4Bytes); |
| 324 | |
| 325 | // Next, extract the (25-bit) time code from the next 4 bytes: |
| 326 | unsigned next4Bytes = get4Bytes(); |
| 327 | unsigned time_code = (next4Bytes&0xFFFFFF80)>>(32-25); |
| 328 | #if defined(DEBUG) || defined(DEBUG_TIMESTAMPS) |
| 329 | Boolean drop_frame_flag = (time_code&0x01000000) != 0; |
| 330 | #endif |
| 331 | unsigned time_code_hours = (time_code&0x00F80000)>>19; |
| 332 | unsigned time_code_minutes = (time_code&0x0007E000)>>13; |
| 333 | unsigned time_code_seconds = (time_code&0x00000FC0)>>6; |
| 334 | unsigned time_code_pictures = (time_code&0x0000003F); |
| 335 | #if defined(DEBUG) || defined(DEBUG_TIMESTAMPS) |
| 336 | 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); |
| 337 | #endif |
| 338 | #ifdef DEBUG |
| 339 | Boolean closed_gop = (next4Bytes&0x00000040) != 0; |
| 340 | Boolean broken_link = (next4Bytes&0x00000020) != 0; |
| 341 | fprintf(stderr, "closed_gop: %d, broken_link: %d\n" , closed_gop, broken_link); |
| 342 | #endif |
| 343 | |
| 344 | // Now, copy all bytes that we see, up until we reach a PICTURE_START_CODE: |
| 345 | do { |
| 346 | saveToNextCode(next4Bytes); |
| 347 | } while (next4Bytes != PICTURE_START_CODE); |
| 348 | |
| 349 | // Record the time code: |
| 350 | usingSource()->setTimeCode(time_code_hours, time_code_minutes, |
| 351 | time_code_seconds, time_code_pictures, |
| 352 | fPicturesSinceLastGOP); |
| 353 | |
| 354 | fPicturesSinceLastGOP = 0; |
| 355 | |
| 356 | // Compute this frame's timestamp: |
| 357 | usingSource()->computePresentationTime(0); |
| 358 | |
| 359 | setParseState(PARSING_PICTURE_HEADER); |
| 360 | |
| 361 | return curFrameSize(); |
| 362 | } |
| 363 | |
| 364 | inline Boolean isSliceStartCode(unsigned fourBytes) { |
| 365 | if ((fourBytes&0xFFFFFF00) != 0x00000100) return False; |
| 366 | |
| 367 | unsigned char lastByte = fourBytes&0xFF; |
| 368 | return lastByte <= 0xAF && lastByte >= 1; |
| 369 | } |
| 370 | |
| 371 | unsigned MPEG1or2VideoStreamParser::() { |
| 372 | #ifdef DEBUG |
| 373 | fprintf(stderr, "parsing picture header\n" ); |
| 374 | #endif |
| 375 | // Note that we've already read the PICTURE_START_CODE |
| 376 | // Next, extract the temporal reference from the next 4 bytes: |
| 377 | unsigned next4Bytes = get4Bytes(); |
| 378 | unsigned short temporal_reference = (next4Bytes&0xFFC00000)>>(32-10); |
| 379 | unsigned char picture_coding_type = (next4Bytes&0x00380000)>>19; |
| 380 | #ifdef DEBUG |
| 381 | unsigned short vbv_delay = (next4Bytes&0x0007FFF8)>>3; |
| 382 | fprintf(stderr, "temporal_reference: %d, picture_coding_type: %d, vbv_delay: %d\n" , temporal_reference, picture_coding_type, vbv_delay); |
| 383 | #endif |
| 384 | |
| 385 | fSkippingCurrentPicture = fIFramesOnly && picture_coding_type != 1; |
| 386 | if (fSkippingCurrentPicture) { |
| 387 | // Skip all bytes that we see, up until we reach a slice_start_code: |
| 388 | do { |
| 389 | skipToNextCode(next4Bytes); |
| 390 | } while (!isSliceStartCode(next4Bytes)); |
| 391 | } else { |
| 392 | // Save the PICTURE_START_CODE that we've already read: |
| 393 | save4Bytes(PICTURE_START_CODE); |
| 394 | |
| 395 | // Copy all bytes that we see, up until we reach a slice_start_code: |
| 396 | do { |
| 397 | saveToNextCode(next4Bytes); |
| 398 | } while (!isSliceStartCode(next4Bytes)); |
| 399 | } |
| 400 | |
| 401 | setParseState(PARSING_SLICE); |
| 402 | |
| 403 | fCurrentSliceNumber = next4Bytes&0xFF; |
| 404 | |
| 405 | // Record the temporal reference: |
| 406 | fCurPicTemporalReference = temporal_reference; |
| 407 | |
| 408 | // Compute this frame's timestamp: |
| 409 | usingSource()->computePresentationTime(fCurPicTemporalReference); |
| 410 | |
| 411 | if (fSkippingCurrentPicture) { |
| 412 | return parse(); // try again, until we get a non-skipped frame |
| 413 | } else { |
| 414 | return curFrameSize(); |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | unsigned MPEG1or2VideoStreamParser::parseSlice() { |
| 419 | // Note that we've already read the slice_start_code: |
| 420 | unsigned next4Bytes = PICTURE_START_CODE|fCurrentSliceNumber; |
| 421 | #ifdef DEBUG_SLICE |
| 422 | fprintf(stderr, "parsing slice: 0x%08x\n" , next4Bytes); |
| 423 | #endif |
| 424 | |
| 425 | if (fSkippingCurrentPicture) { |
| 426 | // Skip all bytes that we see, up until we reach a code of some sort: |
| 427 | skipToNextCode(next4Bytes); |
| 428 | } else { |
| 429 | // Copy all bytes that we see, up until we reach a code of some sort: |
| 430 | saveToNextCode(next4Bytes); |
| 431 | } |
| 432 | |
| 433 | // The next thing to parse depends on the code that we just saw: |
| 434 | if (isSliceStartCode(next4Bytes)) { // common case |
| 435 | setParseState(PARSING_SLICE); |
| 436 | fCurrentSliceNumber = next4Bytes&0xFF; |
| 437 | } else { |
| 438 | // Because we don't see any more slices, we are assumed to have ended |
| 439 | // the current picture: |
| 440 | ++fPicturesSinceLastGOP; |
| 441 | ++usingSource()->fPictureCount; |
| 442 | usingSource()->fPictureEndMarker = True; // HACK ##### |
| 443 | |
| 444 | switch (next4Bytes) { |
| 445 | case SEQUENCE_END_CODE: { |
| 446 | setParseState(PARSING_VIDEO_SEQUENCE_HEADER); |
| 447 | break; |
| 448 | } |
| 449 | case VIDEO_SEQUENCE_HEADER_START_CODE: { |
| 450 | setParseState(PARSING_VIDEO_SEQUENCE_HEADER_SEEN_CODE); |
| 451 | break; |
| 452 | } |
| 453 | case GROUP_START_CODE: { |
| 454 | setParseState(PARSING_GOP_HEADER_SEEN_CODE); |
| 455 | break; |
| 456 | } |
| 457 | case PICTURE_START_CODE: { |
| 458 | setParseState(PARSING_PICTURE_HEADER); |
| 459 | break; |
| 460 | } |
| 461 | default: { |
| 462 | usingSource()->envir() << "MPEG1or2VideoStreamParser::parseSlice(): Saw unexpected code " |
| 463 | << (void*)next4Bytes << "\n" ; |
| 464 | setParseState(PARSING_SLICE); // the safest way to recover... |
| 465 | break; |
| 466 | } |
| 467 | } |
| 468 | } |
| 469 | |
| 470 | // Compute this frame's timestamp: |
| 471 | usingSource()->computePresentationTime(fCurPicTemporalReference); |
| 472 | |
| 473 | if (fSkippingCurrentPicture) { |
| 474 | return parse(); // try again, until we get a non-skipped frame |
| 475 | } else { |
| 476 | return curFrameSize(); |
| 477 | } |
| 478 | } |
| 479 | |