mirror of
				https://github.com/Rockbox/rockbox.git
				synced 2025-10-29 00:36:22 -04:00 
			
		
		
		
	git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28969 a1c6a512-1295-4272-9138-f99709370657
		
			
				
	
	
		
			1203 lines
		
	
	
	
		
			36 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1203 lines
		
	
	
	
		
			36 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /***************************************************************************
 | |
|  *             __________               __   ___.
 | |
|  *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 | |
|  *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 | |
|  *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 | |
|  *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 | |
|  *                     \/            \/     \/    \/            \/
 | |
|  * $Id$
 | |
|  *
 | |
|  * Parser for MPEG streams
 | |
|  *
 | |
|  * Copyright (c) 2007 Michael Sevakis
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU General Public License
 | |
|  * as published by the Free Software Foundation; either version 2
 | |
|  * of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 | |
|  * KIND, either express or implied.
 | |
|  *
 | |
|  ****************************************************************************/
 | |
| #include "plugin.h"
 | |
| #include "mpegplayer.h"
 | |
| 
 | |
| struct stream_parser str_parser SHAREDBSS_ATTR;
 | |
| 
 | |
| static void parser_init_state(void)
 | |
| {
 | |
|     str_parser.last_seek_time = 0;
 | |
|     str_parser.format = STREAM_FMT_UNKNOWN;
 | |
|     str_parser.start_pts = INVALID_TIMESTAMP;
 | |
|     str_parser.end_pts = INVALID_TIMESTAMP;
 | |
|     str_parser.flags = 0;
 | |
|     str_parser.dims.w = 0;
 | |
|     str_parser.dims.h = 0;
 | |
| }
 | |
| 
 | |
| /* Place the stream in a state to begin parsing - sync will be performed
 | |
|  * first */
 | |
| void str_initialize(struct stream *str, off_t pos)
 | |
| {
 | |
|     /* Initial positions start here */
 | |
|     str->hdr.win_left = str->hdr.win_right = pos;
 | |
|     /* No packet */
 | |
|     str->curr_packet = NULL;
 | |
|     /* Pick up parsing from this point in the buffer */
 | |
|     str->curr_packet_end = disk_buf_offset2ptr(pos);
 | |
|     /* No flags */
 | |
|     str->pkt_flags = 0;
 | |
|     /* Sync first */
 | |
|     str->state = SSTATE_SYNC;
 | |
| }
 | |
| 
 | |
| /* Place the stream in an end of data state */
 | |
| void str_end_of_stream(struct stream *str)
 | |
| {
 | |
|     /* Offsets that prevent this stream from being included in the
 | |
|      * min left/max right window so that no buffering is triggered on
 | |
|      * its behalf. Set right to the min first so a thread reading the
 | |
|      * overall window gets doesn't see this as valid no matter what the
 | |
|      * file length. */
 | |
|     str->hdr.win_right = OFF_T_MIN;
 | |
|     str->hdr.win_left = OFF_T_MAX;
 | |
|     /* No packets */
 | |
|     str->curr_packet = str->curr_packet_end = NULL;
 | |
|     /* No flags */
 | |
|     str->pkt_flags = 0;
 | |
|     /* Fin */
 | |
|     str->state = SSTATE_END;
 | |
| }
 | |
| 
 | |
| /* Return a timestamp at address p+offset if the marker bits are in tact */
 | |
| static inline uint32_t read_pts(uint8_t *p, off_t offset)
 | |
| {
 | |
|     return TS_CHECK_MARKERS(p, offset) ?
 | |
|         TS_FROM_HEADER(p, offset) : INVALID_TIMESTAMP;
 | |
| }
 | |
| 
 | |
| static inline bool validate_timestamp(uint32_t ts)
 | |
| {
 | |
|     return ts >= str_parser.start_pts && ts <= str_parser.end_pts;
 | |
| }
 | |
| 
 | |
| /* Find a start code before or after a given position */
 | |
| uint8_t * mpeg_parser_scan_start_code(struct stream_scan *sk, uint32_t code)
 | |
