From a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Sat, 29 Dec 2007 19:46:35 +0000 Subject: 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 --- apps/plugins/mpegplayer/mpeg_parser.c | 1182 +++++++++++++++++++++++++++++++++ 1 file changed, 1182 insertions(+) create mode 100644 apps/plugins/mpegplayer/mpeg_parser.c (limited to 'apps/plugins/mpegplayer/mpeg_parser.c') 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 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Parser for MPEG streams + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 NOCACHEBSS_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 = LONG_MIN; + str->hdr.win_left = LONG_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(4, &p, NULL, NULL); + + 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(4, &p, NULL, NULL); + + 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(35, &p, NULL, NULL); + + 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 < 6) + { + /* 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 = 7; + ssize_t margin = sk->margin; + + /* Skip stuffing_byte */ + while (h[l - 1] == 0xff && ++l <= 23) + --margin; + + if ((h[l - 1] & 0xc0) == 0x40) + { + /* Skip STD_buffer_scale and STD_buffer_size */ + margin -= 2; + l += 2; + } + + if (margin >= 4) + { + /* header points to the mpeg1 pes header */ + h += l; + + if ((h[-1] & 0xe0) == 0x20) + { + sk->data = (h + 4) - p; + return read_pts(h, -1); + } + } + } + /* 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 (str->start_pts >= str->end_pts) + { + 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; + + /* 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, + pos_left, pos_new, 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) + { + /* 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) + pos_left; + /* Point is ahead of us - fudge estimate a bit high */ + pos_new = muldiv_uint32(11, pos_new - pos_left, 10) + + pos_left; + + 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, pos_left, pos_new, 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) + pos_left; + /* Overshot the seek point - fudge estimate a bit low */ + pos_new = muldiv_uint32(9, pos_new - pos_left, 10) + pos_left; + + 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, pos_left, pos_new, 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, pos_left, pos_new, 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, pos_left, pos_new, 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", + pos, (unsigned)pts, (unsigned)time, (unsigned)nextpts); + + if (pts <= time && time < nextpts) + { + /* Smile - it worked */ + DEBUGF(" :) pts<=time 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; + + 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, + 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? */ + 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; + } + 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; + 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 != NULL && 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 && stream_status() != STREAM_PLAYING) + { /* Only prepare image if showing and not playing */ + prepare_image(str_parser.last_seek_time); + } + break; + + case VIDEO_PRINT_FRAME: + case VIDEO_PRINT_THUMBNAIL: + if (stream_status() == STREAM_PLAYING) + 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 transport stream */ + str_parser.format = STREAM_FMT_MPEG_TS; + 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_TS) + { + /* 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; + } + } + 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; +} -- cgit v1.2.3