rockbox/apps/plugins/mpegplayer/video_thread.c
Solomon Peachy 9d3d925295 Revert "RFC: Get rid of mpegplayer plugin"
This reverts commit d25d24812e.

Change-Id: I1563223e343fb1e2eda72a45823b38350025ff93
2022-10-13 11:08:11 -04:00

1059 lines
32 KiB
C

/***************************************************************************
* __________ __ ___.
* 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<=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 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);
}