| {
 | |
|     stream_scan_normalize(sk);
 | |
| 
 | |
|     if (sk->dir < 0)
 | |
|     {
 | |
|         /* Reverse scan - start with at least the min needed */
 | |
|         stream_scan_offset(sk, 4);
 | |
|     }
 | |
| 
 | |
|     code &= 0xff; /* Only the low byte matters */
 | |
| 
 | |
|     while (sk->len >= 0 && sk->margin >= 4)
 | |
|     {
 | |
|         uint8_t *p;
 | |
|         off_t pos = disk_buf_lseek(sk->pos, SEEK_SET);
 | |
|         ssize_t len = disk_buf_getbuffer_l2(&sk->l2, 4, &p);
 | |
| 
 | |
|         if (pos < 0 || len < 4)
 | |
|             break;
 | |
| 
 | |
|         if (CMP_3_CONST(p, PACKET_START_CODE_PREFIX) && p[3] == code)
 | |
|         {
 | |
|             return p;
 | |
|         }
 | |
| 
 | |
|         stream_scan_offset(sk, 1);
 | |
|     }
 | |
| 
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| /* Find a PES packet header for any stream - return stream to which it
 | |
|  * belongs */
 | |
| unsigned mpeg_parser_scan_pes(struct stream_scan *sk)
 | |
| {
 | |
|     stream_scan_normalize(sk);
 | |
| 
 | |
|     if (sk->dir < 0)
 | |
|     {
 | |
|         /* Reverse scan - start with at least the min needed */
 | |
|         stream_scan_offset(sk, 4);
 | |
|     }
 | |
| 
 | |
|     while (sk->len >= 0 && sk->margin >= 4)
 | |
|     {
 | |
|         uint8_t *p;
 | |
|         off_t pos = disk_buf_lseek(sk->pos, SEEK_SET);
 | |
|         ssize_t len = disk_buf_getbuffer_l2(&sk->l2, 4, &p);
 | |
| 
 | |
|         if (pos < 0 || len < 4)
 | |
|             break;
 | |
| 
 | |
|         if (CMP_3_CONST(p, PACKET_START_CODE_PREFIX))
 | |
|         {
 | |
|             unsigned id = p[3];
 | |
|             if (id >= 0xb9)
 | |
|                 return id;  /* PES header */
 | |
|             /* else some video stream element */
 | |
|         }
 | |
| 
 | |
|         stream_scan_offset(sk, 1);
 | |
|     }
 | |
| 
 | |
|     return -1;
 | |
| }
 | |
| 
 | |
| /* Return the first SCR found from the scan direction */
 | |
| uint32_t mpeg_parser_scan_scr(struct stream_scan *sk)
 | |
| {
 | |
|     uint8_t *p = mpeg_parser_scan_start_code(sk, MPEG_STREAM_PACK_HEADER);
 | |
| 
 | |
|     if (p != NULL && sk->margin >= 9) /* 9 bytes total required */
 | |
|     {
 | |
|         sk->data = 9;
 | |
| 
 | |
|         if ((p[4] & 0xc0) == 0x40)      /* mpeg-2 */
 | |
|         {
 | |
|             /* Lookhead p+8 */
 | |
|             if (MPEG2_CHECK_PACK_SCR_MARKERS(p, 4))
 | |
|                 return MPEG2_PACK_HEADER_SCR(p, 4);
 | |
|         }
 | |
|         else if ((p[4] & 0xf0) == 0x20) /* mpeg-1 */
 | |
|         {
 | |
|             /* Lookahead p+8 */
 | |
|             if (TS_CHECK_MARKERS(p, 4))
 | |
|                 return TS_FROM_HEADER(p, 4);
 | |
|         }
 | |
|         /* Weird pack header */
 | |
|         sk->data = 5;
 | |
|     }
 | |
| 
 | |
|     return INVALID_TIMESTAMP;
 | |
| }
 | |
| 
 | |
| uint32_t mpeg_parser_scan_pts(struct stream_scan *sk, unsigned id)
 | |
| {
 | |
|     stream_scan_normalize(sk);
 | |
| 
 | |
|     if (sk->dir < 0)
 | |
|     {
 | |
|         /* Reverse scan - start with at least the min needed */
 | |
|         stream_scan_offset(sk, 4);
 | |
|     }
 | |
| 
 | |
|     while (sk->len >= 0 && sk->margin >= 4)
 | |
|     {
 | |
|         uint8_t *p;
 | |
|         off_t pos = disk_buf_lseek(sk->pos, SEEK_SET);
 | |
|         ssize_t len = disk_buf_getbuffer_l2(&sk->l2, 30, &p);
 | |
| 
 | |
|         if (pos < 0 || len < 4)
 | |
|             break;
 | |
| 
 | |
|         if (CMP_3_CONST(p, PACKET_START_CODE_PREFIX) && p[3] == id)
 | |
|         {
 | |
|             uint8_t *h = p;
 | |
| 
 | |
|             if (sk->margin < 7)
 | |
|             {
 | |
|                 /* Insufficient data */
 | |
|             }
 | |
|             else if ((h[6] & 0xc0) == 0x80) /* mpeg2 */
 | |
|             {
 | |
|                 if (sk->margin >= 14 && (h[7] & 0x80) != 0x00)
 | |
|                 {
 | |
|                     sk->data = 14;
 | |
|                     return read_pts(h, 9);
 | |
|                 }
 | |
|             }
 | |
|             else                            /* mpeg1 */
 | |
|             {
 | |
|                 ssize_t l = 6;
 | |
|                 ssize_t margin = sk->margin;
 | |
| 
 | |
|                 /* Skip stuffing_byte */
 | |
|                 while (margin > 7 && h[l] == 0xff && ++l <= 22)
 | |
|                     --margin;
 | |
| 
 | |
|                 if (margin >= 7)
 | |
|                 {
 | |
|                     if ((h[l] & 0xc0) == 0x40)
 | |
|                     {
 | |
|                         /* Skip STD_buffer_scale and STD_buffer_size */
 | |
|                         margin -= 2;
 | |
|                         l += 2;
 | |
|                     }
 | |
| 
 | |
|                     if (margin >= 5)
 | |
|                     {
 | |
|                         /* Header points to the mpeg1 pes header */
 | |
|                         h += l;
 | |
| 
 | |
|                         if ((h[0] & 0xe0) == 0x20)
 | |
|                         {
 | |
|                             /* PTS or PTS_DTS indicated */
 | |
|                             sk->data = (h + 5) - p;
 | |
|                             return read_pts(h, 0);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             /* No PTS present - keep searching for a matching PES header with
 | |
|              * one */
 | |
|         }
 | |
| 
 | |
|         stream_scan_offset(sk, 1);
 | |
|     }
 | |
| 
 | |
|     return INVALID_TIMESTAMP;
 | |
| }
 | |
| 
 | |
| static bool init_video_info(void)
 | |
| {
 | |
|     DEBUGF("Getting movie size\n");
 | |
| 
 | |
|     /* The decoder handles this in order to initialize its knowledge of the
 | |
|      * movie parameters making seeking easier */
 | |
|     str_send_msg(&video_str, STREAM_RESET, 0);
 | |
|     if (str_send_msg(&video_str, VIDEO_GET_SIZE,
 | |
|                      (intptr_t)&str_parser.dims) != 0)
 | |
|     {
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     DEBUGF("  failed\n");
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| static bool init_times(struct stream *str)
 | |
| {
 | |
|     struct stream tmp_str;
 | |
|     const ssize_t filesize = disk_buf_filesize();
 | |
|     const ssize_t max_probe = MIN(512*1024, filesize);
 | |
|     bool found_stream;
 | |
| 
 | |
|     /* Simply find the first earliest timestamp - this will be the one
 | |
|      * used when streaming anyway */
 | |
|     DEBUGF("Finding start_pts: 0x%02x\n", str->id);
 | |
| 
 | |
|     found_stream = false;
 | |
|     str->start_pts = INVALID_TIMESTAMP;
 | |
|     str->end_pts = INVALID_TIMESTAMP;
 | |
| 
 | |
|     tmp_str.id = str->id;
 | |
|     tmp_str.hdr.pos = 0;
 | |
|     tmp_str.hdr.limit = max_probe;
 | |
| 
 | |
|     /* Probe for many for the start because some stamps could be anomalous.
 | |
|      * Video also can also have things out of order. Just see what it's got.
 | |
|      */
 | |
|     while (1)
 | |
|     {
 | |
|         switch (parser_get_next_data(&tmp_str, STREAM_PM_RANDOM_ACCESS))
 | |
|         {
 | |
|         case STREAM_DATA_END:
 | |
|             break;
 | |
|         case STREAM_OK:
 | |
|             found_stream = true;
 | |
|             if (tmp_str.pkt_flags & PKT_HAS_TS)
 | |
|             {
 | |
|                 if (tmp_str.pts < str->start_pts)
 | |
|                    str->start_pts = tmp_str.pts;
 | |
|             }
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     if (!found_stream)
 | |
|     {
 | |
|         DEBUGF("   stream not found:0x%02x\n", str->id);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     DEBUGF("  start:%u\n", (unsigned)str->start_pts);
 | |
| 
 | |
|     /* Use the decoder thread to perform a synchronized search - no
 | |
|      * decoding should take place but just a simple run through timestamps
 | |
|      * and durations as the decoder would see them. This should give the
 | |
|      * precise time at the end of the last frame for the stream. */
 | |
|     DEBUGF("Finding end_pts: 0x%02x\n", str->id);
 | |
| 
 | |
|     str_parser.parms.sd.time = MAX_TIMESTAMP;
 | |
|     str_parser.parms.sd.sk.pos = filesize - max_probe;
 | |
|     str_parser.parms.sd.sk.len = max_probe;
 | |
|     str_parser.parms.sd.sk.dir = SSCAN_FORWARD;
 | |
| 
 | |
|     str_send_msg(str, STREAM_RESET, 0);
 | |
| 
 | |
|     if (str_send_msg(str, STREAM_FIND_END_TIME,
 | |
|             (intptr_t)&str_parser.parms.sd) == STREAM_PERFECT_MATCH)
 | |
|     {
 | |
|         str->end_pts = str_parser.parms.sd.time;
 | |
|         DEBUGF("  end:%u\n", (unsigned)str->end_pts);
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| static bool check_times(const struct stream *str)
 | |
| {
 | |
|     return str->start_pts < str->end_pts &&
 | |
|         str->end_pts != INVALID_TIMESTAMP;
 | |
| }
 | |
| 
 | |
| /* Return the best-fit file offset of a timestamp in the PES where
 | |
|  * timstamp <= time < next timestamp. Will try to return something reasonably
 | |
|  * valid if best-fit could not be made. */
 | |
| static off_t mpeg_parser_seek_PTS(uint32_t time, unsigned id)
 | |
| {
 | |
|     ssize_t pos_left = 0;
 | |
|     ssize_t pos_right = disk_buf.filesize;
 | |
|     ssize_t pos, pos_new;
 | |
|     uint32_t time_left = str_parser.start_pts;
 | |
|     uint32_t time_right = str_parser.end_pts;
 | |
|     uint32_t pts = 0;
 | |
|     uint32_t prevpts = 0;
 | |
|     enum state_enum state = STATE0;
 | |
|     struct stream_scan sk;
 | |
| 
 | |
|     stream_scan_init(&sk);
 | |
| 
 | |
|     /* Initial estimate taken from average bitrate - later interpolations are
 | |
|      * taken similarly based on the remaining file interval */
 | |
|     pos_new = muldiv_uint32(time - time_left, pos_right - pos_left,
 | |
|                             time_right - time_left) + pos_left;
 | |
| 
 | |
|     /* return this estimated position if nothing better comes up */
 | |
|     pos = pos_new;
 | |
| 
 | |
|     DEBUGF("Seeking stream 0x%02x\n", id);
 | |
|     DEBUGF("$$ tl:%u t:%u ct:?? tr:%u\n   pl:%ld pn:%ld pr:%ld\n",
 | |
|            (unsigned)time_left, (unsigned)time, (unsigned)time_right,
 | |
|            (long)pos_left, (long)pos_new, (long)pos_right);
 | |
| 
 | |
|     sk.dir = SSCAN_REVERSE;
 | |
| 
 | |
|     while (state < STATE9)
 | |
|     {
 | |
|         uint32_t currpts;
 | |
|         sk.pos = pos_new;
 | |
|         sk.len = (sk.dir < 0) ? pos_new - pos_left : pos_right - pos_new;
 | |
| 
 | |
|         currpts = mpeg_parser_scan_pts(&sk, id);
 | |
| 
 | |
|         if (currpts != INVALID_TIMESTAMP)
 | |
|         {
 | |
|             ssize_t pos_adj; /* Adjustment to over or under-estimate */
 | |
| 
 | |
|             /* Found a valid timestamp - see were it lies in relation to
 | |
|              * target */
 | |
|             if (currpts < time)
 | |
|             {
 | |
|                 /* Time at current position is before seek time - move
 | |
|                  * forward */
 | |
|                 if (currpts > pts)
 | |
|                 {
 | |
|                     /* This is less than the desired time but greater than
 | |
|                      * the currently seeked one; move the position up */
 | |
|                     pts = currpts;
 | |
|                     pos = sk.pos;
 | |
|                 }
 | |
| 
 | |
|                 /* No next timestamp can be sooner */
 | |
|                 pos_left = sk.pos + sk.data;
 | |
|                 time_left = currpts;
 | |
| 
 | |
|                 if (pos_right <= pos_left)
 | |
|                     break; /* If the window disappeared - we're done */
 | |
| 
 | |
|                 pos_new = muldiv_uint32(time - time_left,
 | |
|                                         pos_right - pos_left,
 | |
|                                         time_right - time_left);
 | |
|                 /* Point is ahead of us - fudge estimate a bit high */
 | |
|                 pos_adj = pos_new / 10;
 | |
| 
 | |
|                 if (pos_adj > 512*1024)
 | |
|                     pos_adj = 512*1024;
 | |
| 
 | |
|                 pos_new += pos_left + pos_adj;
 | |
| 
 | |
|                 if (pos_new >= pos_right)
 | |
|                 {
 | |
|                     /* Estimate could push too far */
 | |
|                     pos_new = pos_right;
 | |
|                 }
 | |
| 
 | |
|                 state = STATE2; /* Last scan was early */
 | |
|                 sk.dir = SSCAN_REVERSE;
 | |
|    
 | |
|                 DEBUGF(">> tl:%u t:%u ct:%u tr:%u\n   pl:%ld pn:%ld pr:%ld\n",
 | |
|                        (unsigned)time_left, (unsigned)time, (unsigned)currpts,
 | |
|                        (unsigned)time_right, (long)pos_left, (long)pos_new,
 | |
|                        (long)pos_right);
 | |
|             }
 | |
|             else if (currpts > time)
 | |
|             {
 | |
|                 /* Time at current position is past seek time - move
 | |
|                    backward */
 | |
|                 pos_right = sk.pos;
 | |
|                 time_right = currpts;
 | |
| 
 | |
|                 if (pos_right <= pos_left)
 | |
|                     break; /* If the window disappeared - we're done */
 | |
| 
 | |
|                 pos_new = muldiv_uint32(time - time_left,
 | |
|                                         pos_right - pos_left,
 | |
|                                         time_right - time_left);
 | |
|                 /* Overshot the seek point - fudge estimate a bit low */
 | |
|                 pos_adj = pos_new / 10;
 | |
| 
 | |
|                 if (pos_adj > 512*1024)
 | |
|                     pos_adj = 512*1024;
 | |
| 
 | |
|                 pos_new += pos_left - pos_adj;
 | |
| 
 | |
|                 state = STATE3; /* Last scan was late */
 | |
|                 sk.dir = SSCAN_REVERSE;
 | |
| 
 | |
|                 DEBUGF("<< tl:%u t:%u ct:%u tr:%u\n   pl:%ld pn:%ld pr:%ld\n",
 | |
|                        (unsigned)time_left, (unsigned)time, (unsigned)currpts,
 | |
|                        (unsigned)time_right, (long)pos_left, (long)pos_new,
 | |
|                        (long)pos_right);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 /* Exact match - it happens */
 | |
|                 DEBUGF("|| tl:%u t:%u ct:%u tr:%u\n   pl:%ld pn:%ld pr:%ld\n",
 | |
|                        (unsigned)time_left, (unsigned)time, (unsigned)currpts,
 | |
|                        (unsigned)time_right, (long)pos_left, (long)pos_new,
 | |
|                        (long)pos_right);
 | |
|                 pts = currpts;
 | |
|                 pos = sk.pos;
 | |
|                 state = STATE9;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             /* Nothing found */
 | |
| 
 | |
|             switch (state)
 | |
|             {
 | |
|             case STATE1:
 | |
|                 /* We already tried the bruteforce scan and failed again - no
 | |
|                  * more stamps could possibly exist in the interval */
 | |
|                 DEBUGF("!! no timestamp 2x\n");
 | |
|                 break;
 | |
|             case STATE0:
 | |
|                 /* Hardly likely except at very beginning - just do L->R scan
 | |
|                  * to find something */
 | |
|                 DEBUGF("!! no timestamp on first probe: %ld\n", sk.pos);
 | |
|             case STATE2:
 | |
|             case STATE3:
 | |
|                 /* Could just be missing timestamps because the interval is
 | |
|                  * narrowing down. A large block of data from another stream
 | |
|                  * may also be in the midst of our chosen points which could
 | |
|                  * cluster at either extreme end. If anything is there, this
 | |
|                  * will find it. */
 | |
|                 pos_new = pos_left;
 | |
|                 sk.dir = SSCAN_FORWARD;
 | |
|                 DEBUGF("?? tl:%u t:%u ct:%u tr:%u\n   pl:%ld pn:%ld pr:%ld\n",
 | |
|                        (unsigned)time_left, (unsigned)time, (unsigned)currpts,
 | |
|                        (unsigned)time_right, (long)pos_left, (long)pos_new,
 | |
|                        (long)pos_right);
 | |
|                 state = STATE1;
 | |
|                 break;
 | |
|             default:
 | |
|                 DEBUGF("?? Invalid state: %d\n", state);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /* Same timestamp twice = quit */
 | |
|         if (currpts == prevpts)
 | |
|         {
 | |
|             DEBUGF("!! currpts == prevpts (stop)\n");
 | |
|             state = STATE9;
 | |
|         }
 | |
| 
 | |
|         prevpts = currpts;
 | |
|     }
 | |
| 
 | |
| #if defined(DEBUG) || defined(SIMULATOR)
 | |
|     /* The next pts after the seeked-to position should be greater -
 | |
|      * most of the time - frames out of presentation order may muck it
 | |
|      * up a slight bit */
 | |
|     sk.pos = pos + 1;
 | |
|     sk.len = disk_buf.filesize;
 | |
|     sk.dir = SSCAN_FORWARD;
 | |
| 
 | |
|     uint32_t nextpts = mpeg_parser_scan_pts(&sk, id);
 | |
|     DEBUGF("Seek pos:%ld pts:%u t:%u next pts:%u \n",
 | |
|         (long)pos, (unsigned)pts, (unsigned)time, (unsigned)nextpts);
 | |
| 
 | |
|     if (pts <= time && time < nextpts)
 | |
|     {
 | |
|         /* Smile - it worked */
 | |
|         DEBUGF("  :) pts<=time<next pts\n");
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         /* See where things ended up */
 | |
|         if (pts > time)
 | |
|         {
 | |
|             /* Hmm */
 | |
|             DEBUGF("  :\\ pts>time\n");
 | |
|         }
 | |
|         if (pts >= nextpts)
 | |
|         {
 | |
|             /* Weird - probably because of encoded order & tends to be right
 | |
|              * anyway if other criteria are met */
 | |
|             DEBUGF("  :p pts>=next pts\n");
 | |
|         }
 | |
|         if (time >= nextpts)
 | |
|         {
 | |
|             /* Ugh */
 | |
|             DEBUGF("  :( time>=nextpts\n");
 | |
|         }
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     return pos;
 | |
| }
 | |
| 
 | |
| static void prepare_audio(uint32_t time)
 | |
| {
 | |
|     off_t pos;
 | |
| 
 | |
|     if (!str_send_msg(&audio_str, STREAM_NEEDS_SYNC, time))
 | |
|     {
 | |
|         DEBUGF("Audio was ready\n");
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     pos = mpeg_parser_seek_PTS(time, audio_str.id);
 | |
|     str_send_msg(&audio_str, STREAM_RESET, 0);
 | |
| 
 | |
|     str_parser.parms.sd.time = time;
 | |
|     str_parser.parms.sd.sk.pos = pos;
 | |
|     str_parser.parms.sd.sk.len = 1024*1024;
 | |
|     str_parser.parms.sd.sk.dir = SSCAN_FORWARD;
 | |
| 
 | |
|     str_send_msg(&audio_str, STREAM_SYNC, (intptr_t)&str_parser.parms.sd);
 | |
| }
 | |
| 
 | |
| /* This function demuxes the streams and gives the next stream data
 | |
|  * pointer.
 | |
|  *
 | |
|  * STREAM_PM_STREAMING is for operation during playback. If the nescessary
 | |
|  * data and worst-case lookahead margin is not available, the stream is
 | |
|  * registered for notification when the data becomes available. If parsing
 | |
|  * extends beyond the end of the file or the end of stream marker is reached,
 | |
|  * STREAM_DATA_END is returned and the stream state changed to SSTATE_EOS.
 | |
|  *
 | |
|  * STREAM_PM_RANDOM_ACCESS is for operation when not playing such as seeking.
 | |
|  * If the file cache misses for the current position + lookahead, it will be
 | |
|  * loaded from disk. When the specified limit is reached, STREAM_DATA_END is
 | |
|  * returned.
 | |
|  *
 | |
|  * The results from one mode may be used as input to the other. Random access
 | |
|  * requires cooperation amongst threads to avoid evicting another stream's
 | |
|  * data.
 | |
|  */
 | |
| static int parse_demux(struct stream *str, enum stream_parse_mode type)
 | |
| {
 | |
|     #define INC_BUF(offset) \
 | |
|         ({ off_t _o = (offset);           \
 | |
|            str->hdr.win_right += _o;      \
 | |
|            if ((p += _o) >= disk_buf.end) \
 | |
|                 p -= disk_buf.size; })
 | |
| 
 | |
|     static const int mpeg1_skip_table[16] =
 | |
|         { 0, 0, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
 | |
| 
 | |
|     uint8_t *p = str->curr_packet_end;
 | |
| 
 | |
|     str->pkt_flags = 0;
 | |
| 
 | |
|     while (1)
 | |
|     {
 | |
|         uint8_t *header;
 | |
|         unsigned id;
 | |
|         ssize_t length, bytes;
 | |
| 
 | |
|         switch (type)
 | |
|         {
 | |
|         case STREAM_PM_STREAMING:
 | |
|             /* Has the end been reached already? */
 | |
|             switch (str->state)
 | |
|             {
 | |
|             case SSTATE_PARSE: /* Expected case first if no jumptable */
 | |
|                 /* Are we at the end of file? */
 | |
|                 if (str->hdr.win_left < disk_buf.filesize)
 | |
|                     break;
 | |
|                 str_end_of_stream(str);
 | |
|                 return STREAM_DATA_END;
 | |
| 
 | |
|             case SSTATE_SYNC:
 | |
|                 /* Is sync at the end of file? */
 | |
|                 if (str->hdr.win_right < disk_buf.filesize)
 | |
|                     break;
 | |
|                 str_end_of_stream(str);
 | |
|                 /* Fall-through */
 | |
|             case SSTATE_END:
 | |
|                 return STREAM_DATA_END;
 | |
|             }
 | |
| 
 | |
|             if (!disk_buf_is_data_ready(&str->hdr, MIN_BUFAHEAD))
 | |
|             {
 | |
|                 /* This data range is not buffered yet - register stream to
 | |
|                  * be notified when it becomes available. Stream is obliged
 | |
|                  * to enter a TSTATE_DATA state if it must wait. */
 | |
|                 int res = str_next_data_not_ready(str);
 | |
| 
 | |
|                 if (res != STREAM_OK)
 | |
|                     return res;
 | |
|             }
 | |
|             break;
 | |
|             /* STREAM_PM_STREAMING: */
 | |
| 
 | |
|         case STREAM_PM_RANDOM_ACCESS:
 | |
|             str->hdr.pos = disk_buf_lseek(str->hdr.pos, SEEK_SET);
 | |
| 
 | |
|             if (str->hdr.pos < 0 || str->hdr.pos >= str->hdr.limit ||
 | |
|                 disk_buf_getbuffer(MIN_BUFAHEAD, &p, NULL, NULL) <= 0)
 | |
|             {
 | |
|                 str_end_of_stream(str);
 | |
|                 return STREAM_DATA_END;
 | |
|             }
 | |
| 
 | |
|             str->state = SSTATE_SYNC;
 | |
|             str->hdr.win_left = str->hdr.pos;
 | |
|             str->curr_packet = NULL;
 | |
|             str->curr_packet_end = p;
 | |
|             break;
 | |
|             /* STREAM_PM_RANDOM_ACCESS: */
 | |
|         }
 | |
| 
 | |
|         if (str->state == SSTATE_SYNC)
 | |
|         {
 | |
|             /* Scanning for start code */
 | |
|             if (!CMP_3_CONST(p, PACKET_START_CODE_PREFIX))
 | |
|             {
 | |
|                 INC_BUF(1);
 | |
|                 continue;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /* Found a start code - enter parse state */
 | |
|         str->state = SSTATE_PARSE;
 | |
| 
 | |
|         /* Pack header, skip it */
 | |
|         if (CMP_4_CONST(p, PACK_START_CODE))
 | |
|         {
 | |
|             /* Max lookahead: 14 */
 | |
|             if ((p[4] & 0xc0) == 0x40)      /* mpeg-2 */
 | |
|             {
 | |
|                 /* Max delta: 14 + 7 = 21 */
 | |
|                 /* Skip pack header and any stuffing bytes*/
 | |
|                 bytes = 14 + (p[13] & 7);
 | |
|             }
 | |
|             else if ((p[4] & 0xf0) == 0x20) /* mpeg-1 */
 | |
|             {
 | |
|                 bytes = 12;
 | |
|             }
 | |
|             else                            /* unknown - skip it */
 | |
|             {
 | |
|                 DEBUGF("weird pack header!\n");
 | |
|                 bytes = 5;
 | |
|             }
 | |
| 
 | |
|             INC_BUF(bytes);
 | |
|         }
 | |
| 
 | |
|         /* System header, parse and skip it - 6 bytes + size */
 | |
|         if (CMP_4_CONST(p, SYSTEM_HEADER_START_CODE))
 | |
|         {
 | |
|             /* Skip start code */
 | |
|             /* Max Delta = 65535 + 6 = 65541 */
 | |
|             bytes = 6 + ((p[4] << 8) | p[5]);
 | |
|             INC_BUF(bytes);
 | |
|         }
 | |
| 
 | |
|         /* Packet header, parse it */
 | |
|         if (!CMP_3_CONST(p, PACKET_START_CODE_PREFIX))
 | |
|         {
 | |
|             /* Problem? Meh...probably not but just a corrupted section.
 | |
|              * Try to resync the parser which will probably succeed. */
 | |
|             DEBUGF("packet start code prefix not found: 0x%02x\n"
 | |
|                    "  wl:%lu wr:%lu\n"
 | |
|                    "  p:%p cp:%p cpe:%p\n"
 | |
|                    "  dbs:%p dbe:%p dbt:%p\n",
 | |
|                    str->id, str->hdr.win_left, str->hdr.win_right,
 | |
|                    p, str->curr_packet, str->curr_packet_end,
 | |
|                    disk_buf.start, disk_buf.end, disk_buf.tail);
 | |
|             str->state = SSTATE_SYNC;
 | |
|             INC_BUF(1); /* Next byte - this one's no good */
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         /* We retrieve basic infos */
 | |
|         /* Maximum packet length: 6 + 65535 = 65541 */
 | |
|         id = p[3];
 | |
|         length = ((p[4] << 8) | p[5]) + 6;
 | |
| 
 | |
|         if (id != str->id)
 | |
|         {
 | |
|             switch (id)
 | |
|             {
 | |
|             case MPEG_STREAM_PROGRAM_END:
 | |
|                 /* end of stream */
 | |
|                 str_end_of_stream(str);
 | |
|                 DEBUGF("MPEG program end: 0x%02x\n", str->id);
 | |
|                 return STREAM_DATA_END;
 | |
|             case MPEG_STREAM_PACK_HEADER:
 | |
|             case MPEG_STREAM_SYSTEM_HEADER:
 | |
|                 /* These shouldn't be here - no increment or resync
 | |
|                  * since we'll pick it up above. */
 | |
|                 continue;
 | |
|             default:
 | |
|                 /* It's not the packet we're looking for, skip it */
 | |
|                 INC_BUF(length);
 | |
|                 continue;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /* Ok, it's our packet */
 | |
|         header = p;
 | |
| 
 | |
|         if ((header[6] & 0xc0) == 0x80) /* mpeg2 */
 | |
|         {
 | |
|             /* Max Lookahead: 18         */
 | |
|             /* Min length: 9             */
 | |
|             /* Max length: 9 + 255 = 264 */
 | |
|             length = 9 + header[8];
 | |
| 
 | |
|             /* header points to the mpeg2 pes header */
 | |
|             if ((header[7] & 0x80) != 0)
 | |
|             {
 | |
|                 /* header has a pts */
 | |
|                 uint32_t pts = read_pts(header, 9);
 | |
| 
 | |
|                 if (pts != INVALID_TIMESTAMP)
 | |
|                 {
 | |
|                     str->pts = pts;
 | |
| #if 0
 | |
|                    /* DTS isn't used for anything since things just get
 | |
|                       decoded ASAP but keep the code around */
 | |
|                     if (STREAM_IS_VIDEO(id))
 | |
|                     {
 | |
|                         /* Video stream - header may have a dts as well */
 | |
|                         str->dts = pts;
 | |
| 
 | |
|                         if (header[7] & 0x40) != 0x00)
 | |
|                         {
 | |
|                             pts = read_pts(header, 14);
 | |
|                             if (pts != INVALID_TIMESTAMP)
 | |
|                                 str->dts = pts;
 | |
|                         }
 | |
|                     }
 | |
| #endif
 | |
|                     str->pkt_flags |= PKT_HAS_TS;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else                            /* mpeg1 */
 | |
|         {
 | |
|             /* Max lookahead: 24 + 2 + 9 = 35 */
 | |
|             /* Max len_skip: 24 + 2 = 26      */
 | |
|             /* Min length: 7                  */
 | |
|             /* Max length: 24 + 2 + 9 = 35    */
 | |
|             off_t len_skip;
 | |
|             uint8_t * ptsbuf;
 | |
| 
 | |
|             length = 7;
 | |
| 
 | |
|             while (header[length - 1] == 0xff)
 | |
|             {
 | |
|                 if (++length > 23)
 | |
|                 {
 | |
|                     DEBUGF("Too much stuffing" );
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if ((header[length - 1] & 0xc0) == 0x40)
 | |
|                 length += 2;
 | |
| 
 | |
|             len_skip = length;
 | |
|             length += mpeg1_skip_table[header[length - 1] >> 4];
 | |
| 
 | |
|             /* Header points to the mpeg1 pes header */
 | |
|             ptsbuf = header + len_skip;
 | |
| 
 | |
|             if ((ptsbuf[-1] & 0xe0) == 0x20 && TS_CHECK_MARKERS(ptsbuf, -1))
 | |
|             {
 | |
|                 /* header has a pts */
 | |
|                 uint32_t pts = read_pts(ptsbuf, -1);
 | |
| 
 | |
|                 if (pts != INVALID_TIMESTAMP)
 | |
|                 {
 | |
|                     str->pts = pts;
 | |
| #if 0
 | |
|                     /* DTS isn't used for anything since things just get
 | |
|                        decoded ASAP but keep the code around */
 | |
|                     if (STREAM_IS_VIDEO(id))
 | |
|                     {
 | |
|                         /* Video stream - header may have a dts as well */
 | |
|                         str->dts = pts;
 | |
| 
 | |
|                         if (ptsbuf[-1] & 0xf0) == 0x30)
 | |
|                         {
 | |
|                             pts = read_pts(ptsbuf, 4);
 | |
| 
 | |
|                             if (pts != INVALID_TIMESTAMP)
 | |
|                                 str->dts = pts;
 | |
|                         }
 | |
|                     }
 | |
| #endif
 | |
|                     str->pkt_flags |= PKT_HAS_TS;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         p += length;
 | |
|         /* Max bytes: 6 + 65535 - 7 = 65534 */
 | |
|         bytes = 6 + (header[4] << 8) + header[5] - length;
 | |
| 
 | |
|         str->curr_packet = p;
 | |
|         str->curr_packet_end = p + bytes;
 | |
|         str->hdr.win_left = str->hdr.win_right + length;
 | |
|         str->hdr.win_right = str->hdr.win_left + bytes;
 | |
| 
 | |
|         if (str->hdr.win_right > disk_buf.filesize)
 | |
|         {
 | |
|             /* No packet that exceeds end of file can be valid */
 | |
|             str_end_of_stream(str);
 | |
|             return STREAM_DATA_END;
 | |
|         }
 | |
| 
 | |
|         return STREAM_OK;
 | |
|     } /* end while */
 | |
| 
 | |
|     #undef INC_BUF
 | |
| }
 | |
| 
 | |
| /* This simply reads data from the file one page at a time and returns a
 | |
|  * pointer to it in the buffer. */
 | |
| static int parse_elementary(struct stream *str, enum stream_parse_mode type)
 | |
| {
 | |
|     uint8_t *p;
 | |
|     ssize_t len = 0;
 | |
| 
 | |
|     str->pkt_flags = 0;
 | |
| 
 | |
|     switch (type)
 | |
|     {
 | |
|     case STREAM_PM_STREAMING:
 | |
|         /* Has the end been reached already? */
 | |
|         if (str->state == SSTATE_END)
 | |
|             return STREAM_DATA_END;
 | |
| 
 | |
|         /* Are we at the end of file? */
 | |
|         if (str->hdr.win_left >= disk_buf.filesize)
 | |
|         {
 | |
|             str_end_of_stream(str);
 | |
|             return STREAM_DATA_END;
 | |
|         }
 | |
| 
 | |
|         if (!disk_buf_is_data_ready(&str->hdr, MIN_BUFAHEAD))
 | |
|         {
 | |
|             /* This data range is not buffered yet - register stream to
 | |
|              * be notified when it becomes available. Stream is obliged
 | |
|              * to enter a TSTATE_DATA state if it must wait. */
 | |
|             int res = str_next_data_not_ready(str);
 | |
| 
 | |
|             if (res != STREAM_OK)
 | |
|                 return res;
 | |
|         }
 | |
| 
 | |
|         len = DISK_BUF_PAGE_SIZE;
 | |
| 
 | |
|         if ((size_t)(str->hdr.win_right + len) > (size_t)disk_buf.filesize)
 | |
|             len = disk_buf.filesize - str->hdr.win_right;
 | |
| 
 | |
|         if (len <= 0)
 | |
|         {
 | |
|             str_end_of_stream(str);
 | |
|             return STREAM_DATA_END;
 | |
|         }
 | |
| 
 | |
|         p = str->curr_packet_end;
 | |
|         if (p >= disk_buf.end)
 | |
|             p -= disk_buf.size;
 | |
|         break;
 | |
|         /* STREAM_PM_STREAMING: */
 | |
| 
 | |
|     case STREAM_PM_RANDOM_ACCESS:
 | |
|         str->hdr.pos = disk_buf_lseek(str->hdr.pos, SEEK_SET);
 | |
|         len = disk_buf_getbuffer(DISK_BUF_PAGE_SIZE, &p, NULL, NULL);
 | |
| 
 | |
|         if (len <= 0 || str->hdr.pos < 0 || str->hdr.pos >= str->hdr.limit)
 | |
|         {
 | |
|             str_end_of_stream(str);
 | |
|             return STREAM_DATA_END;
 | |
|         }
 | |
|         break;
 | |
|         /* STREAM_PM_RANDOM_ACCESS: */
 | |
|     }
 | |
| 
 | |
|     str->state = SSTATE_PARSE;
 | |
|     str->curr_packet = p;
 | |
|     str->curr_packet_end = p + len;
 | |
|     str->hdr.win_left = str->hdr.win_right;
 | |
|     str->hdr.win_right = str->hdr.win_left + len;
 | |
| 
 | |
|     return STREAM_OK;
 | |
| }
 | |
| 
 | |
| bool parser_prepare_image(uint32_t time)
 | |
| {
 | |
|     struct stream_scan sk;
 | |
|     int tries;
 | |
|     int result;
 | |
| 
 | |
|     stream_scan_init(&sk);
 | |
| 
 | |
|     if (!str_send_msg(&video_str, STREAM_NEEDS_SYNC, time))
 | |
|     {
 | |
|         DEBUGF("Image was ready\n");
 | |
|         return true; /* Should already have the image */
 | |
|     }
 | |
| 
 | |
| #ifdef HAVE_ADJUSTABLE_CPU_FREQ
 | |
|     rb->cpu_boost(true); /* No interference with trigger_cpu_boost */
 | |
| #endif
 | |
| 
 | |
|     str_send_msg(&video_str, STREAM_RESET, 0);
 | |
| 
 | |
|     sk.pos = parser_can_seek() ?
 | |
|                 mpeg_parser_seek_PTS(time, video_str.id) : 0;
 | |
|     sk.len = sk.pos;
 | |
|     sk.dir = SSCAN_REVERSE;
 | |
| 
 | |
|     tries = 1;
 | |
| try_again:
 | |
| 
 | |
|     if (mpeg_parser_scan_start_code(&sk, MPEG_START_GOP))
 | |
|     {
 | |
|         DEBUGF("GOP found at: %ld\n", sk.pos);
 | |
| 
 | |
|         unsigned id = mpeg_parser_scan_pes(&sk);
 | |
| 
 | |
|         if (id != video_str.id && sk.pos > 0)
 | |
|         {
 | |
|             /* Not part of our stream */
 | |
|             DEBUGF("  wrong stream: 0x%02x\n", id);
 | |
|             goto try_again;
 | |
|         }
 | |
| 
 | |
|         /* This will hit the PES header since it's known to be there */
 | |
|         uint32_t pts = mpeg_parser_scan_pts(&sk, id);
 | |
| 
 | |
|         if (pts == INVALID_TIMESTAMP || pts > time)
 | |
|         {
 | |
|             DEBUGF("  wrong timestamp: %u\n", (unsigned)pts);
 | |
|             goto try_again;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     str_parser.parms.sd.time = time;
 | |
|     str_parser.parms.sd.sk.pos = MAX(sk.pos, 0);
 | |
|     str_parser.parms.sd.sk.len = 1024*1024;
 | |
|     str_parser.parms.sd.sk.dir = SSCAN_FORWARD;
 | |
| 
 | |
|     DEBUGF("thumb pos:%ld len:%ld\n", str_parser.parms.sd.sk.pos,
 | |
|            (long)str_parser.parms.sd.sk.len);
 | |
| 
 | |
|     result = str_send_msg(&video_str, STREAM_SYNC,
 | |
|                           (intptr_t)&str_parser.parms.sd);
 | |
| 
 | |
|     if (result != STREAM_PERFECT_MATCH)
 | |
|     {
 | |
|         /* Two tries should be all that is nescessary to find the exact frame
 | |
|          * if the first GOP actually started later than the timestamp - the
 | |
|          * GOP just prior must then start on or earlier. */
 | |
|         if (++tries <= 2)
 | |
|             goto try_again;
 | |
|     }
 | |
| 
 | |
| #ifdef HAVE_ADJUSTABLE_CPU_FREQ
 | |
|     rb->cpu_boost(false);
 | |
| #endif
 | |
| 
 | |
|     return result > STREAM_OK;
 | |
| }
 | |
| 
 | |
| /* Seek parser to the specified time and return absolute time.
 | |
|  * No actual hard stuff is performed here. That's done when streaming is
 | |
|  * about to begin or something from the current position is requested */
 | |
| uint32_t parser_seek_time(uint32_t time)
 | |
| {
 | |
|     if (!parser_can_seek())
 | |
|         time = 0;
 | |
|     else if (time > str_parser.duration)
 | |
|         time = str_parser.duration;
 | |
| 
 | |
|     str_parser.last_seek_time = time + str_parser.start_pts;
 | |
|     return str_parser.last_seek_time;
 | |
| }
 | |
| 
 | |
| void parser_prepare_streaming(void)
 | |
| {
 | |
|     struct stream_window sw;
 | |
| 
 | |
|     DEBUGF("parser_prepare_streaming\n");
 | |
| 
 | |
|     /* Prepare initial video frame */
 | |
|     parser_prepare_image(str_parser.last_seek_time);
 | |
| 
 | |
|     /* Sync audio stream */
 | |
|     if (audio_str.start_pts != INVALID_TIMESTAMP)
 | |
|         prepare_audio(str_parser.last_seek_time);
 | |
| 
 | |
|     /* Prequeue some data and set buffer window */
 | |
|     if (!stream_get_window(&sw))
 | |
|         sw.left = sw.right = disk_buf.filesize;
 | |
| 
 | |
|     DEBUGF("  swl:%ld swr:%ld\n", sw.left, sw.right);
 | |
| 
 | |
|     if (sw.right > disk_buf.filesize - 4*MIN_BUFAHEAD)
 | |
|         sw.right = disk_buf.filesize - 4*MIN_BUFAHEAD;
 | |
| 
 | |
|     disk_buf_prepare_streaming(sw.left,
 | |
|         sw.right - sw.left + 4*MIN_BUFAHEAD);
 | |
| }
 | |
| 
 | |
| int parser_init_stream(void)
 | |
| {
 | |
|     if (disk_buf.in_file < 0)
 | |
|         return STREAM_ERROR;
 | |
| 
 | |
|     /* TODO: Actually find which streams are available */
 | |
|     audio_str.id = MPEG_STREAM_AUDIO_FIRST;
 | |
|     video_str.id = MPEG_STREAM_VIDEO_FIRST;
 | |
| 
 | |
|     /* Try to pull a video PES - if not found, try video init anyway which
 | |
|      * should succeed if it really is a video-only stream */
 | |
|     video_str.hdr.pos = 0;
 | |
|     video_str.hdr.limit = 256*1024;
 | |
|     
 | |
|     if (parse_demux(&video_str, STREAM_PM_RANDOM_ACCESS) == STREAM_OK)
 | |
|     {
 | |
|         /* Found a video packet - assume program stream */
 | |
|         str_parser.format = STREAM_FMT_MPEG_PS;
 | |
|         str_parser.next_data = parse_demux;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         /* No PES element found - assume video elementary stream */
 | |
|         str_parser.format = STREAM_FMT_MPV;
 | |
|         str_parser.next_data = parse_elementary;
 | |
|     }
 | |
| 
 | |
|     if (!init_video_info())
 | |
|     {
 | |
|         /* Cannot determine video size, etc. */
 | |
|         parser_init_state();
 | |
|         return STREAM_UNSUPPORTED;
 | |
|     }
 | |
| 
 | |
|     if (str_parser.format == STREAM_FMT_MPEG_PS)
 | |
|     {
 | |
|         /* Initalize start_pts and end_pts with the length (in 45kHz units) of
 | |
|          * the movie. INVALID_TIMESTAMP if the time could not be determined */
 | |
|         if (!init_times(&video_str) || !check_times(&video_str))
 | |
|         {
 | |
|             /* Must have video at least */
 | |
|             parser_init_state();
 | |
|             return STREAM_UNSUPPORTED;
 | |
|         }
 | |
| 
 | |
|         str_parser.flags |= STREAMF_CAN_SEEK;
 | |
| 
 | |
|         if (init_times(&audio_str))
 | |
|         {
 | |
|             /* Audio will be part of playback pool */
 | |
|             stream_add_stream(&audio_str);
 | |
| 
 | |
|             if (check_times(&audio_str))
 | |
|             {
 | |
|                 /* Overall duration is maximum span */
 | |
|                 str_parser.start_pts = MIN(audio_str.start_pts, video_str.start_pts);
 | |
|                 str_parser.end_pts = MAX(audio_str.end_pts, video_str.end_pts);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 /* Bad times on audio - use video times */
 | |
|                 str_parser.start_pts = video_str.start_pts;
 | |
|                 str_parser.end_pts = video_str.end_pts;
 | |
| 
 | |
|                 /* Questionable: could use bitrate seek and match video to that */
 | |
|                 audio_str.start_pts = video_str.start_pts;
 | |
|                 audio_str.end_pts = video_str.end_pts;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             /* No audio stream - use video only */
 | |
|             str_parser.start_pts = video_str.start_pts;
 | |
|             str_parser.end_pts = video_str.end_pts;
 | |
|         }
 | |
| 
 | |
|         str_parser.last_seek_time = str_parser.start_pts;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         /* There's no way to handle times on this without a full file
 | |
|          * scan */
 | |
|         audio_str.start_pts = INVALID_TIMESTAMP;
 | |
|         audio_str.end_pts = INVALID_TIMESTAMP;
 | |
|         video_str.start_pts = 0;
 | |
|         video_str.end_pts = INVALID_TIMESTAMP;
 | |
|         str_parser.start_pts = 0;
 | |
|         str_parser.end_pts = INVALID_TIMESTAMP;
 | |
|     }
 | |
| 
 | |
|     /* Add video to playback pool */
 | |
|     stream_add_stream(&video_str);
 | |
| 
 | |
|     /* Cache duration - it's used very often */
 | |
|     str_parser.duration = str_parser.end_pts - str_parser.start_pts;
 | |
| 
 | |
|     DEBUGF("Movie info:\n"
 | |
|            "  size:%dx%d\n"
 | |
|            "  start:%u\n"
 | |
|            "  end:%u\n"
 | |
|            "  duration:%u\n",
 | |
|            str_parser.dims.w, str_parser.dims.h,
 | |
|            (unsigned)str_parser.start_pts,
 | |
|            (unsigned)str_parser.end_pts,
 | |
|            (unsigned)str_parser.duration);
 | |
| 
 | |
|     return STREAM_OK;
 | |
| }
 | |
| 
 | |
| void parser_close_stream(void)
 | |
| {
 | |
|     stream_remove_streams();
 | |
|     parser_init_state();
 | |
| }
 | |
| 
 | |
| bool parser_init(void)
 | |
| {
 | |
|     parser_init_state();
 | |
|     return true;
 | |
| }
 |