| /*************************************************************************** |
| * __________ __ ___. |
| * 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 = 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) |
| { |
| 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, 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); |
| /* 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, 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<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; |
| |
| 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? */ |
| 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 != 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 && 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 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; |
| } |
| |
| 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; |
| } |