forked from len0rd/rockbox
mpegplayer: Better sync, smoother frames - corrected a few minor flaws. Keep timestamps 32-bit in stead of 33 - a 45kHz clock is good enough for humans. Increase pts queue size since the mpeg buffer is now filled when buffering the audio stream. Cleanup frame drop code a little too.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13175 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
parent
7a5d4011f5
commit
eb4dcd80b7
1 changed files with 122 additions and 95 deletions
|
@ -108,10 +108,6 @@ FPS | 27Mhz | 100Hz | 44.1KHz | 48KHz
|
||||||
#include "mpeg2.h"
|
#include "mpeg2.h"
|
||||||
#include "mpeg_settings.h"
|
#include "mpeg_settings.h"
|
||||||
#include "video_out.h"
|
#include "video_out.h"
|
||||||
#ifndef ATTR_ALIGN
|
|
||||||
#define ATTR_ALIGN(a) __attribute__((aligned (a)))
|
|
||||||
#endif
|
|
||||||
#include "mpeg2_internal.h"
|
|
||||||
#include "../../codecs/libmad/mad.h"
|
#include "../../codecs/libmad/mad.h"
|
||||||
|
|
||||||
PLUGIN_HEADER
|
PLUGIN_HEADER
|
||||||
|
@ -493,12 +489,13 @@ static void init_mad(void* mad_frame_overlap)
|
||||||
#define SYSTEM_HEADER_START_CODE 0x000001bbul
|
#define SYSTEM_HEADER_START_CODE 0x000001bbul
|
||||||
|
|
||||||
/* p = base pointer, b0 - b4 = byte offsets from p */
|
/* p = base pointer, b0 - b4 = byte offsets from p */
|
||||||
|
/* We only care about the MS 32 bits of the 33 and so the ticks are 45kHz */
|
||||||
#define TS_FROM_HEADER(p, b0, b1, b2, b3, b4) \
|
#define TS_FROM_HEADER(p, b0, b1, b2, b3, b4) \
|
||||||
((uint32_t)(((p)[b0] >> 1 << 30) | \
|
((uint32_t)(((p)[b0] >> 1 << 29) | \
|
||||||
((p)[b1] << 22) | \
|
((p)[b1] << 21) | \
|
||||||
((p)[b2] >> 1 << 15) | \
|
((p)[b2] >> 1 << 14) | \
|
||||||
((p)[b3] << 7) | \
|
((p)[b3] << 6) | \
|
||||||
((p)[b4] >> 1 )))
|
((p)[b4] >> 2 )))
|
||||||
|
|
||||||
/* This function demuxes the streams and gives the next stream data pointer */
|
/* This function demuxes the streams and gives the next stream data pointer */
|
||||||
static void get_next_data( Stream* str )
|
static void get_next_data( Stream* str )
|
||||||
|
@ -612,10 +609,12 @@ static void get_next_data( Stream* str )
|
||||||
/* header points to the mpeg2 pes header */
|
/* header points to the mpeg2 pes header */
|
||||||
if (header[7] & 0x80)
|
if (header[7] & 0x80)
|
||||||
{
|
{
|
||||||
|
/* header has a pts */
|
||||||
uint32_t pts = TS_FROM_HEADER(header, 9, 10, 11, 12, 13);
|
uint32_t pts = TS_FROM_HEADER(header, 9, 10, 11, 12, 13);
|
||||||
|
|
||||||
if (stream >= 0xe0)
|
if (stream >= 0xe0)
|
||||||
{
|
{
|
||||||
|
/* video stream - header may have a dts as well */
|
||||||
uint32_t dts = (header[7] & 0x40) == 0 ?
|
uint32_t dts = (header[7] & 0x40) == 0 ?
|
||||||
pts : TS_FROM_HEADER(header, 14, 15, 16, 17, 18);
|
pts : TS_FROM_HEADER(header, 14, 15, 16, 17, 18);
|
||||||
|
|
||||||
|
@ -659,10 +658,12 @@ static void get_next_data( Stream* str )
|
||||||
|
|
||||||
if ((ptsbuf[-1] & 0xe0) == 0x20)
|
if ((ptsbuf[-1] & 0xe0) == 0x20)
|
||||||
{
|
{
|
||||||
|
/* header has a pts */
|
||||||
uint32_t pts = TS_FROM_HEADER(ptsbuf, -1, 0, 1, 2, 3);
|
uint32_t pts = TS_FROM_HEADER(ptsbuf, -1, 0, 1, 2, 3);
|
||||||
|
|
||||||
if (stream >= 0xe0)
|
if (stream >= 0xe0)
|
||||||
{
|
{
|
||||||
|
/* video stream - header may have a dts as well */
|
||||||
uint32_t dts = (ptsbuf[-1] & 0xf0) != 0x30 ?
|
uint32_t dts = (ptsbuf[-1] & 0xf0) != 0x30 ?
|
||||||
pts : TS_FROM_HEADER(ptsbuf, 4, 5, 6, 7, 18);
|
pts : TS_FROM_HEADER(ptsbuf, 4, 5, 6, 7, 18);
|
||||||
|
|
||||||
|
@ -723,8 +724,8 @@ static void get_next_data( Stream* str )
|
||||||
|
|
||||||
/* For simple lowpass filtering of sync variables */
|
/* For simple lowpass filtering of sync variables */
|
||||||
#define AVERAGE(var, x, count) (((var) * (count-1) + (x)) / (count))
|
#define AVERAGE(var, x, count) (((var) * (count-1) + (x)) / (count))
|
||||||
/* Convert 90kHz PTS/DTS ticks to our clock ticks */
|
/* Convert 45kHz PTS/DTS ticks to our clock ticks */
|
||||||
#define TS_TO_TICKS(pts) ((uint64_t)CLOCK_RATE*(pts) / 90000)
|
#define TS_TO_TICKS(pts) ((uint64_t)CLOCK_RATE*(pts) / 45000)
|
||||||
/* Convert 27MHz ticks to our clock ticks */
|
/* Convert 27MHz ticks to our clock ticks */
|
||||||
#define TIME_TO_TICKS(stamp) ((uint64_t)CLOCK_RATE*(stamp) / 27000000)
|
#define TIME_TO_TICKS(stamp) ((uint64_t)CLOCK_RATE*(stamp) / 27000000)
|
||||||
|
|
||||||
|
@ -737,14 +738,14 @@ static bool init_mpabuf(void)
|
||||||
return mpa_buffer != NULL;
|
return mpa_buffer != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define PTS_QUEUE_LEN (1 << 4) /* 16 should be way more than sufficient -
|
#define PTS_QUEUE_LEN (1 << 5) /* 32 should be way more than sufficient -
|
||||||
if not, the case is handled */
|
if not, the case is handled */
|
||||||
#define PTS_QUEUE_MASK (PTS_QUEUE_LEN-1)
|
#define PTS_QUEUE_MASK (PTS_QUEUE_LEN-1)
|
||||||
struct pts_queue_slot
|
struct pts_queue_slot
|
||||||
{
|
{
|
||||||
uint32_t pts; /* Time stamp for packet */
|
uint32_t pts; /* Time stamp for packet */
|
||||||
ssize_t size; /* Number of bytes left in packet */
|
ssize_t size; /* Number of bytes left in packet */
|
||||||
} pts_queue[PTS_QUEUE_MASK+1];
|
} pts_queue[PTS_QUEUE_LEN];
|
||||||
|
|
||||||
/* This starts out wr == rd but will never be emptied to zero during
|
/* This starts out wr == rd but will never be emptied to zero during
|
||||||
streaming again in order to support initializing the first packet's
|
streaming again in order to support initializing the first packet's
|
||||||
|
@ -752,12 +753,6 @@ struct pts_queue_slot
|
||||||
static unsigned pts_queue_rd;
|
static unsigned pts_queue_rd;
|
||||||
static unsigned pts_queue_wr;
|
static unsigned pts_queue_wr;
|
||||||
|
|
||||||
/* Resets the pts queue - call when starting and seeking */
|
|
||||||
static void pts_queue_reset(void)
|
|
||||||
{
|
|
||||||
pts_queue_rd = pts_queue_wr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Increments the queue head postion - should be used to preincrement */
|
/* Increments the queue head postion - should be used to preincrement */
|
||||||
static bool pts_queue_add_head(void)
|
static bool pts_queue_add_head(void)
|
||||||
{
|
{
|
||||||
|
@ -790,6 +785,16 @@ static struct pts_queue_slot * pts_queue_tail(void)
|
||||||
return &pts_queue[pts_queue_rd & PTS_QUEUE_MASK];
|
return &pts_queue[pts_queue_rd & PTS_QUEUE_MASK];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Resets the pts queue - call when starting and seeking */
|
||||||
|
static void pts_queue_reset(void)
|
||||||
|
{
|
||||||
|
struct pts_queue_slot *pts;
|
||||||
|
pts_queue_rd = pts_queue_wr;
|
||||||
|
pts = pts_queue_tail();
|
||||||
|
pts->pts = 0;
|
||||||
|
pts->size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
struct pcm_frame_header /* Header added to pcm data every time a decoded
|
struct pcm_frame_header /* Header added to pcm data every time a decoded
|
||||||
mpa frame is sent out */
|
mpa frame is sent out */
|
||||||
{
|
{
|
||||||
|
@ -992,6 +997,21 @@ static void audio_thread(void)
|
||||||
if (audiostatus == PLEASE_STOP)
|
if (audiostatus == PLEASE_STOP)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
|
if (pts->size <= 0)
|
||||||
|
{
|
||||||
|
/* Carry any overshoot to the next size since we're technically
|
||||||
|
-pts->size bytes into it already. If size is negative an audio
|
||||||
|
frame was split accross packets. Old has to be saved before
|
||||||
|
moving the tail. */
|
||||||
|
if (pts_queue_remove_tail())
|
||||||
|
{
|
||||||
|
struct pts_queue_slot *old = pts;
|
||||||
|
pts = pts_queue_tail();
|
||||||
|
pts->size += old->size;
|
||||||
|
old->size = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Buffering **/
|
/** Buffering **/
|
||||||
if (mpabuf_used >= MPA_MAX_FRAME_SIZE + MAD_BUFFER_GUARD)
|
if (mpabuf_used >= MPA_MAX_FRAME_SIZE + MAD_BUFFER_GUARD)
|
||||||
{
|
{
|
||||||
|
@ -999,46 +1019,53 @@ static void audio_thread(void)
|
||||||
}
|
}
|
||||||
else if (audio_str.curr_packet != NULL)
|
else if (audio_str.curr_packet != NULL)
|
||||||
{
|
{
|
||||||
/* Get data from next audio packet */
|
do
|
||||||
len = audio_str.curr_packet_end - audio_str.curr_packet;
|
|
||||||
|
|
||||||
if (audio_str.tagged)
|
|
||||||
{
|
{
|
||||||
struct pts_queue_slot *stamp = pts;
|
/* Get data from next audio packet */
|
||||||
|
len = audio_str.curr_packet_end - audio_str.curr_packet;
|
||||||
|
|
||||||
if (pts_queue_add_head())
|
if (audio_str.tagged)
|
||||||
{
|
{
|
||||||
stamp = pts_queue_head();
|
struct pts_queue_slot *stamp = pts;
|
||||||
stamp->pts = TS_TO_TICKS(audio_str.curr_pts);
|
|
||||||
/* pts->size should have been zeroed when slot was freed */
|
if (pts_queue_add_head())
|
||||||
|
{
|
||||||
|
stamp = pts_queue_head();
|
||||||
|
stamp->pts = TS_TO_TICKS(audio_str.curr_pts);
|
||||||
|
/* pts->size should have been zeroed when slot was
|
||||||
|
freed */
|
||||||
|
}
|
||||||
|
/* else queue full - just count up from the last to make
|
||||||
|
it look like more data in the same packet */
|
||||||
|
stamp->size += len;
|
||||||
|
audio_str.tagged = 0;
|
||||||
}
|
}
|
||||||
/* else queue full - just count up from the last to make it look
|
else
|
||||||
like more data in the same packet */
|
{
|
||||||
stamp->size += len;
|
/* Add to the one just behind the head - this may be the
|
||||||
audio_str.tagged = 0;
|
tail or the previouly added head - whether or not we'll
|
||||||
|
ever reach this is quite in question since audio always
|
||||||
|
seems to have every packet timestamped */
|
||||||
|
pts_queue_head()->size += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slide any remainder over to beginning - avoid function
|
||||||
|
call overhead if no data remaining as well */
|
||||||
|
if (mpabuf > mpa_buffer && mpabuf_used > 0)
|
||||||
|
rb->memmove(mpa_buffer, mpabuf, mpabuf_used);
|
||||||
|
|
||||||
|
/* Splice this packet onto any remainder */
|
||||||
|
rb->memcpy(mpa_buffer + mpabuf_used, audio_str.curr_packet,
|
||||||
|
len);
|
||||||
|
|
||||||
|
mpabuf_used += len;
|
||||||
|
mpabuf = mpa_buffer;
|
||||||
|
|
||||||
|
/* Get data from next audio packet */
|
||||||
|
get_next_data(&audio_str);
|
||||||
}
|
}
|
||||||
else
|
while (audio_str.curr_packet != NULL &&
|
||||||
{
|
mpabuf_used < MPA_MAX_FRAME_SIZE + MAD_BUFFER_GUARD);
|
||||||
/* Add to the one just behind the head - this may be the tail or
|
|
||||||
the previouly added head - whether or not we'll ever reach this
|
|
||||||
is quite in question since audio always seems to have every
|
|
||||||
packet timestamped */
|
|
||||||
pts_queue_head()->size += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Slide any remainder over to beginning - avoid function call overhead if
|
|
||||||
no data remaining as well */
|
|
||||||
if (mpabuf > mpa_buffer && mpabuf_used > 0)
|
|
||||||
rb->memmove(mpa_buffer, mpabuf, mpabuf_used);
|
|
||||||
|
|
||||||
/* Splice this packet onto any remainder */
|
|
||||||
rb->memcpy(mpa_buffer + mpabuf_used, audio_str.curr_packet, len);
|
|
||||||
|
|
||||||
mpabuf_used += len;
|
|
||||||
mpabuf = mpa_buffer;
|
|
||||||
|
|
||||||
/* Move stream position to the next packet */
|
|
||||||
get_next_data(&audio_str);
|
|
||||||
}
|
}
|
||||||
else if (mpabuf_used <= 0)
|
else if (mpabuf_used <= 0)
|
||||||
{
|
{
|
||||||
|
@ -1051,6 +1078,13 @@ static void audio_thread(void)
|
||||||
|
|
||||||
mad_stat = mad_frame_decode(&frame, &stream);
|
mad_stat = mad_frame_decode(&frame, &stream);
|
||||||
|
|
||||||
|
if (stream.next_frame == NULL)
|
||||||
|
{
|
||||||
|
/* What to do here? (This really is fatal) */
|
||||||
|
DEBUGF("/* What to do here? */\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* Next mad stream buffer is the next frame postion */
|
/* Next mad stream buffer is the next frame postion */
|
||||||
mpabuf = (uint8_t *)stream.next_frame;
|
mpabuf = (uint8_t *)stream.next_frame;
|
||||||
|
|
||||||
|
@ -1059,25 +1093,6 @@ static void audio_thread(void)
|
||||||
mpabuf_used -= len;
|
mpabuf_used -= len;
|
||||||
pts->size -= len;
|
pts->size -= len;
|
||||||
|
|
||||||
if (pts->size <= 0)
|
|
||||||
{
|
|
||||||
/* Carry any overshoot to the next size since we're technically
|
|
||||||
-pts->size bytes into it already. If size is negative an audio
|
|
||||||
frame was split accross packets. Old has to be saved before
|
|
||||||
moving the tail. */
|
|
||||||
struct pts_queue_slot *old = pts;
|
|
||||||
|
|
||||||
if (pts_queue_remove_tail())
|
|
||||||
{
|
|
||||||
pts = pts_queue_tail();
|
|
||||||
pts->size += old->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Zero the size of the last slot to simplify stamping code and ensure
|
|
||||||
that it is never negative if the tail didn't move */
|
|
||||||
old->size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mad_stat != 0)
|
if (mad_stat != 0)
|
||||||
{
|
{
|
||||||
DEBUGF("Audio stream error - %d\n", stream.error);
|
DEBUGF("Audio stream error - %d\n", stream.error);
|
||||||
|
@ -1113,13 +1128,6 @@ static void audio_thread(void)
|
||||||
/* Generate the pcm samples */
|
/* Generate the pcm samples */
|
||||||
mad_synth_frame(&synth, &frame);
|
mad_synth_frame(&synth, &frame);
|
||||||
|
|
||||||
if (stream.next_frame == NULL)
|
|
||||||
{
|
|
||||||
/* What to do here? */
|
|
||||||
DEBUGF("/* What to do here? */\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Output **/
|
/** Output **/
|
||||||
|
|
||||||
/* TODO: Output through core dsp. We'll still use our own PCM buffer
|
/* TODO: Output through core dsp. We'll still use our own PCM buffer
|
||||||
|
@ -1257,6 +1265,7 @@ static void video_thread(void)
|
||||||
char str[80];
|
char str[80];
|
||||||
int dither_index = 0;
|
int dither_index = 0;
|
||||||
uint32_t curr_time = 0;
|
uint32_t curr_time = 0;
|
||||||
|
uint32_t period = 0; /* Frame period in clock ticks */
|
||||||
uint32_t eta_audio = UINT_MAX, eta_video = 0;
|
uint32_t eta_audio = UINT_MAX, eta_video = 0;
|
||||||
int32_t eta_early = 0, eta_late = 0;
|
int32_t eta_early = 0, eta_late = 0;
|
||||||
int frame_drop_level = 0;
|
int frame_drop_level = 0;
|
||||||
|
@ -1307,6 +1316,8 @@ static void video_thread(void)
|
||||||
|
|
||||||
while (videostatus == STREAM_PAUSING)
|
while (videostatus == STREAM_PAUSING)
|
||||||
rb->sleep(HZ/10);
|
rb->sleep(HZ/10);
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
state = mpeg2_parse (mpeg2dec);
|
state = mpeg2_parse (mpeg2dec);
|
||||||
|
@ -1336,7 +1347,6 @@ static void video_thread(void)
|
||||||
case STATE_PICTURE:
|
case STATE_PICTURE:
|
||||||
{
|
{
|
||||||
/* A new picture is available - see if we should draw it */
|
/* A new picture is available - see if we should draw it */
|
||||||
uint32_t period; /* Frame period in clock ticks */
|
|
||||||
int32_t offset; /* Tick adjustment to keep sync */
|
int32_t offset; /* Tick adjustment to keep sync */
|
||||||
|
|
||||||
/* No limiting => no dropping so simply make sure skipping is off
|
/* No limiting => no dropping so simply make sure skipping is off
|
||||||
|
@ -1344,12 +1354,12 @@ static void video_thread(void)
|
||||||
if (!settings.limitfps)
|
if (!settings.limitfps)
|
||||||
goto picture_done;
|
goto picture_done;
|
||||||
|
|
||||||
period = TIME_TO_TICKS(info->sequence->frame_period);
|
|
||||||
|
|
||||||
/* Get presentation times in audio samples - quite accurate
|
/* Get presentation times in audio samples - quite accurate
|
||||||
enough */
|
enough - add previous frame duration if not stamped */
|
||||||
curr_time = (info->current_picture->flags & PIC_FLAG_TAGS) ?
|
curr_time = (info->display_picture->flags & PIC_FLAG_TAGS) ?
|
||||||
TS_TO_TICKS(info->current_picture->tag) : (curr_time + period);
|
TS_TO_TICKS(info->display_picture->tag) : (curr_time + period);
|
||||||
|
|
||||||
|
period = TIME_TO_TICKS(info->sequence->frame_period);
|
||||||
|
|
||||||
eta_video = curr_time;
|
eta_video = curr_time;
|
||||||
eta_audio = get_stream_time();
|
eta_audio = get_stream_time();
|
||||||
|
@ -1442,21 +1452,32 @@ static void video_thread(void)
|
||||||
if (frame_drop_level > 1 || offset > CLOCK_RATE*167/1000)
|
if (frame_drop_level > 1 || offset > CLOCK_RATE*167/1000)
|
||||||
{
|
{
|
||||||
/* Frame type: I/P/B/D */
|
/* Frame type: I/P/B/D */
|
||||||
int type = info->current_picture->flags & PIC_MASK_CODING_TYPE;
|
int type = info->display_picture->flags & PIC_MASK_CODING_TYPE;
|
||||||
|
|
||||||
/* Things are running a bit late or all frames are being
|
/* Things are running a bit late or all frames are being
|
||||||
dropped until a key frame */
|
dropped until a key frame */
|
||||||
|
if (frame_drop_level > 1)
|
||||||
if (frame_drop_level > 1 && (type == I_TYPE || type == D_TYPE))
|
{
|
||||||
frame_drop_level = 0; /* This frame can be drawn */
|
switch (type)
|
||||||
|
{
|
||||||
|
case PIC_FLAG_CODING_TYPE_I:
|
||||||
|
case PIC_FLAG_CODING_TYPE_D:
|
||||||
|
frame_drop_level = 0; /* This frame can be drawn */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (frame_drop_level <= 1 && offset > CLOCK_RATE*250/1000)
|
if (frame_drop_level <= 1 && offset > CLOCK_RATE*250/1000)
|
||||||
{
|
{
|
||||||
/* Things are very, very late. Resort to stronger measures
|
/* Things are very, very late. Resort to stronger measures
|
||||||
to keep sync by dropping a I/D/P frame. Drawing cannot
|
to keep sync by dropping a I/D/P frame. Drawing cannot
|
||||||
take place again until the next key frame. */
|
take place again until the next key frame. */
|
||||||
if (type == I_TYPE || type == D_TYPE || type == P_TYPE)
|
switch (type)
|
||||||
|
{
|
||||||
|
case PIC_FLAG_CODING_TYPE_I:
|
||||||
|
case PIC_FLAG_CODING_TYPE_P:
|
||||||
|
case PIC_FLAG_CODING_TYPE_D:
|
||||||
frame_drop_level = 2;
|
frame_drop_level = 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drop_frame = 1 << frame_drop_level;
|
drop_frame = 1 << frame_drop_level;
|
||||||
|
@ -1466,12 +1487,18 @@ static void video_thread(void)
|
||||||
/* Timeout has expired and this frame will be drawn if it
|
/* Timeout has expired and this frame will be drawn if it
|
||||||
is available. This may reverse the decision to drop a
|
is available. This may reverse the decision to drop a
|
||||||
key frame above. */
|
key frame above. */
|
||||||
if (frame_drop_level <= 1 || type == I_TYPE || type == D_TYPE)
|
switch (type)
|
||||||
{
|
{
|
||||||
drop_frame = 0;
|
default:
|
||||||
|
if (frame_drop_level <= 1)
|
||||||
|
{
|
||||||
|
case PIC_FLAG_CODING_TYPE_I:
|
||||||
|
case PIC_FLAG_CODING_TYPE_D:
|
||||||
|
drop_frame = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (type == B_TYPE)
|
else if (type == PIC_FLAG_CODING_TYPE_B)
|
||||||
{
|
{
|
||||||
/* We want to drop something, so this B frame won't even be
|
/* We want to drop something, so this B frame won't even be
|
||||||
decoded. Drawing can happen on the next frame if so
|
decoded. Drawing can happen on the next frame if so
|
||||||
|
@ -1538,7 +1565,7 @@ static void video_thread(void)
|
||||||
if (clock_ticks != 0)
|
if (clock_ticks != 0)
|
||||||
fps = num_drawn*CLOCK_RATE*10ll / clock_ticks;
|
fps = num_drawn*CLOCK_RATE*10ll / clock_ticks;
|
||||||
|
|
||||||
rb->snprintf(str, sizeof(str), "%d.%d %d %d",
|
rb->snprintf(str, sizeof(str), "%d.%d %d %d ",
|
||||||
fps / 10, fps % 10, num_skipped,
|
fps / 10, fps % 10, num_skipped,
|
||||||
info->display_picture->temporal_reference);
|
info->display_picture->temporal_reference);
|
||||||
rb->lcd_putsxy(0, 0, str);
|
rb->lcd_putsxy(0, 0, str);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue