/*************************************************************************** * __________ __ ___. * 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 charpic_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 intvideo_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 boolinit_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;returnvideo_str_scan(td, &sd) == STREAM_OK;}static boolcheck_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 intsync_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<=t<fe\n");
td->syncf_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 boolframe_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 voidvideo_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->cpucache_invalidate());vo_lock();
rb->lcd_update();vo_unlock();}#endifbreak;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 voidvideo_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 */boolvideo_thread_init(void){intptr_t rep;IF_COP(rb->cpucache_flush());
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->cpucache_invalidate());return rep ==0;/* Normally STREAM_NULL should be ignored */}/* Terminates the video thread */voidvideo_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->cpucache_invalidate());
video_str.thread =0;}}/** Misc **/voidvideo_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);}