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 | |