/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * mpegplayer video thread implementation * * 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" #include "libmpeg2/mpeg2dec_config.h" #include "lib/grey.h" #include "video_out.h" #include "mpeg_settings.h" /** Video stream and thread **/ /* Video thread data passed around to its various functions */ struct video_thread_data { /* Stream data */ mpeg2dec_t *mpeg2dec; /* Our video decoder */ const mpeg2_info_t *info; /* Info about video stream */ int state; /* Thread state */ int status; /* Media status */ struct queue_event ev;/* Our event queue to receive commands */ /* Operational info */ uint32_t stream_time; /* Current time from beginning of stream */ uint32_t goal_time; /* Scheduled time of current frame */ int32_t remain_time; /* T-minus value to frame_time (-:early, +:late) */ int skip_ref_pics; /* Severe skipping - wait for I-frame */ int skip_level; /* Number of frames still to skip */ int num_picture; /* Number of picture headers read */ int num_intra; /* Number of I-picture headers read */ int group_est; /* Estmated number remaining as of last I */ long last_render; /* Last time a frame was drawn */ /* Sync info */ uint32_t frame_time; /* Current due time of frame (unadjusted) */ uint32_t frame_period; /* Frame period in clock ticks */ int num_ref_pics; /* Number of I and P frames since sync/skip */ int syncf_perfect; /* Last sync fit result */ }; /* Number drawn since reset */ static int video_num_drawn SHAREDBSS_ATTR; /* Number skipped since reset */ static int video_num_skipped SHAREDBSS_ATTR; /* TODO: Check if 4KB is appropriate - it works for my test streams, so maybe we can reduce it. */ #define VIDEO_STACKSIZE (4*1024) static uint32_t video_stack[VIDEO_STACKSIZE / sizeof(uint32_t)] IBSS_ATTR; static struct event_queue video_str_queue SHAREDBSS_ATTR; static struct queue_sender_list video_str_queue_send SHAREDBSS_ATTR; struct stream video_str IBSS_ATTR; #define DEFAULT_GOP_SIZE INT_MAX /* no I/P skips until it learns */ #define DROP_THRESHOLD (100*TS_SECOND/1000) #define MAX_EARLINESS (120*TS_SECOND/1000) #if defined(DEBUG) || defined(SIMULATOR) static unsigned char pic_coding_type_char(unsigned type) { switch (type) { case PIC_FLAG_CODING_TYPE_I: return 'I'; /* Intra-coded */ case PIC_FLAG_CODING_TYPE_P: return 'P'; /* Forward-predicted */ case PIC_FLAG_CODING_TYPE_B: return 'B'; /* Bidirectionally-predicted */ case PIC_FLAG_CODING_TYPE_D: return 'D'; /* DC-coded */ default: return '?'; /* Say what? */ } } #endif /* defined(DEBUG) || defined(SIMULATOR) */ /* Multi-use: * 1) Find the sequence header and initialize video out * 2) Find the end of the final frame */ static int video_str_scan(struct video_thread_data *td, struct str_sync_data *sd) { int retval = STREAM_ERROR; uint32_t time = INVALID_TIMESTAMP; uint32_t period = 0; struct stream tmp_str; tmp_str.id = video_str.id; tmp_str.hdr.pos = sd->sk.pos; tmp_str.hdr.limit = sd->sk.pos + sd->sk.len; /* Fully reset if obtaining size for a new stream */ mpeg2_reset(td->mpeg2dec, td->ev.id == VIDEO_GET_SIZE); mpeg2_skip(td->mpeg2dec, 1); while (1) { mpeg2_state_t mp2state = mpeg2_parse(td->mpeg2dec); rb->yield(); switch (mp2state) { case STATE_BUFFER: switch (parser_get_next_data(&tmp_str, STREAM_PM_RANDOM_ACCESS)) { case STREAM_DATA_END: DEBUGF("video_stream_scan:STREAM_DATA_END\n"); goto scan_finished; case STREAM_OK: if (tmp_str.pkt_flags & PKT_HAS_TS) mpeg2_tag_picture(td->mpeg2dec, tmp_str.pts, 0); mpeg2_buffer(td->mpeg2dec, tmp_str.curr_packet, tmp_str.curr_packet_end); td->info = mpeg2_info(td->mpeg2dec); break; } break; case STATE_SEQUENCE: DEBUGF("video_stream_scan:STATE_SEQUENCE\n"); vo_setup(td->info->sequence); if (td->ev.id == VIDEO_GET_SIZE) { retval = STREAM_OK; goto scan_finished; } break; case STATE_SLICE: case STATE_END: case STATE_INVALID_END: { if (td->info->display_picture == NULL) break; switch (td->ev.id) { case STREAM_SYNC: retval = STREAM_OK; goto scan_finished; case STREAM_FIND_END_TIME: if (td->info->display_picture->flags & PIC_FLAG_TAGS) time = td->info->display_picture->tag; else if (time != INVALID_TIMESTAMP) time += period; period = TC_TO_TS(td->info->sequence->frame_period); break; } break; } default: break; } } scan_finished: if (td->ev.id == STREAM_FIND_END_TIME) { if (time != INVALID_TIMESTAMP) { sd->time = time + period; retval = STREAM_PERFECT_MATCH; } else { retval = STREAM_NOT_FOUND; } } mpeg2_skip(td->mpeg2dec, 0); return retval; } static bool init_sequence(struct video_thread_data *td) { struct str_sync_data sd; sd.time = 0; /* Ignored */ sd.sk.pos = 0; sd.sk.len = 1024*1024; sd.sk.dir = SSCAN_FORWARD; return video_str_scan(td, &sd) == STREAM_OK; } static bool check_needs_sync(struct video_thread_data *td, uint32_t time) { uint32_t end_time; DEBUGF("check_needs_sync:\n"); if (td->info == NULL || td->info->display_fbuf == NULL) { DEBUGF(" no fbuf\n"); return true; } if (td->syncf_perfect == 0) { DEBUGF(" no frame\n"); return true; } time = clip_time(&video_str, time); end_time = td->frame_time + td->frame_period; DEBUGF(" sft:%u t:%u sfte:%u\n", (unsigned)td->frame_time, (unsigned)time, (unsigned)end_time); if (time < td->frame_time) return true; if (time >= end_time) return time < video_str.end_pts || end_time < video_str.end_pts; return false; } /* Do any needed decoding/slide up to the specified time */ static int sync_decoder(struct video_thread_data *td, struct str_sync_data *sd) { int retval = STREAM_ERROR; uint32_t time = clip_time(&video_str, sd->time); td->syncf_perfect = 0; td->frame_time = 0; td->frame_period = 0; td->num_ref_pics = 0; /* Sometimes theres no sequence headers nearby and libmpeg2 may have reset * fully at some point */ if ((td->info == NULL || td->info->sequence == NULL) && !init_sequence(td)) { DEBUGF("sync_decoder=>init_sequence failed\n"); goto sync_finished; } video_str.hdr.pos = sd->sk.pos; video_str.hdr.limit = sd->sk.pos + sd->sk.len; mpeg2_reset(td->mpeg2dec, false); mpeg2_skip(td->mpeg2dec, 1); while (1) { mpeg2_state_t mp2state = mpeg2_parse(td->mpeg2dec); switch (mp2state) { case STATE_BUFFER: switch (parser_get_next_data(&video_str, STREAM_PM_RANDOM_ACCESS)) { case STREAM_DATA_END: DEBUGF("sync_decoder:STR_DATA_END\n"); if (td->info && td->info->display_picture && !(td->info->display_picture->flags & PIC_FLAG_SKIP)) { /* No frame matching the time was found up to the end of * the stream - consider a perfect match since no better * can be made */ retval = STREAM_PERFECT_MATCH; td->syncf_perfect = 1; } goto sync_finished; case STREAM_OK: if (video_str.pkt_flags & PKT_HAS_TS) mpeg2_tag_picture(td->mpeg2dec, video_str.pts, 0); mpeg2_buffer(td->mpeg2dec, video_str.curr_packet, video_str.curr_packet_end); td->info = mpeg2_info(td->mpeg2dec); break; } break; case STATE_SEQUENCE: DEBUGF(" STATE_SEQUENCE\n"); vo_setup(td->info->sequence); break; case STATE_GOP: DEBUGF(" STATE_GOP: (%s)\n", (td->info->gop->flags & GOP_FLAG_CLOSED_GOP) ? "closed" : "open"); break; case STATE_PICTURE: { int type = td->info->current_picture->flags & PIC_MASK_CODING_TYPE; switch (type) { case PIC_FLAG_CODING_TYPE_I: /* I-frame; start decoding */ mpeg2_skip(td->mpeg2dec, 0); td->num_ref_pics++; break; case PIC_FLAG_CODING_TYPE_P: /* P-frames don't count without I-frames */ if (td->num_ref_pics > 0) td->num_ref_pics++; break; } if (td->info->current_picture->flags & PIC_FLAG_TAGS) { DEBUGF(" STATE_PICTURE (%c): %u\n", pic_coding_type_char(type), (unsigned)td->info->current_picture->tag); } else { DEBUGF(" STATE_PICTURE (%c): -\n", pic_coding_type_char(type)); } break; } case STATE_SLICE: case STATE_END: case STATE_INVALID_END: { uint32_t end_time; if (td->info->display_picture == NULL) { DEBUGF(" td->info->display_picture == NULL\n"); break; /* No picture */ } int type = td->info->display_picture->flags & PIC_MASK_CODING_TYPE; if (td->info->display_picture->flags & PIC_FLAG_TAGS) { td->frame_time = td->info->display_picture->tag; DEBUGF(" frame tagged:%u (%c%s)\n", (unsigned)td->frame_time, pic_coding_type_char(type), (td->info->display_picture->flags & PIC_FLAG_SKIP) ? " skipped" : ""); } else { td->frame_time += td->frame_period; DEBUGF(" add frame_period:%u (%c%s)\n", (unsigned)td->frame_time, pic_coding_type_char(type), (td->info->display_picture->flags & PIC_FLAG_SKIP) ? " skipped" : ""); } td->frame_period = TC_TO_TS(td->info->sequence->frame_period); end_time = td->frame_time + td->frame_period; DEBUGF(" ft:%u t:%u fe:%u (%c%s)", (unsigned)td->frame_time, (unsigned)time, (unsigned)end_time, pic_coding_type_char(type), (td->info->display_picture->flags & PIC_FLAG_SKIP) ? " skipped" : ""); if (end_time <= time && end_time < video_str.end_pts) { /* Still too early and have not hit at EOS */ DEBUGF(" too early\n"); break; } else if (!(td->info->display_picture->flags & PIC_FLAG_SKIP)) { /* One perfect point if dependent frames were decoded */ switch (type) { case PIC_FLAG_CODING_TYPE_B: if (td->num_ref_pics > 1) { case PIC_FLAG_CODING_TYPE_P: if (td->num_ref_pics > 0) { case PIC_FLAG_CODING_TYPE_I: td->syncf_perfect = 1; break; } } } if ((td->frame_time <= time && time < end_time) || end_time >= video_str.end_pts) { /* One perfect point for matching time goal */ DEBUGF(" ft<=tsyncf_perfect++; } else { DEBUGF(" ft>t\n"); } /* Two or more perfect points = perfect match - yay! */ retval = (td->syncf_perfect >= 2) ? STREAM_PERFECT_MATCH : STREAM_MATCH; } else { /* Too late, no I-Frame yet */ DEBUGF("\n"); } goto sync_finished; } default: break; } rb->yield(); } /* end while */ sync_finished: mpeg2_skip(td->mpeg2dec, 0); return retval; } static bool frame_print_handler(struct video_thread_data *td) { bool retval; uint8_t * const * buf = NULL; if (td->info != NULL && td->info->display_fbuf != NULL && td->syncf_perfect > 0) buf = td->info->display_fbuf->buf; if (td->ev.id == VIDEO_PRINT_THUMBNAIL) { /* Print a thumbnail of whatever was last decoded - scale and * position to fill the specified rectangle */ retval = vo_draw_frame_thumb(buf, (struct vo_rect *)td->ev.data); } else { /* Print the last frame decoded */ vo_draw_frame(buf); retval = buf != NULL; } return retval; } /* This only returns to play or quit */ static void video_thread_msg(struct video_thread_data *td) { while (1) { intptr_t reply = 0; switch (td->ev.id) { case STREAM_PLAY: td->status = STREAM_PLAYING; switch (td->state) { case TSTATE_INIT: /* Begin decoding state */ td->state = TSTATE_DECODE; /* */ case TSTATE_DECODE: if (td->syncf_perfect <= 0) break; /* There should be a frame already, just draw it */ td->goal_time = td->frame_time; td->state = TSTATE_RENDER_WAIT; /* */ case TSTATE_RENDER_WAIT: /* Settings may have changed to nonlimited - just draw * what was previously being waited for */ td->stream_time = TICKS_TO_TS(stream_get_time()); if (!settings.limitfps) td->state = TSTATE_RENDER; /* */ case TSTATE_RENDER: break; case TSTATE_EOS: /* At end of stream - no playback possible so fire the * completion event */ stream_generate_event(&video_str, STREAM_EV_COMPLETE, 0); break; } reply = td->state != TSTATE_EOS; break; case STREAM_PAUSE: td->status = STREAM_PAUSED; reply = td->state != TSTATE_EOS; break; case STREAM_STOP: if (td->state == TSTATE_DATA) stream_clear_notify(&video_str, DISK_BUF_DATA_NOTIFY); td->status = STREAM_STOPPED; td->state = TSTATE_EOS; reply = true; break; case VIDEO_DISPLAY_IS_VISIBLE: reply = vo_is_visible(); break; case VIDEO_DISPLAY_SHOW: /* Show video and draw the last frame we had if any or reveal the * underlying framebuffer if hiding */ reply = vo_show(!!td->ev.data); #ifdef HAVE_LCD_COLOR /* Match graylib behavior as much as possible */ if (!td->ev.data == !reply) break; if (td->ev.data) { frame_print_handler(td); } else { IF_COP(rb->commit_discard_dcache()); vo_lock(); rb->lcd_update(); vo_unlock(); } #endif break; case STREAM_RESET: if (td->state == TSTATE_DATA) stream_clear_notify(&video_str, DISK_BUF_DATA_NOTIFY); td->state = TSTATE_INIT; td->status = STREAM_STOPPED; /* Reset operational info but not sync info */ td->stream_time = UINT32_MAX; td->goal_time = 0; td->remain_time = 0; td->skip_ref_pics = 0; td->skip_level = 0; td->num_picture = 0; td->num_intra = 0; td->group_est = DEFAULT_GOP_SIZE; td->last_render = *rb->current_tick - HZ; video_num_drawn = 0; video_num_skipped = 0; reply = true; break; case STREAM_NEEDS_SYNC: reply = check_needs_sync(td, td->ev.data); break; case STREAM_SYNC: if (td->state == TSTATE_INIT) reply = sync_decoder(td, (struct str_sync_data *)td->ev.data); break; case DISK_BUF_DATA_NOTIFY: /* Our bun is done */ if (td->state != TSTATE_DATA) break; td->state = TSTATE_DECODE; str_data_notify_received(&video_str); break; case VIDEO_PRINT_FRAME: case VIDEO_PRINT_THUMBNAIL: reply = frame_print_handler(td); break; case VIDEO_SET_CLIP_RECT: vo_set_clip_rect((const struct vo_rect *)td->ev.data); break; case VIDEO_GET_CLIP_RECT: reply = vo_get_clip_rect((struct vo_rect *)td->ev.data); break; case VIDEO_GET_SIZE: { if (td->state != TSTATE_INIT) break; /* Can only use after a reset was issued */ /* This will reset the decoder in full for this particular event */ if (init_sequence(td)) { reply = true; vo_dimensions((struct vo_ext *)td->ev.data); } break; } case STREAM_FIND_END_TIME: if (td->state != TSTATE_INIT) { reply = STREAM_ERROR; break; } reply = video_str_scan(td, (struct str_sync_data *)td->ev.data); break; case VIDEO_SET_POST_FRAME_CALLBACK: vo_set_post_draw_callback((void (*)(void))td->ev.data); reply = true; break; case STREAM_QUIT: /* Time to go - make thread exit */ td->state = TSTATE_EOS; return; } str_reply_msg(&video_str, reply); if (td->status == STREAM_PLAYING) { switch (td->state) { case TSTATE_DECODE: case TSTATE_RENDER: case TSTATE_RENDER_WAIT: /* These return when in playing state */ return; } } str_get_msg(&video_str, &td->ev); } } static void video_thread(void) { struct video_thread_data td; memset(&td, 0, sizeof (td)); td.mpeg2dec = mpeg2_init(); td.status = STREAM_STOPPED; td.state = TSTATE_EOS; if (td.mpeg2dec == NULL) { td.status = STREAM_ERROR; /* Loop and wait for quit message */ while (1) { str_get_msg(&video_str, &td.ev); if (td.ev.id == STREAM_QUIT) return; str_reply_msg(&video_str, STREAM_ERROR); } } vo_init(); goto message_wait; while (1) { mpeg2_state_t mp2state; td.state = TSTATE_DECODE; /* Check for any pending messages and process them */ if (str_have_msg(&video_str)) { message_wait: /* Wait for a message to be queued */ str_get_msg(&video_str, &td.ev); message_process: /* Process a message already dequeued */ video_thread_msg(&td); switch (td.state) { /* These states are the only ones that should return */ case TSTATE_DECODE: goto picture_decode; case TSTATE_RENDER: goto picture_draw; case TSTATE_RENDER_WAIT: goto picture_wait; /* Anything else is interpreted as an exit */ default: goto video_exit; } } picture_decode: mp2state = mpeg2_parse (td.mpeg2dec); switch (mp2state) { case STATE_BUFFER: /* Request next packet data */ switch (parser_get_next_data(&video_str, STREAM_PM_STREAMING)) { case STREAM_DATA_NOT_READY: /* Wait for data to be buffered */ td.state = TSTATE_DATA; goto message_wait; case STREAM_DATA_END: /* No more data. */ td.state = TSTATE_EOS; if (td.status == STREAM_PLAYING) stream_generate_event(&video_str, STREAM_EV_COMPLETE, 0); goto message_wait; case STREAM_OK: if (video_str.pkt_flags & PKT_HAS_TS) mpeg2_tag_picture(td.mpeg2dec, video_str.pts, 0); mpeg2_buffer(td.mpeg2dec, video_str.curr_packet, video_str.curr_packet_end); td.info = mpeg2_info(td.mpeg2dec); break; } break; case STATE_SEQUENCE: /* New video sequence, inform output of any changes */ vo_setup(td.info->sequence); break; case STATE_PICTURE: { /* This is not in presentation order - do our best anyway */ int skip = td.skip_ref_pics; /* Frame type: I/P/B/D */ switch (td.info->current_picture->flags & PIC_MASK_CODING_TYPE) { case PIC_FLAG_CODING_TYPE_I: if (++td.num_intra >= 2) td.group_est = td.num_picture / (td.num_intra - 1); /* Things are extremely late and all frames will be dropped until the next key frame */ if (td.skip_level > 0 && td.skip_level >= td.group_est) { td.skip_level--; /* skip frame */ skip = td.skip_ref_pics = 1; /* wait for I-frame */ td.num_ref_pics = 0; } else if (skip != 0) { skip = td.skip_ref_pics = 0; /* now, decode */ td.num_ref_pics = 1; } break; case PIC_FLAG_CODING_TYPE_P: if (skip == 0) { td.num_ref_pics++; /* If skip_level at least the estimated number of frames left in I-I span, skip until next I-frame */ if (td.group_est > 0 && td.skip_level >= td.group_est) { skip = td.skip_ref_pics = 1; /* wait for I-frame */ td.num_ref_pics = 0; } } if (skip != 0) td.skip_level--; break; case PIC_FLAG_CODING_TYPE_B: /* We want to drop something, so this B-frame won't even be decoded. Drawing can happen on the next frame if so desired so long as the B-frames were not dependent upon those from a previous open GOP where the needed reference frames were skipped */ if (td.skip_level > 0 || td.num_ref_pics < 2) { skip = 1; td.skip_level--; } break; default: skip = 1; break; } if (td.num_intra > 0) td.num_picture++; td.group_est--; mpeg2_skip(td.mpeg2dec, skip); break; } case STATE_SLICE: case STATE_END: case STATE_INVALID_END: { int32_t offset; /* Tick adjustment to keep sync */ if (td.info->display_fbuf == NULL) break; /* No picture */ /* Get presentation times in audio samples - quite accurate enough - add previous frame duration if not stamped */ if (td.info->display_picture->flags & PIC_FLAG_TAGS) td.frame_time = td.info->display_picture->tag; else td.frame_time += td.frame_period; td.frame_period = TC_TO_TS(td.info->sequence->frame_period); if (!settings.limitfps) { /* No limiting => no dropping or waiting - draw this frame */ td.remain_time = 0; td.skip_level = 0; td.syncf_perfect = 1; /* have frame */ goto picture_draw; } td.goal_time = td.frame_time; td.stream_time = TICKS_TO_TS(stream_get_time()); /* How early/late are we? > 0 = late, < 0 early */ offset = td.stream_time - td.goal_time; if (offset >= 0) { /* Late or on-time */ if (td.remain_time < 0) td.remain_time = 0; /* now, late */ offset = AVERAGE(td.remain_time, offset, 4); td.remain_time = offset; } else { /* Early */ if (td.remain_time >= 0) td.remain_time = 0; /* now, early */ else if (offset > td.remain_time) td.remain_time = MAX(offset, -MAX_EARLINESS); /* less early */ else if (td.remain_time != 0) td.remain_time = AVERAGE(td.remain_time, 0, 8); /* earlier/same */ /* else there's been no frame drop */ offset = -td.remain_time; } /* Skip anything not decoded */ if (td.info->display_picture->flags & PIC_FLAG_SKIP) goto picture_skip; td.syncf_perfect = 1; /* have frame (assume so from now on) */ /* Keep goal_time >= 0 */ if ((uint32_t)offset > td.goal_time) offset = td.goal_time; td.goal_time -= offset; if (!settings.skipframes) { /* No skipping - just wait if we're early and correct for lateness as much as possible. */ td.skip_level = 0; goto picture_wait; } /** Possibly skip this frame **/ /* Frameskipping has the following order of preference: * * Frame Type Who Notes/Rationale * B decoder arbitrarily drop - no decode or draw * Any renderer arbitrarily drop - I/P unless B decoded * P decoder must wait for I-frame * I decoder must wait for I-frame * * If a frame can be drawn and it has been at least 1/2 second, * the image will be updated no matter how late it is just to * avoid looking stuck. */ if (td.skip_level > 0 && TIME_BEFORE(*rb->current_tick, td.last_render + HZ/2)) { /* Frame skip was set previously but either there wasn't anything dropped yet or not dropped enough. So we quit at least rendering the actual frame to avoid further increase of a/v-drift. */ td.skip_level--; goto picture_skip; } /* At this point a frame _will_ be drawn - a skip may happen on the next however */ /* Calculate number of frames to drop/skip - allow brief periods of lateness before producing skips */ td.skip_level = 0; if (td.remain_time > 0 && (uint32_t)offset > DROP_THRESHOLD) { td.skip_level = (offset - DROP_THRESHOLD + td.frame_period) / td.frame_period; } picture_wait: td.state = TSTATE_RENDER_WAIT; /* Wait until time catches up */ while (1) { int32_t twait = td.goal_time - td.stream_time; /* Watch for messages while waiting for the frame time */ if (twait <= 0) break; if (twait > TS_SECOND/HZ) { /* Several ticks to wait - do some sleeping */ int timeout = (twait - HZ) / (TS_SECOND/HZ); str_get_msg_w_tmo(&video_str, &td.ev, MAX(timeout, 1)); if (td.ev.id != SYS_TIMEOUT) goto message_process; } else { /* Just a little left - spin and be accurate */ rb->yield(); if (str_have_msg(&video_str)) goto message_wait; } td.stream_time = TICKS_TO_TS(stream_get_time()); } picture_draw: /* Record last frame time */ td.last_render = *rb->current_tick; vo_draw_frame(td.info->display_fbuf->buf); video_num_drawn++; break; picture_skip: if (td.remain_time <= DROP_THRESHOLD) { td.skip_level = 0; if (td.remain_time <= 0) td.remain_time = INT32_MIN; } video_num_skipped++; break; } default: break; } rb->yield(); } /* end while */ video_exit: vo_cleanup(); mpeg2_close(td.mpeg2dec); } /* Initializes the video thread */ bool video_thread_init(void) { intptr_t rep; IF_COP(rb->commit_dcache()); video_str.hdr.q = &video_str_queue; rb->queue_init(video_str.hdr.q, false); /* We put the video thread on another processor for multi-core targets. */ video_str.thread = rb->create_thread( video_thread, video_stack, VIDEO_STACKSIZE, 0, "mpgvideo" IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, COP)); rb->queue_enable_queue_send(video_str.hdr.q, &video_str_queue_send, video_str.thread); if (video_str.thread == 0) return false; /* Wait for thread to initialize */ rep = str_send_msg(&video_str, STREAM_NULL, 0); IF_COP(rb->commit_discard_dcache()); return rep == 0; /* Normally STREAM_NULL should be ignored */ } /* Terminates the video thread */ void video_thread_exit(void) { if (video_str.thread != 0) { str_post_msg(&video_str, STREAM_QUIT, 0); rb->thread_wait(video_str.thread); IF_COP(rb->commit_discard_dcache()); video_str.thread = 0; } } /** Misc **/ void video_thread_get_stats(struct video_output_stats *s) { uint32_t start; uint32_t now = stream_get_ticks(&start); s->num_drawn = video_num_drawn; s->num_skipped = video_num_skipped; s->fps = 0; if (now > start) s->fps = muldiv_uint32(CLOCK_RATE*100, s->num_drawn, now - start); }