d7244926f5
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@26232 a1c6a512-1295-4272-9138-f99709370657
1224 lines
37 KiB
C
1224 lines
37 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 void init_times(struct stream *str)
|
|
{
|
|
int i;
|
|
struct stream tmp_str;
|
|
const ssize_t filesize = disk_buf_filesize();
|
|
const ssize_t max_probe = MIN(512*1024, filesize);
|
|
|
|
/* Simply find the first earliest timestamp - this will be the one
|
|
* used when streaming anyway */
|
|
DEBUGF("Finding start_pts: 0x%02x\n", str->id);
|
|
|
|
tmp_str.id = str->id;
|
|
tmp_str.hdr.pos = 0;
|
|
tmp_str.hdr.limit = max_probe;
|
|
|
|
str->start_pts = INVALID_TIMESTAMP;
|
|
|
|
/* Probe many for video because of B-frames */
|
|
for (i = STREAM_IS_VIDEO(str->id) ? 5 : 1; i > 0;)
|
|
{
|
|
switch (parser_get_next_data(&tmp_str, STREAM_PM_RANDOM_ACCESS))
|
|
{
|
|
case STREAM_DATA_END:
|
|
break;
|
|
case STREAM_OK:
|
|
if (tmp_str.pkt_flags & PKT_HAS_TS)
|
|
{
|
|
if (tmp_str.pts < str->start_pts)
|
|
str->start_pts = tmp_str.pts;
|
|
i--; /* Decrement timestamp counter */
|
|
}
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
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->end_pts = INVALID_TIMESTAMP;
|
|
|
|
if (str->start_pts != INVALID_TIMESTAMP)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* End must be greater than start. If the start PTS is found, the end PTS
|
|
* must be valid too. If the start PTS was invalid, then the end will never
|
|
* be scanned above. */
|
|
if (str->start_pts >= str->end_pts || str->end_pts == INVALID_TIMESTAMP)
|
|
{
|
|
str->start_pts = INVALID_TIMESTAMP;
|
|
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 bool 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
intptr_t parser_send_video_msg(long id, intptr_t data)
|
|
{
|
|
intptr_t retval = 0;
|
|
|
|
if (video_str.thread != 0 && disk_buf.in_file >= 0)
|
|
{
|
|
/* Hook certain messages since they involve multiple operations
|
|
* behind the scenes */
|
|
switch (id)
|
|
{
|
|
case VIDEO_DISPLAY_SHOW:
|
|
if (data != 0 && disk_buf_status() == STREAM_STOPPED)
|
|
{ /* Only prepare image if showing and not playing */
|
|
prepare_image(str_parser.last_seek_time);
|
|
}
|
|
break;
|
|
|
|
case VIDEO_PRINT_FRAME:
|
|
if (data)
|
|
break;
|
|
case VIDEO_PRINT_THUMBNAIL:
|
|
if (disk_buf_status() != STREAM_STOPPED)
|
|
break; /* Prepare image if not playing */
|
|
|
|
if (!prepare_image(str_parser.last_seek_time))
|
|
return false; /* Preparation failed */
|
|
|
|
/* Image ready - pass message to video thread */
|
|
break;
|
|
}
|
|
|
|
retval = str_send_msg(&video_str, id, data);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* 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 */
|
|
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. */
|
|
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 */
|
|
init_times(&audio_str);
|
|
init_times(&video_str);
|
|
|
|
if (video_str.start_pts == INVALID_TIMESTAMP)
|
|
{
|
|
/* Must have video at least */
|
|
return STREAM_UNSUPPORTED;
|
|
}
|
|
|
|
str_parser.flags |= STREAMF_CAN_SEEK;
|
|
|
|
if (audio_str.start_pts != INVALID_TIMESTAMP)
|
|
{
|
|
/* 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);
|
|
|
|
/* Audio will be part of playback pool */
|
|
stream_add_stream(&audio_str);
|
|
}
|
|
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;
|
|
}
|