summaryrefslogtreecommitdiff
path: root/apps/plugins/mpegplayer/mpeg_parser.c
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2007-12-29 19:46:35 +0000
committerMichael Sevakis <jethead71@rockbox.org>2007-12-29 19:46:35 +0000
commita222f27c4a17ed8f9809cda7861fe5f23d4cc0c1 (patch)
treed393a23d83549f99772bb156e59ffb88725148b6 /apps/plugins/mpegplayer/mpeg_parser.c
parent1d0f6b90ff43776e55b4b9f062c9bea3f99aa768 (diff)
downloadrockbox-a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1.tar.gz
rockbox-a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1.zip
mpegplayer: Make playback engine fully seekable and frame-accurate and split into logical parts. Be sure to have all current features work. Actual UI for seeking will be added soon. Recommended GOP size is about 15-30 frames depending on target or seeking can be slow with really long GOPs (nature of MPEG video). More refined encoding recommendations for a particular player should be posted soon.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15977 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/plugins/mpegplayer/mpeg_parser.c')
-rw-r--r--apps/plugins/mpegplayer/mpeg_parser.c1182
1 files changed, 1182 insertions, 0 deletions
diff --git a/apps/plugins/mpegplayer/mpeg_parser.c b/apps/plugins/mpegplayer/mpeg_parser.c
new file mode 100644
index 0000000000..c996f9540d
--- /dev/null
+++ b/apps/plugins/mpegplayer/mpeg_parser.c
@@ -0,0 +1,1182 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Parser for MPEG streams
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22#include "mpegplayer.h"
23
24struct stream_parser str_parser NOCACHEBSS_ATTR;
25
26static void parser_init_state(void)
27{
28 str_parser.last_seek_time = 0;
29 str_parser.format = STREAM_FMT_UNKNOWN;
30 str_parser.start_pts = INVALID_TIMESTAMP;
31 str_parser.end_pts = INVALID_TIMESTAMP;
32 str_parser.flags = 0;
33 str_parser.dims.w = 0;
34 str_parser.dims.h = 0;
35}
36
37/* Place the stream in a state to begin parsing - sync will be performed
38 * first */
39void str_initialize(struct stream *str, off_t pos)
40{
41 /* Initial positions start here */
42 str->hdr.win_left = str->hdr.win_right = pos;
43 /* No packet */
44 str->curr_packet = NULL;
45 /* Pick up parsing from this point in the buffer */
46 str->curr_packet_end = disk_buf_offset2ptr(pos);
47 /* No flags */
48 str->pkt_flags = 0;
49 /* Sync first */
50 str->state = SSTATE_SYNC;
51}
52
53/* Place the stream in an end of data state */
54void str_end_of_stream(struct stream *str)
55{
56 /* Offsets that prevent this stream from being included in the
57 * min left/max right window so that no buffering is triggered on
58 * its behalf. Set right to the min first so a thread reading the
59 * overall window gets doesn't see this as valid no matter what the
60 * file length. */
61 str->hdr.win_right = LONG_MIN;
62 str->hdr.win_left = LONG_MAX;
63 /* No packets */
64 str->curr_packet = str->curr_packet_end = NULL;
65 /* No flags */
66 str->pkt_flags = 0;
67 /* Fin */
68 str->state = SSTATE_END;
69}
70
71/* Return a timestamp at address p+offset if the marker bits are in tact */
72static inline uint32_t read_pts(uint8_t *p, off_t offset)
73{
74 return TS_CHECK_MARKERS(p, offset) ?
75 TS_FROM_HEADER(p, offset) : INVALID_TIMESTAMP;
76}
77
78static inline bool validate_timestamp(uint32_t ts)
79{
80 return ts >= str_parser.start_pts && ts <= str_parser.end_pts;
81}
82
83/* Find a start code before or after a given position */
84uint8_t * mpeg_parser_scan_start_code(struct stream_scan *sk, uint32_t code)
85{
86 stream_scan_normalize(sk);
87
88 if (sk->dir < 0)
89 {
90 /* Reverse scan - start with at least the min needed */
91 stream_scan_offset(sk, 4);
92 }
93
94 code &= 0xff; /* Only the low byte matters */
95
96 while (sk->len >= 0 && sk->margin >= 4)
97 {
98 uint8_t *p;
99 off_t pos = disk_buf_lseek(sk->pos, SEEK_SET);
100 ssize_t len = disk_buf_getbuffer(4, &p, NULL, NULL);
101
102 if (pos < 0 || len < 4)
103 break;
104
105 if (CMP_3_CONST(p, PACKET_START_CODE_PREFIX) && p[3] == code)
106 {
107 return p;
108 }
109
110 stream_scan_offset(sk, 1);
111 }
112
113 return NULL;
114}
115
116/* Find a PES packet header for any stream - return stream to which it
117 * belongs */
118unsigned mpeg_parser_scan_pes(struct stream_scan *sk)
119{
120 stream_scan_normalize(sk);
121
122 if (sk->dir < 0)
123 {
124 /* Reverse scan - start with at least the min needed */
125 stream_scan_offset(sk, 4);
126 }
127
128 while (sk->len >= 0 && sk->margin >= 4)
129 {
130 uint8_t *p;
131 off_t pos = disk_buf_lseek(sk->pos, SEEK_SET);
132 ssize_t len = disk_buf_getbuffer(4, &p, NULL, NULL);
133
134 if (pos < 0 || len < 4)
135 break;
136
137 if (CMP_3_CONST(p, PACKET_START_CODE_PREFIX))
138 {
139 unsigned id = p[3];
140 if (id >= 0xb9)
141 return id; /* PES header */
142 /* else some video stream element */
143 }
144
145 stream_scan_offset(sk, 1);
146 }
147
148 return -1;
149}
150
151/* Return the first SCR found from the scan direction */
152uint32_t mpeg_parser_scan_scr(struct stream_scan *sk)
153{
154 uint8_t *p = mpeg_parser_scan_start_code(sk, MPEG_STREAM_PACK_HEADER);
155
156 if (p != NULL && sk->margin >= 9) /* 9 bytes total required */
157 {
158 sk->data = 9;
159
160 if ((p[4] & 0xc0) == 0x40) /* mpeg-2 */
161 {
162 /* Lookhead p+8 */
163 if (MPEG2_CHECK_PACK_SCR_MARKERS(p, 4))
164 return MPEG2_PACK_HEADER_SCR(p, 4);
165 }
166 else if ((p[4] & 0xf0) == 0x20) /* mpeg-1 */
167 {
168 /* Lookahead p+8 */
169 if (TS_CHECK_MARKERS(p, 4))
170 return TS_FROM_HEADER(p, 4);
171 }
172 /* Weird pack header */
173 sk->data = 5;
174 }
175
176 return INVALID_TIMESTAMP;
177}
178
179uint32_t mpeg_parser_scan_pts(struct stream_scan *sk, unsigned id)
180{
181 stream_scan_normalize(sk);
182
183 if (sk->dir < 0)
184 {
185 /* Reverse scan - start with at least the min needed */
186 stream_scan_offset(sk, 4);
187 }
188
189 while (sk->len >= 0 && sk->margin >= 4)
190 {
191 uint8_t *p;
192 off_t pos = disk_buf_lseek(sk->pos, SEEK_SET);
193 ssize_t len = disk_buf_getbuffer(35, &p, NULL, NULL);
194
195 if (pos < 0 || len < 4)
196 break;
197
198 if (CMP_3_CONST(p, PACKET_START_CODE_PREFIX) && p[3] == id)
199 {
200 uint8_t *h = p;
201
202 if (sk->margin < 6)
203 {
204 /* Insufficient data */
205 }
206 else if ((h[6] & 0xc0) == 0x80) /* mpeg2 */
207 {
208 if (sk->margin >= 14 && (h[7] & 0x80) != 0x00)
209 {
210 sk->data = 14;
211 return read_pts(h, 9);
212 }
213 }
214 else /* mpeg1 */
215 {
216 ssize_t l = 7;
217 ssize_t margin = sk->margin;
218
219 /* Skip stuffing_byte */
220 while (h[l - 1] == 0xff && ++l <= 23)
221 --margin;
222
223 if ((h[l - 1] & 0xc0) == 0x40)
224 {
225 /* Skip STD_buffer_scale and STD_buffer_size */
226 margin -= 2;
227 l += 2;
228 }
229
230 if (margin >= 4)
231 {
232 /* header points to the mpeg1 pes header */
233 h += l;
234
235 if ((h[-1] & 0xe0) == 0x20)
236 {
237 sk->data = (h + 4) - p;
238 return read_pts(h, -1);
239 }
240 }
241 }
242 /* No PTS present - keep searching for a matching PES header with
243 * one */
244 }
245
246 stream_scan_offset(sk, 1);
247 }
248
249 return INVALID_TIMESTAMP;
250}
251
252static bool init_video_info(void)
253{
254 DEBUGF("Getting movie size\n");
255
256 /* The decoder handles this in order to initialize its knowledge of the
257 * movie parameters making seeking easier */
258 str_send_msg(&video_str, STREAM_RESET, 0);
259 if (str_send_msg(&video_str, VIDEO_GET_SIZE,
260 (intptr_t)&str_parser.dims) != 0)
261 {
262 return true;
263 }
264
265 DEBUGF(" failed\n");
266 return false;
267}
268
269static void init_times(struct stream *str)
270{
271 int i;
272 struct stream tmp_str;
273 const ssize_t filesize = disk_buf_filesize();
274 const ssize_t max_probe = MIN(512*1024, filesize);
275
276 /* Simply find the first earliest timestamp - this will be the one
277 * used when streaming anyway */
278 DEBUGF("Finding start_pts: 0x%02x\n", str->id);
279
280 tmp_str.id = str->id;
281 tmp_str.hdr.pos = 0;
282 tmp_str.hdr.limit = max_probe;
283
284 str->start_pts = INVALID_TIMESTAMP;
285
286 /* Probe many for video because of B-frames */
287 for (i = STREAM_IS_VIDEO(str->id) ? 5 : 1; i > 0;)
288 {
289 switch (parser_get_next_data(&tmp_str, STREAM_PM_RANDOM_ACCESS))
290 {
291 case STREAM_DATA_END:
292 break;
293 case STREAM_OK:
294 if (tmp_str.pkt_flags & PKT_HAS_TS)
295 {
296 if (tmp_str.pts < str->start_pts)
297 str->start_pts = tmp_str.pts;
298 i--; /* Decrement timestamp counter */
299 }
300 continue;
301 }
302
303 break;
304 }
305
306 DEBUGF(" start:%u\n", (unsigned)str->start_pts);
307
308 /* Use the decoder thread to perform a synchronized search - no
309 * decoding should take place but just a simple run through timestamps
310 * and durations as the decoder would see them. This should give the
311 * precise time at the end of the last frame for the stream. */
312 DEBUGF("Finding end_pts: 0x%02x\n", str->id);
313
314 str->end_pts = INVALID_TIMESTAMP;
315
316 if (str->start_pts != INVALID_TIMESTAMP)
317 {
318 str_parser.parms.sd.time = MAX_TIMESTAMP;
319 str_parser.parms.sd.sk.pos = filesize - max_probe;
320 str_parser.parms.sd.sk.len = max_probe;
321 str_parser.parms.sd.sk.dir = SSCAN_FORWARD;
322
323 str_send_msg(str, STREAM_RESET, 0);
324
325 if (str_send_msg(str, STREAM_FIND_END_TIME,
326 (intptr_t)&str_parser.parms.sd) == STREAM_PERFECT_MATCH)
327 {
328 str->end_pts = str_parser.parms.sd.time;
329 DEBUGF(" end:%u\n", (unsigned)str->end_pts);
330 }
331 }
332
333 /* End must be greater than start */
334 if (str->start_pts >= str->end_pts)
335 {
336 str->start_pts = INVALID_TIMESTAMP;
337 str->end_pts = INVALID_TIMESTAMP;
338 }
339}
340
341/* Return the best-fit file offset of a timestamp in the PES where
342 * timstamp <= time < next timestamp. Will try to return something reasonably
343 * valid if best-fit could not be made. */
344static off_t mpeg_parser_seek_PTS(uint32_t time, unsigned id)
345{
346 ssize_t pos_left = 0;
347 ssize_t pos_right = disk_buf.filesize;
348 ssize_t pos, pos_new;
349 uint32_t time_left = str_parser.start_pts;
350 uint32_t time_right = str_parser.end_pts;
351 uint32_t pts = 0;
352 uint32_t prevpts = 0;
353 enum state_enum state = state0;
354 struct stream_scan sk;
355
356 /* Initial estimate taken from average bitrate - later interpolations are
357 * taken similarly based on the remaining file interval */
358 pos_new = muldiv_uint32(time - time_left, pos_right - pos_left,
359 time_right - time_left) + pos_left;
360
361 /* return this estimated position if nothing better comes up */
362 pos = pos_new;
363
364 DEBUGF("Seeking stream 0x%02x\n", id);
365 DEBUGF("$$ tl:%u t:%u ct:?? tr:%u\n pl:%ld pn:%ld pr:%ld\n",
366 (unsigned)time_left, (unsigned)time, (unsigned)time_right,
367 pos_left, pos_new, pos_right);
368
369 sk.dir = SSCAN_REVERSE;
370
371 while (state < state9)
372 {
373 uint32_t currpts;
374 sk.pos = pos_new;
375 sk.len = (sk.dir < 0) ? pos_new - pos_left : pos_right - pos_new;
376
377 currpts = mpeg_parser_scan_pts(&sk, id);
378
379 if (currpts != INVALID_TIMESTAMP)
380 {
381 /* Found a valid timestamp - see were it lies in relation to
382 * target */
383 if (currpts < time)
384 {
385 /* Time at current position is before seek time - move
386 * forward */
387 if (currpts > pts)
388 {
389 /* This is less than the desired time but greater than
390 * the currently seeked one; move the position up */
391 pts = currpts;
392 pos = sk.pos;
393 }
394
395 /* No next timestamp can be sooner */
396 pos_left = sk.pos + sk.data;
397 time_left = currpts;
398
399 if (pos_right <= pos_left)
400 break; /* If the window disappeared - we're done */
401
402 pos_new = muldiv_uint32(time - time_left,
403 pos_right - pos_left,
404 time_right - time_left) + pos_left;
405 /* Point is ahead of us - fudge estimate a bit high */
406 pos_new = muldiv_uint32(11, pos_new - pos_left, 10)
407 + pos_left;
408
409 if (pos_new >= pos_right)
410 {
411 /* Estimate could push too far */
412 pos_new = pos_right;
413 }
414
415 state = state2; /* Last scan was early */
416 sk.dir = SSCAN_REVERSE;
417
418 DEBUGF(">> tl:%u t:%u ct:%u tr:%u\n pl:%ld pn:%ld pr:%ld\n",
419 (unsigned)time_left, (unsigned)time, (unsigned)currpts,
420 (unsigned)time_right, pos_left, pos_new, pos_right);
421 }
422 else if (currpts > time)
423 {
424 /* Time at current position is past seek time - move
425 backward */
426 pos_right = sk.pos;
427 time_right = currpts;
428
429 if (pos_right <= pos_left)
430 break; /* If the window disappeared - we're done */
431
432 pos_new = muldiv_uint32(time - time_left,
433 pos_right - pos_left,
434 time_right - time_left) + pos_left;
435 /* Overshot the seek point - fudge estimate a bit low */
436 pos_new = muldiv_uint32(9, pos_new - pos_left, 10) + pos_left;
437
438 state = state3; /* Last scan was late */
439 sk.dir = SSCAN_REVERSE;
440
441 DEBUGF("<< tl:%u t:%u ct:%u tr:%u\n pl:%ld pn:%ld pr:%ld\n",
442 (unsigned)time_left, (unsigned)time, (unsigned)currpts,
443 (unsigned)time_right, pos_left, pos_new, pos_right);
444 }
445 else
446 {
447 /* Exact match - it happens */
448 DEBUGF("|| tl:%u t:%u ct:%u tr:%u\n pl:%ld pn:%ld pr:%ld\n",
449 (unsigned)time_left, (unsigned)time, (unsigned)currpts,
450 (unsigned)time_right, pos_left, pos_new, pos_right);
451 pts = currpts;
452 pos = sk.pos;
453 state = state9;
454 }
455 }
456 else
457 {
458 /* Nothing found */
459
460 switch (state)
461 {
462 case state1:
463 /* We already tried the bruteforce scan and failed again - no
464 * more stamps could possibly exist in the interval */
465 DEBUGF("!! no timestamp 2x\n");
466 break;
467 case state0:
468 /* Hardly likely except at very beginning - just do L->R scan
469 * to find something */
470 DEBUGF("!! no timestamp on first probe: %ld\n", sk.pos);
471 case state2:
472 case state3:
473 /* Could just be missing timestamps because the interval is
474 * narrowing down. A large block of data from another stream
475 * may also be in the midst of our chosen points which could
476 * cluster at either extreme end. If anything is there, this
477 * will find it. */
478 pos_new = pos_left;
479 sk.dir = SSCAN_FORWARD;
480 DEBUGF("?? tl:%u t:%u ct:%u tr:%u\n pl:%ld pn:%ld pr:%ld\n",
481 (unsigned)time_left, (unsigned)time, (unsigned)currpts,
482 (unsigned)time_right, pos_left, pos_new, pos_right);
483 state = state1;
484 break;
485 default:
486 DEBUGF("?? Invalid state: %d\n", state);
487 }
488 }
489
490 /* Same timestamp twice = quit */
491 if (currpts == prevpts)
492 {
493 DEBUGF("!! currpts == prevpts (stop)\n");
494 state = state9;
495 }
496
497 prevpts = currpts;
498 }
499
500#if defined(DEBUG) || defined(SIMULATOR)
501 /* The next pts after the seeked-to position should be greater -
502 * most of the time - frames out of presentation order may muck it
503 * up a slight bit */
504 sk.pos = pos + 1;
505 sk.len = disk_buf.filesize;
506 sk.dir = SSCAN_FORWARD;
507
508 uint32_t nextpts = mpeg_parser_scan_pts(&sk, id);
509 DEBUGF("Seek pos:%ld pts:%u t:%u next pts:%u \n",
510 pos, (unsigned)pts, (unsigned)time, (unsigned)nextpts);
511
512 if (pts <= time && time < nextpts)
513 {
514 /* Smile - it worked */
515 DEBUGF(" :) pts<=time<next pts\n");
516 }
517 else
518 {
519 /* See where things ended up */
520 if (pts > time)
521 {
522 /* Hmm */
523 DEBUGF(" :\\ pts>time\n");
524 }
525 if (pts >= nextpts)
526 {
527 /* Weird - probably because of encoded order & tends to be right
528 * anyway if other criteria are met */
529 DEBUGF(" :p pts>=next pts\n");
530 }
531 if (time >= nextpts)
532 {
533 /* Ugh */
534 DEBUGF(" :( time>=nextpts\n");
535 }
536 }
537#endif
538
539 return pos;
540}
541
542static bool prepare_image(uint32_t time)
543{
544 struct stream_scan sk;
545 int tries;
546 int result;
547
548 if (!str_send_msg(&video_str, STREAM_NEEDS_SYNC, time))
549 {
550 DEBUGF("Image was ready\n");
551 return true; /* Should already have the image */
552 }
553
554#ifdef HAVE_ADJUSTABLE_CPU_FREQ
555 rb->cpu_boost(true); /* No interference with trigger_cpu_boost */
556#endif
557
558 str_send_msg(&video_str, STREAM_RESET, 0);
559
560 sk.pos = parser_can_seek() ?
561 mpeg_parser_seek_PTS(time, video_str.id) : 0;
562 sk.len = sk.pos;
563 sk.dir = SSCAN_REVERSE;
564
565 tries = 1;
566try_again:
567
568 if (mpeg_parser_scan_start_code(&sk, MPEG_START_GOP))
569 {
570 DEBUGF("GOP found at: %ld\n", sk.pos);
571
572 unsigned id = mpeg_parser_scan_pes(&sk);
573
574 if (id != video_str.id && sk.pos > 0)
575 {
576 /* Not part of our stream */
577 DEBUGF(" wrong stream: 0x%02x\n", id);
578 goto try_again;
579 }
580
581 /* This will hit the PES header since it's known to be there */
582 uint32_t pts = mpeg_parser_scan_pts(&sk, id);
583
584 if (pts == INVALID_TIMESTAMP || pts > time)
585 {
586 DEBUGF(" wrong timestamp: %u\n", (unsigned)pts);
587 goto try_again;
588 }
589 }
590
591 str_parser.parms.sd.time = time;
592 str_parser.parms.sd.sk.pos = MAX(sk.pos, 0);
593 str_parser.parms.sd.sk.len = 1024*1024;
594 str_parser.parms.sd.sk.dir = SSCAN_FORWARD;
595
596 DEBUGF("thumb pos:%ld len:%ld\n", str_parser.parms.sd.sk.pos,
597 str_parser.parms.sd.sk.len);
598
599 result = str_send_msg(&video_str, STREAM_SYNC,
600 (intptr_t)&str_parser.parms.sd);
601
602 if (result != STREAM_PERFECT_MATCH)
603 {
604 /* Two tries should be all that is nescessary to find the exact frame
605 * if the first GOP actually started later than the timestamp - the
606 * GOP just prior must then start on or earlier. */
607 if (++tries <= 2)
608 goto try_again;
609 }
610
611#ifdef HAVE_ADJUSTABLE_CPU_FREQ
612 rb->cpu_boost(false);
613#endif
614
615 return result > STREAM_OK;
616}
617
618static void prepare_audio(uint32_t time)
619{
620 off_t pos;
621
622 if (!str_send_msg(&audio_str, STREAM_NEEDS_SYNC, time))
623 {
624 DEBUGF("Audio was ready\n");
625 return;
626 }
627
628 pos = mpeg_parser_seek_PTS(time, audio_str.id);
629 str_send_msg(&audio_str, STREAM_RESET, 0);
630
631 str_parser.parms.sd.time = time;
632 str_parser.parms.sd.sk.pos = pos;
633 str_parser.parms.sd.sk.len = 1024*1024;
634 str_parser.parms.sd.sk.dir = SSCAN_FORWARD;
635
636 str_send_msg(&audio_str, STREAM_SYNC, (intptr_t)&str_parser.parms.sd);
637}
638
639/* This function demuxes the streams and gives the next stream data
640 * pointer.
641 *
642 * STREAM_PM_STREAMING is for operation during playback. If the nescessary
643 * data and worst-case lookahead margin is not available, the stream is
644 * registered for notification when the data becomes available. If parsing
645 * extends beyond the end of the file or the end of stream marker is reached,
646 * STREAM_DATA_END is returned and the stream state changed to SSTATE_EOS.
647 *
648 * STREAM_PM_RANDOM_ACCESS is for operation when not playing such as seeking.
649 * If the file cache misses for the current position + lookahead, it will be
650 * loaded from disk. When the specified limit is reached, STREAM_DATA_END is
651 * returned.
652 *
653 * The results from one mode may be used as input to the other. Random access
654 * requires cooperation amongst threads to avoid evicting another stream's
655 * data.
656 */
657static int parse_demux(struct stream *str, enum stream_parse_mode type)
658{
659 #define INC_BUF(offset) \
660 ({ off_t _o = (offset); \
661 str->hdr.win_right += _o; \
662 if ((p += _o) >= disk_buf.end) \
663 p -= disk_buf.size; })
664
665 static const int mpeg1_skip_table[16] =
666 { 0, 0, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
667
668 uint8_t *p = str->curr_packet_end;
669
670 str->pkt_flags = 0;
671
672 while (1)
673 {
674 uint8_t *header;
675 unsigned id;
676 ssize_t length, bytes;
677
678 switch (type)
679 {
680 case STREAM_PM_STREAMING:
681 /* Has the end been reached already? */
682 if (str->state == SSTATE_END)
683 return STREAM_DATA_END;
684
685 /* Are we at the end of file? */
686 if (str->hdr.win_left >= disk_buf.filesize)
687 {
688 str_end_of_stream(str);
689 return STREAM_DATA_END;
690 }
691
692 if (!disk_buf_is_data_ready(&str->hdr, MIN_BUFAHEAD))
693 {
694 /* This data range is not buffered yet - register stream to
695 * be notified when it becomes available. Stream is obliged
696 * to enter a TSTATE_DATA state if it must wait. */
697 int res = str_next_data_not_ready(str);
698
699 if (res != STREAM_OK)
700 return res;
701 }
702 break;
703 /* STREAM_PM_STREAMING: */
704
705 case STREAM_PM_RANDOM_ACCESS:
706 str->hdr.pos = disk_buf_lseek(str->hdr.pos, SEEK_SET);
707
708 if (str->hdr.pos < 0 || str->hdr.pos >= str->hdr.limit ||
709 disk_buf_getbuffer(MIN_BUFAHEAD, &p, NULL, NULL) <= 0)
710 {
711 str_end_of_stream(str);
712 return STREAM_DATA_END;
713 }
714
715 str->state = SSTATE_SYNC;
716 str->hdr.win_left = str->hdr.pos;
717 str->curr_packet = NULL;
718 str->curr_packet_end = p;
719 break;
720 /* STREAM_PM_RANDOM_ACCESS: */
721 }
722
723 if (str->state == SSTATE_SYNC)
724 {
725 /* Scanning for start code */
726 if (!CMP_3_CONST(p, PACKET_START_CODE_PREFIX))
727 {
728 INC_BUF(1);
729 continue;
730 }
731 }
732
733 /* Found a start code - enter parse state */
734 str->state = SSTATE_PARSE;
735
736 /* Pack header, skip it */
737 if (CMP_4_CONST(p, PACK_START_CODE))
738 {
739 /* Max lookahead: 14 */
740 if ((p[4] & 0xc0) == 0x40) /* mpeg-2 */
741 {
742 /* Max delta: 14 + 7 = 21 */
743 /* Skip pack header and any stuffing bytes*/
744 bytes = 14 + (p[13] & 7);
745 }
746 else if ((p[4] & 0xf0) == 0x20) /* mpeg-1 */
747 {
748 bytes = 12;
749 }
750 else /* unknown - skip it */
751 {
752 DEBUGF("weird pack header!\n");
753 bytes = 5;
754 }
755
756 INC_BUF(bytes);
757 }
758
759 /* System header, parse and skip it - 6 bytes + size */
760 if (CMP_4_CONST(p, SYSTEM_HEADER_START_CODE))
761 {
762 /* Skip start code */
763 /* Max Delta = 65535 + 6 = 65541 */
764 bytes = 6 + ((p[4] << 8) | p[5]);
765 INC_BUF(bytes);
766 }
767
768 /* Packet header, parse it */
769 if (!CMP_3_CONST(p, PACKET_START_CODE_PREFIX))
770 {
771 /* Problem? Meh...probably not but just a corrupted section.
772 * Try to resync the parser which will probably succeed. */
773 DEBUGF("packet start code prefix not found: 0x%02x\n"
774 " wl:%lu wr:%lu\n"
775 " p:%p cp:%p cpe:%p\n"
776 " dbs:%p dbe:%p dbt:%p\n",
777 str->id, str->hdr.win_left, str->hdr.win_right,
778 p, str->curr_packet, str->curr_packet_end,
779 disk_buf.start, disk_buf.end, disk_buf.tail);
780 str->state = SSTATE_SYNC;
781 INC_BUF(1); /* Next byte - this one's no good */
782 continue;
783 }
784
785 /* We retrieve basic infos */
786 /* Maximum packet length: 6 + 65535 = 65541 */
787 id = p[3];
788 length = ((p[4] << 8) | p[5]) + 6;
789
790 if (id != str->id)
791 {
792 switch (id)
793 {
794 case MPEG_STREAM_PROGRAM_END:
795 /* end of stream */
796 str_end_of_stream(str);
797 DEBUGF("MPEG program end: 0x%02x\n", str->id);
798 return STREAM_DATA_END;
799 case MPEG_STREAM_PACK_HEADER:
800 case MPEG_STREAM_SYSTEM_HEADER:
801 /* These shouldn't be here - no increment or resync
802 * since we'll pick it up above. */
803 continue;
804 default:
805 /* It's not the packet we're looking for, skip it */
806 INC_BUF(length);
807 continue;
808 }
809 }
810
811 /* Ok, it's our packet */
812 header = p;
813
814 if ((header[6] & 0xc0) == 0x80) /* mpeg2 */
815 {
816 /* Max Lookahead: 18 */
817 /* Min length: 9 */
818 /* Max length: 9 + 255 = 264 */
819 length = 9 + header[8];
820
821 /* header points to the mpeg2 pes header */
822 if ((header[7] & 0x80) != 0)
823 {
824 /* header has a pts */
825 uint32_t pts = read_pts(header, 9);
826
827 if (pts != INVALID_TIMESTAMP)
828 {
829 str->pts = pts;
830#if 0
831 /* DTS isn't used for anything since things just get
832 decoded ASAP but keep the code around */
833 if (STREAM_IS_VIDEO(id))
834 {
835 /* Video stream - header may have a dts as well */
836 str->dts = pts;
837
838 if (header[7] & 0x40) != 0x00)
839 {
840 pts = read_pts(header, 14);
841 if (pts != INVALID_TIMESTAMP)
842 str->dts = pts;
843 }
844 }
845#endif
846 str->pkt_flags |= PKT_HAS_TS;
847 }
848 }
849 }
850 else /* mpeg1 */
851 {
852 /* Max lookahead: 24 + 2 + 9 = 35 */
853 /* Max len_skip: 24 + 2 = 26 */
854 /* Min length: 7 */
855 /* Max length: 24 + 2 + 9 = 35 */
856 off_t len_skip;
857 uint8_t * ptsbuf;
858
859 length = 7;
860
861 while (header[length - 1] == 0xff)
862 {
863 if (++length > 23)
864 {
865 DEBUGF("Too much stuffing" );
866 break;
867 }
868 }
869
870 if ((header[length - 1] & 0xc0) == 0x40)
871 length += 2;
872
873 len_skip = length;
874 length += mpeg1_skip_table[header[length - 1] >> 4];
875
876 /* Header points to the mpeg1 pes header */
877 ptsbuf = header + len_skip;
878
879 if ((ptsbuf[-1] & 0xe0) == 0x20 && TS_CHECK_MARKERS(ptsbuf, -1))
880 {
881 /* header has a pts */
882 uint32_t pts = read_pts(ptsbuf, -1);
883
884 if (pts != INVALID_TIMESTAMP)
885 {
886 str->pts = pts;
887#if 0
888 /* DTS isn't used for anything since things just get
889 decoded ASAP but keep the code around */
890 if (STREAM_IS_VIDEO(id))
891 {
892 /* Video stream - header may have a dts as well */
893 str->dts = pts;
894
895 if (ptsbuf[-1] & 0xf0) == 0x30)
896 {
897 pts = read_pts(ptsbuf, 4);
898
899 if (pts != INVALID_TIMESTAMP)
900 str->dts = pts;
901 }
902 }
903#endif
904 str->pkt_flags |= PKT_HAS_TS;
905 }
906 }
907 }
908
909 p += length;
910 /* Max bytes: 6 + 65535 - 7 = 65534 */
911 bytes = 6 + (header[4] << 8) + header[5] - length;
912
913 str->curr_packet = p;
914 str->curr_packet_end = p + bytes;
915 str->hdr.win_left = str->hdr.win_right + length;
916 str->hdr.win_right = str->hdr.win_left + bytes;
917
918 if (str->hdr.win_right > disk_buf.filesize)
919 {
920 /* No packet that exceeds end of file can be valid */
921 str_end_of_stream(str);
922 return STREAM_DATA_END;
923 }
924
925 return STREAM_OK;
926 } /* end while */
927
928 #undef INC_BUF
929}
930
931/* This simply reads data from the file one page at a time and returns a
932 * pointer to it in the buffer. */
933static int parse_elementary(struct stream *str, enum stream_parse_mode type)
934{
935 uint8_t *p;
936 ssize_t len = 0;
937
938 str->pkt_flags = 0;
939
940 switch (type)
941 {
942 case STREAM_PM_STREAMING:
943 /* Has the end been reached already? */
944 if (str->state == SSTATE_END)
945 return STREAM_DATA_END;
946
947 /* Are we at the end of file? */
948 if (str->hdr.win_left >= disk_buf.filesize)
949 {
950 str_end_of_stream(str);
951 return STREAM_DATA_END;
952 }
953
954 if (!disk_buf_is_data_ready(&str->hdr, MIN_BUFAHEAD))
955 {
956 /* This data range is not buffered yet - register stream to
957 * be notified when it becomes available. Stream is obliged
958 * to enter a TSTATE_DATA state if it must wait. */
959 int res = str_next_data_not_ready(str);
960
961 if (res != STREAM_OK)
962 return res;
963 }
964
965 len = DISK_BUF_PAGE_SIZE;
966
967 if ((size_t)(str->hdr.win_right + len) > (size_t)disk_buf.filesize)
968 len = disk_buf.filesize - str->hdr.win_right;
969
970 if (len <= 0)
971 {
972 str_end_of_stream(str);
973 return STREAM_DATA_END;
974 }
975
976 p = str->curr_packet_end;
977 break;
978 /* STREAM_PM_STREAMING: */
979
980 case STREAM_PM_RANDOM_ACCESS:
981 str->hdr.pos = disk_buf_lseek(str->hdr.pos, SEEK_SET);
982 len = disk_buf_getbuffer(DISK_BUF_PAGE_SIZE, &p, NULL, NULL);
983
984 if (len <= 0 || str->hdr.pos < 0 || str->hdr.pos >= str->hdr.limit)
985 {
986 str_end_of_stream(str);
987 return STREAM_DATA_END;
988 }
989 break;
990 /* STREAM_PM_RANDOM_ACCESS: */
991 }
992
993 str->state = SSTATE_PARSE;
994 str->curr_packet = p;
995 str->curr_packet_end = p + len;
996 str->hdr.win_left = str->hdr.win_right;
997 str->hdr.win_right = str->hdr.win_left + len;
998
999 return STREAM_OK;
1000}
1001
1002intptr_t parser_send_video_msg(long id, intptr_t data)
1003{
1004 intptr_t retval = 0;
1005
1006 if (video_str.thread != NULL && disk_buf.in_file >= 0)
1007 {
1008 /* Hook certain messages since they involve multiple operations
1009 * behind the scenes */
1010 switch (id)
1011 {
1012 case VIDEO_DISPLAY_SHOW:
1013 if (data != 0 && stream_status() != STREAM_PLAYING)
1014 { /* Only prepare image if showing and not playing */
1015 prepare_image(str_parser.last_seek_time);
1016 }
1017 break;
1018
1019 case VIDEO_PRINT_FRAME:
1020 case VIDEO_PRINT_THUMBNAIL:
1021 if (stream_status() == STREAM_PLAYING)
1022 break; /* Prepare image if not playing */
1023
1024 if (!prepare_image(str_parser.last_seek_time))
1025 return false; /* Preparation failed */
1026
1027 /* Image ready - pass message to video thread */
1028 break;
1029 }
1030
1031 retval = str_send_msg(&video_str, id, data);
1032 }
1033
1034 return retval;
1035}
1036
1037/* Seek parser to the specified time and return absolute time.
1038 * No actual hard stuff is performed here. That's done when streaming is
1039 * about to begin or something from the current position is requested */
1040uint32_t parser_seek_time(uint32_t time)
1041{
1042 if (!parser_can_seek())
1043 time = 0;
1044 else if (time > str_parser.duration)
1045 time = str_parser.duration;
1046
1047 str_parser.last_seek_time = time + str_parser.start_pts;
1048 return str_parser.last_seek_time;
1049}
1050
1051void parser_prepare_streaming(void)
1052{
1053 struct stream_window sw;
1054
1055 DEBUGF("parser_prepare_streaming\n");
1056
1057 /* Prepare initial video frame */
1058 prepare_image(str_parser.last_seek_time);
1059
1060 /* Sync audio stream */
1061 if (audio_str.start_pts != INVALID_TIMESTAMP)
1062 prepare_audio(str_parser.last_seek_time);
1063
1064 /* Prequeue some data and set buffer window */
1065 if (!stream_get_window(&sw))
1066 sw.left = sw.right = disk_buf.filesize;
1067
1068 DEBUGF(" swl:%ld swr:%ld\n", sw.left, sw.right);
1069
1070 if (sw.right > disk_buf.filesize - 4*MIN_BUFAHEAD)
1071 sw.right = disk_buf.filesize - 4*MIN_BUFAHEAD;
1072
1073 disk_buf_prepare_streaming(sw.left,
1074 sw.right - sw.left + 4*MIN_BUFAHEAD);
1075}
1076
1077int parser_init_stream(void)
1078{
1079 if (disk_buf.in_file < 0)
1080 return STREAM_ERROR;
1081
1082 /* TODO: Actually find which streams are available */
1083 audio_str.id = MPEG_STREAM_AUDIO_FIRST;
1084 video_str.id = MPEG_STREAM_VIDEO_FIRST;
1085
1086 /* Try to pull a video PES - if not found, try video init anyway which
1087 * should succeed if it really is a video-only stream */
1088 video_str.hdr.pos = 0;
1089 video_str.hdr.limit = 256*1024;
1090
1091 if (parse_demux(&video_str, STREAM_PM_RANDOM_ACCESS) == STREAM_OK)
1092 {
1093 /* Found a video packet - assume transport stream */
1094 str_parser.format = STREAM_FMT_MPEG_TS;
1095 str_parser.next_data = parse_demux;
1096 }
1097 else
1098 {
1099 /* No PES element found - assume video elementary stream */
1100 str_parser.format = STREAM_FMT_MPV;
1101 str_parser.next_data = parse_elementary;
1102 }
1103
1104 if (!init_video_info())
1105 {
1106 /* Cannot determine video size, etc. */
1107 return STREAM_UNSUPPORTED;
1108 }
1109
1110 if (str_parser.format == STREAM_FMT_MPEG_TS)
1111 {
1112 /* Initalize start_pts and end_pts with the length (in 45kHz units) of
1113 * the movie. INVALID_TIMESTAMP if the time could not be determined */
1114 init_times(&audio_str);
1115 init_times(&video_str);
1116
1117 if (video_str.start_pts == INVALID_TIMESTAMP)
1118 {
1119 /* Must have video at least */
1120 return STREAM_UNSUPPORTED;
1121 }
1122
1123 str_parser.flags |= STREAMF_CAN_SEEK;
1124
1125 if (audio_str.start_pts != INVALID_TIMESTAMP)
1126 {
1127 /* Overall duration is maximum span */
1128 str_parser.start_pts = MIN(audio_str.start_pts, video_str.start_pts);
1129 str_parser.end_pts = MAX(audio_str.end_pts, video_str.end_pts);
1130
1131 /* Audio will be part of playback pool */
1132 stream_add_stream(&audio_str);
1133 }
1134 else
1135 {
1136 /* No audio stream - use video only */
1137 str_parser.start_pts = video_str.start_pts;
1138 str_parser.end_pts = video_str.end_pts;
1139 }
1140 }
1141 else
1142 {
1143 /* There's no way to handle times on this without a full file
1144 * scan */
1145 audio_str.start_pts = INVALID_TIMESTAMP;
1146 audio_str.end_pts = INVALID_TIMESTAMP;
1147 video_str.start_pts = 0;
1148 video_str.end_pts = INVALID_TIMESTAMP;
1149 str_parser.start_pts = 0;
1150 str_parser.end_pts = INVALID_TIMESTAMP;
1151 }
1152
1153 /* Add video to playback pool */
1154 stream_add_stream(&video_str);
1155
1156 /* Cache duration - it's used very often */
1157 str_parser.duration = str_parser.end_pts - str_parser.start_pts;
1158
1159 DEBUGF("Movie info:\n"
1160 " size:%dx%d\n"
1161 " start:%u\n"
1162 " end:%u\n"
1163 " duration:%u\n",
1164 str_parser.dims.w, str_parser.dims.h,
1165 (unsigned)str_parser.start_pts,
1166 (unsigned)str_parser.end_pts,
1167 (unsigned)str_parser.duration);
1168
1169 return STREAM_OK;
1170}
1171
1172void parser_close_stream(void)
1173{
1174 stream_remove_streams();
1175 parser_init_state();
1176}
1177
1178bool parser_init(void)
1179{
1180 parser_init_state();
1181 return true;
1182}