Remove video fifo in ffmpeg core.

The video fifo can be removed, since we have a ring buffer in it's
place. This removes unneeded copy operations and as a positive side
improves overall decoding speed.

Makes 8k60p SW and 4k60p HW decoding possible on my system.

For now the ring buffer is 32 images deep. This limitation will
be removed, once audio and video decoder have their own
packet handling.
This commit is contained in:
Nils Hasenbanck 2019-12-22 18:57:09 +01:00
parent 6eed40f80c
commit c6309d963d
7 changed files with 515 additions and 491 deletions

View File

@ -1966,7 +1966,7 @@ endif
ifeq ($(HAVE_FFMPEG), 1)
OBJ += record/drivers/record_ffmpeg.o \
cores/libretro-ffmpeg/ffmpeg_core.o \
cores/libretro-ffmpeg/swsbuffer.o \
cores/libretro-ffmpeg/video_buffer.o \
$(LIBRETRO_COMM_DIR)/rthreads/tpool.o
LIBS += $(AVCODEC_LIBS) $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(SWRESAMPLE_LIBS) $(FFMPEG_LIBS)

View File

@ -19,7 +19,7 @@ SWRESAMPLE_DIR := $(BASE_DIR)/libswresample
INCFLAGS += -I$(BASE_DIR) -I$(CORE_DIR) -I$(LIBRETRO_COMM_DIR)/include -I$(LIBRETRO_COMM_DIR)/include/compat
LIBRETRO_SOURCE += $(CORE_DIR)/ffmpeg_core.c \
$(CORE_DIR)/swsbuffer.c \
$(CORE_DIR)/video_buffer.c \
$(LIBRETRO_COMM_DIR)/rthreads/tpool.c \
$(LIBRETRO_COMM_DIR)/queues/fifo_queue.c \
$(LIBRETRO_COMM_DIR)/rthreads/rthreads.c

View File

@ -50,7 +50,7 @@ extern "C" {
#include <rthreads/tpool.h>
#include <queues/fifo_queue.h>
#include <string/stdstring.h>
#include "swsbuffer.h"
#include "video_buffer.h"
#include <libretro.h>
#ifdef RARCH_INTERNAL
@ -90,7 +90,7 @@ static enum AVColorSpace colorspace;
static unsigned sw_decoder_threads;
static unsigned sw_sws_threads;
static swsbuffer_t *swsbuffer;
static video_buffer_t *video_buffer;
static tpool_t *tpool;
#if LIBAVUTIL_VERSION_MAJOR > 55
@ -141,20 +141,17 @@ static double pts_bias;
/* Threaded FIFOs. */
static volatile bool decode_thread_dead;
static fifo_buffer_t *video_decode_fifo;
static fifo_buffer_t *audio_decode_fifo;
static scond_t *fifo_cond;
static scond_t *fifo_decode_cond;
static slock_t *fifo_lock;
static slock_t *decode_thread_lock;
static sthread_t *decode_thread_handle;
static double decode_last_video_time;
static double decode_last_audio_time;
static bool main_sleeping;
static uint32_t *video_frame_temp_buffer;
static bool main_sleeping;
/* Seeking. */
static bool do_seek;
static double seek_time;
@ -459,7 +456,7 @@ static void check_variables(bool firststart)
{
sw_decoder_threads = strtoul(sw_threads_var.value, NULL, 0);
}
/* Scale the sws threads based on core count but use at min 2 and max 4 threads */
/* Scale the sws threads based on core count but use at least 2 and at most 4 threads */
sw_sws_threads = MIN(MAX(2, sw_decoder_threads / 2), 4);
}
}
@ -492,8 +489,9 @@ static void seek_frame(int seek_frames)
}
audio_frames = frame_cnt * media.sample_rate / media.interpolate_fps;
if (video_decode_fifo)
fifo_clear(video_decode_fifo);
tpool_wait(tpool);
video_buffer_clear(video_buffer);
if (audio_decode_fifo)
fifo_clear(audio_decode_fifo);
scond_signal(fifo_decode_cond);
@ -692,24 +690,19 @@ void CORE_PREFIX(retro_run)(void)
while (!decode_thread_dead && min_pts > frames[1].pts)
{
size_t to_read_frame_bytes;
int64_t pts = 0;
slock_lock(fifo_lock);
to_read_frame_bytes = media.width * media.height * sizeof(uint32_t) + sizeof(int64_t);
while (!decode_thread_dead && fifo_read_avail(video_decode_fifo) < to_read_frame_bytes)
{
main_sleeping = true;
scond_signal(fifo_decode_cond);
scond_wait(fifo_cond, fifo_lock);
main_sleeping = false;
}
if (!decode_thread_dead)
video_buffer_wait_for_finished_slot(video_buffer);
if (!decode_thread_dead)
{
uint32_t *data = video_frame_temp_buffer;
fifo_read(video_decode_fifo, &pts, sizeof(int64_t));
video_decoder_context_t *ctx = NULL;
video_buffer_get_finished_slot(video_buffer, &ctx);
pts = ctx->pts;
#if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES)
if (use_gl)
{
@ -723,7 +716,11 @@ void CORE_PREFIX(retro_run)(void)
#endif
#endif
fifo_read(video_decode_fifo, data, media.width * media.height * sizeof(uint32_t));
const uint8_t *src = ctx->target->data[0];
int stride = ctx->target->linesize[0];
int width = media.width * sizeof(uint32_t);
for (unsigned y = 0; y < media.height; y++, src += stride, data += width/4)
memcpy(data, src, width);
#ifndef HAVE_OPENGLES
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
@ -744,14 +741,17 @@ void CORE_PREFIX(retro_run)(void)
else
#endif
{
fifo_read(video_decode_fifo, data, media.width * media.height * sizeof(uint32_t));
const uint8_t *src = ctx->target->data[0];
int stride = ctx->target->linesize[0];
size_t width = media.width * sizeof(uint32_t);
for (unsigned y = 0; y < media.height; y++, src += stride, data += width/4)
memcpy(data, src, width);
dupe = false;
}
video_buffer_open_slot(video_buffer, ctx);
}
scond_signal(fifo_decode_cond);
slock_unlock(fifo_lock);
frames[1].pts = av_q2d(fctx->streams[video_stream_index]->time_base) * pts;
}
@ -1289,7 +1289,7 @@ static void sws_worker_thread(void *arg)
{
int ret = 0;
AVFrame *tmp_frame = NULL;
sws_context_t *ctx = (sws_context_t*) arg;
video_decoder_context_t *ctx = (video_decoder_context_t*) arg;
#if LIBAVUTIL_VERSION_MAJOR > 55
if (hw_decoding_enabled)
@ -1318,75 +1318,25 @@ static void sws_worker_thread(void *arg)
#endif
}
swsbuffer_finish_slot(swsbuffer, ctx);
}
ctx->pts = ctx->source->best_effort_timestamp;
#ifdef HAVE_SSA
static void add_frame_to_fifo(size_t frame_size, ASS_Track *ass_track_active)
#else
static void add_frame_to_fifo(size_t frame_size)
#endif
{
sws_context_t *ctx = NULL;
swsbuffer_get_finished_slot(swsbuffer, &ctx);
size_t decoded_size;
int64_t pts = ctx->source->best_effort_timestamp;
double video_time = pts * av_q2d(fctx->streams[video_stream_index]->time_base);
#ifdef HAVE_SSA
if (ass_render && ass_track_active)
double video_time = ctx->pts * av_q2d(fctx->streams[video_stream_index]->time_base);
if (ass_render && ctx->ass_track_active)
{
int change = 0;
ASS_Image *img = ass_render_frame(ass_render, ass_track_active,
ASS_Image *img = ass_render_frame(ass_render, ctx->ass_track_active,
1000 * video_time, &change);
/*
* Do it on CPU for now.
* We're in a thread anyways, so shouldn't really matter.
*/
render_ass_img(ctx->target, img);
}
#endif
decoded_size = frame_size + sizeof(pts);
slock_lock(fifo_lock);
while (!decode_thread_dead && (video_decode_fifo != NULL)
&& fifo_write_avail(video_decode_fifo) < decoded_size)
{
if (!main_sleeping)
scond_wait(fifo_decode_cond, fifo_lock);
else
{
fifo_clear(video_decode_fifo);
break;
}
}
decode_last_video_time = video_time;
if (!decode_thread_dead)
{
int stride;
unsigned y;
const uint8_t *src = NULL;
fifo_write(video_decode_fifo, &pts, sizeof(pts));
src = ctx->target->data[0];
stride = ctx->target->linesize[0];
for (y = 0; y < media.height; y++, src += stride)
fifo_write(video_decode_fifo, src, media.width * sizeof(uint32_t));
}
scond_signal(fifo_cond);
slock_unlock(fifo_lock);
av_frame_unref(ctx->source);
#if LIBAVUTIL_VERSION_MAJOR > 55
av_frame_unref(ctx->hw_source);
#endif
swsbuffer_open_slot(swsbuffer, ctx);
video_buffer_finish_slot(video_buffer, ctx);
}
#ifdef HAVE_SSA
@ -1396,7 +1346,24 @@ static void decode_video(AVCodecContext *ctx, AVPacket *pkt, size_t frame_size)
#endif
{
int ret = 0;
sws_context_t *sws_ctx = NULL;
video_decoder_context_t *decoder_ctx = NULL;
/* Stop decoding thread until video_buffer is not full again */
while (!decode_thread_dead && !video_buffer_has_open_slot(video_buffer))
{
/* If we don't buffer enough video frames we can run into a deadlock.
* for now drop frames in this case. This could happen with MP4 files
* since the often save the audio frames into the stream.
* Longterm solution: audio and video decoding in their own threads
* with their own file handle. */
if (main_sleeping)
{
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Thread: Video deadlock detected.\n");
tpool_wait(tpool);
video_buffer_clear(video_buffer);
return;
}
}
if ((ret = avcodec_send_packet(ctx, pkt)) < 0)
{
@ -1408,24 +1375,11 @@ static void decode_video(AVCodecContext *ctx, AVPacket *pkt, size_t frame_size)
return;
}
/* Stop decoding thread until swsbuffer is not full again */
while (!swsbuffer_has_open_slot(swsbuffer))
while (!decode_thread_dead && video_buffer_has_open_slot(video_buffer))
{
while(swsbuffer_has_finished_slot(swsbuffer))
{
#ifdef HAVE_SSA
add_frame_to_fifo(frame_size, ass_track_active);
#else
add_frame_to_fifo(frame_size);
#endif
}
}
video_buffer_get_open_slot(video_buffer, &decoder_ctx);
while (swsbuffer_has_open_slot(swsbuffer))
{
swsbuffer_get_open_slot(swsbuffer, &sws_ctx);
ret = avcodec_receive_frame(ctx, sws_ctx->source);
ret = avcodec_receive_frame(ctx, decoder_ctx->source);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
ret = -42;
@ -1444,7 +1398,7 @@ static void decode_video(AVCodecContext *ctx, AVPacket *pkt, size_t frame_size)
#if LIBAVUTIL_VERSION_MAJOR > 55
if (hw_decoding_enabled)
/* Copy data from VRAM to RAM */
if ((ret = av_hwframe_transfer_data(sws_ctx->hw_source, sws_ctx->source, 0)) < 0)
if ((ret = av_hwframe_transfer_data(decoder_ctx->hw_source, decoder_ctx->source, 0)) < 0)
{
#ifdef __cplusplus
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Error transferring the data to system memory: %d\n", ret);
@ -1455,21 +1409,16 @@ static void decode_video(AVCodecContext *ctx, AVPacket *pkt, size_t frame_size)
}
#endif
tpool_add_work(tpool, sws_worker_thread, sws_ctx);
while(swsbuffer_has_finished_slot(swsbuffer))
{
#ifdef HAVE_SSA
add_frame_to_fifo(frame_size, ass_track_active);
#else
add_frame_to_fifo(frame_size);
decoder_ctx->ass_track_active = ass_track_active;
#endif
}
tpool_add_work(tpool, sws_worker_thread, decoder_ctx);
end:
if (ret < 0)
{
swsbuffer_return_open_slot(swsbuffer, sws_ctx);
video_buffer_return_open_slot(video_buffer, decoder_ctx);
break;
}
}
@ -1523,7 +1472,7 @@ static int16_t *decode_audio(AVCodecContext *ctx, AVPacket *pkt,
(const uint8_t**)frame->data,
frame->nb_samples);
pts = av_frame_get_best_effort_timestamp(frame);
pts = frame->best_effort_timestamp;
slock_lock(fifo_lock);
while (!decode_thread_dead && fifo_write_avail(audio_decode_fifo) < required_buffer)
@ -1559,7 +1508,6 @@ static void decode_thread_seek(double time)
if (seek_to < 0)
seek_to = 0;
decode_last_video_time = time;
decode_last_audio_time = time;
if(avformat_seek_file(fctx, -1, INT64_MIN, seek_to, INT64_MAX, 0) < 0)
@ -1606,7 +1554,7 @@ static void decode_thread(void *data)
if (video_stream_index >= 0)
{
frame_size = avpicture_get_size(PIX_FMT_RGB32, media.width, media.height);
swsbuffer = swsbuffer_create(sw_sws_threads, frame_size, media.width, media.height);
video_buffer = video_buffer_create(32, frame_size, media.width, media.height);
tpool = tpool_create(sw_sws_threads);
log_cb(RETRO_LOG_INFO, "[FFMPEG] Configured filtering threads: %d\n", sw_sws_threads);
}
@ -1631,20 +1579,18 @@ static void decode_thread(void *data)
if (seek)
{
if (video_stream_index >= 0)
{
tpool_wait(tpool);
swsbuffer_clear(swsbuffer);
}
decode_thread_seek(seek_time_thread);
slock_lock(fifo_lock);
do_seek = false;
seek_time = 0.0;
if (video_decode_fifo)
fifo_clear(video_decode_fifo);
if (video_stream_index >= 0)
{
tpool_wait(tpool);
video_buffer_clear(video_buffer);
}
if (audio_decode_fifo)
fifo_clear(audio_decode_fifo);
@ -1719,12 +1665,6 @@ static void decode_thread(void *data)
av_frame_free(&aud_frame);
av_freep(&audio_buffer);
if (video_stream_index >= 0)
{
tpool_destroy(tpool);
swsbuffer_destroy(swsbuffer);
}
slock_lock(fifo_lock);
decode_thread_dead = true;
scond_signal(fifo_cond);
@ -1836,8 +1776,12 @@ void CORE_PREFIX(retro_unload_game)(void)
if (decode_thread_handle)
{
slock_lock(fifo_lock);
tpool_wait(tpool);
video_buffer_clear(video_buffer);
decode_thread_dead = true;
scond_signal(fifo_decode_cond);
slock_unlock(fifo_lock);
sthread_join(decode_thread_handle);
}
@ -1852,8 +1796,6 @@ void CORE_PREFIX(retro_unload_game)(void)
if (decode_thread_lock)
slock_free(decode_thread_lock);
if (video_decode_fifo)
fifo_free(video_decode_fifo);
if (audio_decode_fifo)
fifo_free(audio_decode_fifo);
@ -1861,10 +1803,8 @@ void CORE_PREFIX(retro_unload_game)(void)
fifo_decode_cond = NULL;
fifo_lock = NULL;
decode_thread_lock = NULL;
video_decode_fifo = NULL;
audio_decode_fifo = NULL;
decode_last_video_time = 0.0;
decode_last_audio_time = 0.0;
frames[0].pts = frames[1].pts = 0.0;
@ -1994,11 +1934,6 @@ bool CORE_PREFIX(retro_load_game)(const struct retro_game_info *info)
if (video_stream_index >= 0 || is_fft)
{
/* video fifo is 2 frames deep */
video_decode_fifo = fifo_new(
media.width * media.height * sizeof(uint32_t) * 2
);
#if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES)
use_gl = true;
hw_render.context_reset = context_reset;
@ -2014,15 +1949,15 @@ bool CORE_PREFIX(retro_load_game)(const struct retro_game_info *info)
if (!CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render))
{
use_gl = false;
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Cannot initialize HW render.");
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Cannot initialize HW render.\n");
}
#endif
}
if (audio_streams_num > 0)
{
/* audio fifo is 1 second deep */
/* audio fifo is 4 seconds deep */
audio_decode_fifo = fifo_new(
media.sample_rate * sizeof(int16_t) * 2
media.sample_rate * sizeof(int16_t) * 2 * 4
);
}

View File

@ -1,185 +0,0 @@
#include <libavformat/avformat.h>
#include <rthreads/rthreads.h>
#include "swsbuffer.h"
enum kbStatus
{
KB_OPEN,
KB_IN_PROGRESS,
KB_FINISHED
};
struct swsbuffer
{
sws_context_t *buffer;
enum kbStatus *status;
size_t size;
slock_t *lock;
int64_t head;
int64_t tail;
};
swsbuffer_t *swsbuffer_create(size_t num, int frame_size, int width, int height)
{
swsbuffer_t *b = malloc(sizeof (swsbuffer_t));
if (b == NULL)
return NULL;
b->status = malloc(sizeof(enum kbStatus) * num);
if (b->status == NULL)
return NULL;
for (int i = 0; i < num; i++)
b->status[i] = KB_OPEN;
b->lock = slock_new();
if (b->lock == NULL)
return NULL;
b->buffer = malloc(sizeof(sws_context_t) * num);
if (b->buffer == NULL)
return NULL;
for (int i = 0; i < num; i++)
{
b->buffer[i].index = i;
b->buffer[i].sws = sws_alloc_context();
b->buffer[i].source = av_frame_alloc();
#if LIBAVUTIL_VERSION_MAJOR > 55
b->buffer[i].hw_source = av_frame_alloc();
#endif
b->buffer[i].target = av_frame_alloc();
b->buffer[i].frame_buf = av_malloc(frame_size);
avpicture_fill((AVPicture*)b->buffer[i].target, (const uint8_t*)b->buffer[i].frame_buf,
PIX_FMT_RGB32, width, height);
}
b->size = num;
b->head = 0;
b->tail = 0;
return b;
}
void swsbuffer_destroy(swsbuffer_t *swsbuffer)
{
if (swsbuffer != NULL) {
slock_free(swsbuffer->lock);
free(swsbuffer->status);
for (int i = 0; i < swsbuffer->size; i++)
{
#if LIBAVUTIL_VERSION_MAJOR > 55
av_frame_free(&swsbuffer->buffer[i].hw_source);
#endif
av_frame_free(&swsbuffer->buffer[i].source);
av_frame_free(&swsbuffer->buffer[i].target);
av_freep(&swsbuffer->buffer[i].frame_buf);
sws_freeContext(swsbuffer->buffer[i].sws);
}
free(swsbuffer->buffer);
free(swsbuffer);
}
}
void swsbuffer_clear(swsbuffer_t *swsbuffer)
{
slock_lock(swsbuffer->lock);
swsbuffer->head = 0;
swsbuffer->tail = 0;
for (int i = 0; i < swsbuffer->size; i++)
swsbuffer->status[i] = KB_OPEN;
slock_unlock(swsbuffer->lock);
}
void swsbuffer_get_open_slot(swsbuffer_t *swsbuffer, sws_context_t **context)
{
slock_lock(swsbuffer->lock);
if (swsbuffer->status[swsbuffer->head] == KB_OPEN)
{
*context = &swsbuffer->buffer[swsbuffer->head];
swsbuffer->status[swsbuffer->head] = KB_IN_PROGRESS;
swsbuffer->head++;
swsbuffer->head %= swsbuffer->size;
}
slock_unlock(swsbuffer->lock);
}
void swsbuffer_return_open_slot(swsbuffer_t *swsbuffer, sws_context_t *context)
{
slock_lock(swsbuffer->lock);
if (swsbuffer->status[context->index] == KB_IN_PROGRESS)
{
swsbuffer->status[context->index] = KB_OPEN;
swsbuffer->head--;
swsbuffer->head %= swsbuffer->size;
}
slock_unlock(swsbuffer->lock);
}
void swsbuffer_open_slot(swsbuffer_t *swsbuffer, sws_context_t *context)
{
slock_lock(swsbuffer->lock);
if (swsbuffer->status[context->index] == KB_FINISHED)
{
swsbuffer->status[context->index] = KB_OPEN;
swsbuffer->tail++;
swsbuffer->tail %= (swsbuffer->size);
}
slock_unlock(swsbuffer->lock);
}
void swsbuffer_get_finished_slot(swsbuffer_t *swsbuffer, sws_context_t **context)
{
slock_lock(swsbuffer->lock);
if (swsbuffer->status[swsbuffer->tail] == KB_FINISHED)
*context = &swsbuffer->buffer[swsbuffer->tail];
slock_unlock(swsbuffer->lock);
}
void swsbuffer_finish_slot(swsbuffer_t *swsbuffer, sws_context_t *context)
{
slock_lock(swsbuffer->lock);
if (swsbuffer->status[context->index] == KB_IN_PROGRESS)
swsbuffer->status[context->index] = KB_FINISHED;
slock_unlock(swsbuffer->lock);
}
bool swsbuffer_has_open_slot(swsbuffer_t *swsbuffer)
{
bool ret = false;
slock_lock(swsbuffer->lock);
if (swsbuffer->status[swsbuffer->head] == KB_OPEN)
ret = true;
slock_unlock(swsbuffer->lock);
return ret;
}
bool swsbuffer_has_finished_slot(swsbuffer_t *swsbuffer)
{
bool ret = false;
slock_lock(swsbuffer->lock);
if (swsbuffer->status[swsbuffer->tail] == KB_FINISHED)
ret = true;
slock_unlock(swsbuffer->lock);
return ret;
}

View File

@ -1,166 +0,0 @@
#ifndef __LIBRETRO_SDK_SWSBUFFER_H__
#define __LIBRETRO_SDK_SWSBUFFER_H__
#include <retro_common_api.h>
#include <boolean.h>
#include <libavutil/frame.h>
#include <libswscale/swscale.h>
#include <retro_inline.h>
#include <retro_miscellaneous.h>
RETRO_BEGIN_DECLS
#ifndef PIX_FMT_RGB32
#define PIX_FMT_RGB32 AV_PIX_FMT_RGB32
#endif
/**
* sws_context
*
* Context object for the sws worker threads.
*
*/
struct sws_context
{
int index;
struct SwsContext *sws;
AVFrame *source;
#if LIBAVUTIL_VERSION_MAJOR > 55
AVFrame *hw_source;
#endif
AVFrame *target;
void *frame_buf;
};
typedef struct sws_context sws_context_t;
/**
* swsbuffer
*
* The swsbuffer is a ring buffer, that can be used as a
* buffer for many workers while keeping the order.
*
* It is thread safe in a sensem that it is designed to work
* with one work coordinator, that allocates work slots for
* workers threads to work on and later collect the work
* product in the same order, as the slots were allocated.
*
*/
struct swsbuffer;
typedef struct swsbuffer swsbuffer_t;
/**
* swsbuffer_create:
* @num : Size of the buffer.
* @frame_size : Size of the target frame.
* @width : Width of the target frame.
* @height : Height of the target frame.
*
* Create a swsbuffer.
*
* Returns: swsbuffer.
*/
swsbuffer_t *swsbuffer_create(size_t num, int frame_size, int width, int height);
/**
* swsbuffer_destroy:
* @swsbuffer : sswsbuffer.
*
* Destory a swsbuffer.
*
* Does also free the buffer allocated with swsbuffer_create().
* User has to shut down any external worker threads that may have
* a reference to this swsbuffer.
*
**/
void swsbuffer_destroy(swsbuffer_t *swsbuffer);
/**
* swsbuffer_clear:
* @swsbuffer : sswsbuffer.
*
* Clears a swsbuffer.
*
**/
void swsbuffer_clear(swsbuffer_t *swsbuffer);
/**
* swsbuffer_get_open_slot:
* @swsbuffer : sswsbuffer.
* @contex : sws context.
*
* Returns the next open context inside the ring buffer
* and it's index. The status of the slot will be marked as
* 'in progress' until slot is marked as finished with
* swsbuffer_finish_slot();
*
**/
void swsbuffer_get_open_slot(swsbuffer_t *swsbuffer, sws_context_t **context);
/**
* swsbuffer_return_open_slot:
* @swsbuffer : sswsbuffer.
* @contex : sws context.
*
* Marks the given sws context that is "in progress" as "open" again.
*
**/
void swsbuffer_return_open_slot(swsbuffer_t *swsbuffer, sws_context_t *context);
/**
* swsbuffer_open_slot:
* @swsbuffer : sswsbuffer.
* @context : sws context.
*
* Sets the status of the given context from "finished" to "open".
* The slot is then available for producers to claim again with swsbuffer_get_open_slot().
**/
void swsbuffer_open_slot(swsbuffer_t *swsbuffer, sws_context_t *context);
/**
* swsbuffer_get_finished_slot:
* @swsbuffer : sswsbuffer.
* @context : sws context.
*
* Returns a reference for the next context inside
* the ring buffer. User needs to use swsbuffer_open_slot()
* to open the slot in the ringbuffer for the next
* work assignment. User is free to re-allocate or
* re-use the context.
*
*/
void swsbuffer_get_finished_slot(swsbuffer_t *swsbuffer, sws_context_t **context);
/**
* swsbuffer_finish_slot:
* @swsbuffer : sswsbuffer.
* @context : sws context.
*
* Sets the status of the given context from "in progress" to "finished".
* This is normally done by a producer. User can then retrieve the finished work
* context by calling swsbuffer_get_finished_slot().
*/
void swsbuffer_finish_slot(swsbuffer_t *swsbuffer, sws_context_t *context);
/**
* swsbuffer_has_open_slot:
* @swsbuffer : sswsbuffer.
*
* Returns true if the buffer has a open slot available.
*/
bool swsbuffer_has_open_slot(swsbuffer_t *swsbuffer);
/**
* swsbuffer_has_finished_slot:
* @swsbuffer : sswsbuffer.
*
* Returns true if the buffers next slot is finished and a
* context available.
*/
bool swsbuffer_has_finished_slot(swsbuffer_t *swsbuffer);
RETRO_END_DECLS
#endif

View File

@ -0,0 +1,244 @@
#include <libavformat/avformat.h>
#include <rthreads/rthreads.h>
#include "video_buffer.h"
enum kbStatus
{
KB_OPEN,
KB_IN_PROGRESS,
KB_FINISHED
};
struct video_buffer
{
video_decoder_context_t *buffer;
enum kbStatus *status;
size_t size;
slock_t *lock;
scond_t *open_cond;
scond_t *finished_cond;
int64_t head;
int64_t tail;
};
video_buffer_t *video_buffer_create(size_t num, int frame_size, int width, int height)
{
video_buffer_t *b = malloc(sizeof (video_buffer_t));
if (!b)
return NULL;
b->lock = NULL;
b->open_cond = NULL;
b->finished_cond = NULL;
b->buffer = NULL;
b->size = num;
b->head = 0;
b->tail = 0;
b->status = malloc(sizeof(enum kbStatus) * num);
if (!b->status)
goto fail;
for (int i = 0; i < num; i++)
b->status[i] = KB_OPEN;
b->lock = slock_new();
b->open_cond = scond_new();
b->finished_cond = scond_new();
if (!b->lock || !b->open_cond || !b->finished_cond)
goto fail;
b->buffer = malloc(sizeof(video_decoder_context_t) * num);
if (!b->buffer)
goto fail;
for (int i = 0; i < num; i++)
{
b->buffer[i].index = i;
b->buffer[i].pts = 0;
b->buffer[i].sws = sws_alloc_context();
b->buffer[i].source = av_frame_alloc();
#if LIBAVUTIL_VERSION_MAJOR > 55
b->buffer[i].hw_source = av_frame_alloc();
#endif
b->buffer[i].target = av_frame_alloc();
b->buffer[i].frame_buf = av_malloc(frame_size);
avpicture_fill((AVPicture*)b->buffer[i].target, (const uint8_t*)b->buffer[i].frame_buf,
PIX_FMT_RGB32, width, height);
if (!b->buffer[i].sws ||
!b->buffer[i].source ||
!b->buffer[i].hw_source ||
!b->buffer[i].target ||
!b->buffer[i].frame_buf)
goto fail;
}
return b;
fail:
video_buffer_destroy(b);
return NULL;
}
void video_buffer_destroy(video_buffer_t *video_buffer)
{
if (!video_buffer)
return;
slock_free(video_buffer->lock);
scond_free(video_buffer->open_cond);
scond_free(video_buffer->finished_cond);
free(video_buffer->status);
if (video_buffer->buffer)
for (int i = 0; i < video_buffer->size; i++)
{
#if LIBAVUTIL_VERSION_MAJOR > 55
av_frame_free(&video_buffer->buffer[i].hw_source);
#endif
av_frame_free(&video_buffer->buffer[i].source);
av_frame_free(&video_buffer->buffer[i].target);
av_freep(&video_buffer->buffer[i].frame_buf);
sws_freeContext(video_buffer->buffer[i].sws);
}
free(video_buffer->buffer);
free(video_buffer);
}
void video_buffer_clear(video_buffer_t *video_buffer)
{
if (!video_buffer)
return;
slock_lock(video_buffer->lock);
scond_signal(video_buffer->open_cond);
scond_signal(video_buffer->finished_cond);
video_buffer->head = 0;
video_buffer->tail = 0;
for (int i = 0; i < video_buffer->size; i++)
video_buffer->status[i] = KB_OPEN;
slock_unlock(video_buffer->lock);
}
void video_buffer_get_open_slot(video_buffer_t *video_buffer, video_decoder_context_t **context)
{
slock_lock(video_buffer->lock);
if (video_buffer->status[video_buffer->head] == KB_OPEN)
{
*context = &video_buffer->buffer[video_buffer->head];
video_buffer->status[video_buffer->head] = KB_IN_PROGRESS;
video_buffer->head++;
video_buffer->head %= video_buffer->size;
}
slock_unlock(video_buffer->lock);
}
void video_buffer_return_open_slot(video_buffer_t *video_buffer, video_decoder_context_t *context)
{
slock_lock(video_buffer->lock);
if (video_buffer->status[context->index] == KB_IN_PROGRESS)
{
video_buffer->status[context->index] = KB_OPEN;
video_buffer->head--;
video_buffer->head %= video_buffer->size;
}
slock_unlock(video_buffer->lock);
}
void video_buffer_open_slot(video_buffer_t *video_buffer, video_decoder_context_t *context)
{
slock_lock(video_buffer->lock);
if (video_buffer->status[context->index] == KB_FINISHED)
{
video_buffer->status[context->index] = KB_OPEN;
video_buffer->tail++;
video_buffer->tail %= (video_buffer->size);
scond_signal(video_buffer->open_cond);
}
slock_unlock(video_buffer->lock);
}
void video_buffer_get_finished_slot(video_buffer_t *video_buffer, video_decoder_context_t **context)
{
slock_lock(video_buffer->lock);
if (video_buffer->status[video_buffer->tail] == KB_FINISHED)
*context = &video_buffer->buffer[video_buffer->tail];
slock_unlock(video_buffer->lock);
}
void video_buffer_finish_slot(video_buffer_t *video_buffer, video_decoder_context_t *context)
{
slock_lock(video_buffer->lock);
if (video_buffer->status[context->index] == KB_IN_PROGRESS)
{
video_buffer->status[context->index] = KB_FINISHED;
scond_signal(video_buffer->finished_cond);
}
slock_unlock(video_buffer->lock);
}
bool video_buffer_wait_for_open_slot(video_buffer_t *video_buffer)
{
slock_lock(video_buffer->lock);
while (video_buffer->status[video_buffer->head] != KB_OPEN)
scond_wait(video_buffer->open_cond, video_buffer->lock);
slock_unlock(video_buffer->lock);
return true;
}
bool video_buffer_wait_for_finished_slot(video_buffer_t *video_buffer)
{
slock_lock(video_buffer->lock);
while (video_buffer->status[video_buffer->tail] != KB_FINISHED)
scond_wait(video_buffer->finished_cond, video_buffer->lock);
slock_unlock(video_buffer->lock);
return true;
}
bool video_buffer_has_open_slot(video_buffer_t *video_buffer)
{
bool ret = false;
slock_lock(video_buffer->lock);
if (video_buffer->status[video_buffer->head] == KB_OPEN)
ret = true;
slock_unlock(video_buffer->lock);
return ret;
}
bool video_buffer_has_finished_slot(video_buffer_t *video_buffer)
{
bool ret = false;
slock_lock(video_buffer->lock);
if (video_buffer->status[video_buffer->tail] == KB_FINISHED)
ret = true;
slock_unlock(video_buffer->lock);
return ret;
}

View File

@ -0,0 +1,196 @@
#ifndef __LIBRETRO_SDK_SWSBUFFER_H__
#define __LIBRETRO_SDK_SWSBUFFER_H__
#include <retro_common_api.h>
#include <boolean.h>
#include <stdint.h>
#ifdef HAVE_SSA
#include <ass/ass.h>
#endif
#include <libavutil/frame.h>
#include <libswscale/swscale.h>
#include <retro_miscellaneous.h>
RETRO_BEGIN_DECLS
#ifndef PIX_FMT_RGB32
#define PIX_FMT_RGB32 AV_PIX_FMT_RGB32
#endif
/**
* video_decoder_context
*
* Context object for the sws worker threads.
*
*/
struct video_decoder_context
{
int index;
int64_t pts;
struct SwsContext *sws;
AVFrame *source;
#if LIBAVUTIL_VERSION_MAJOR > 55
AVFrame *hw_source;
#endif
AVFrame *target;
#ifdef HAVE_SSA
ASS_Track *ass_track_active;
#endif
uint8_t *frame_buf;
};
typedef struct video_decoder_context video_decoder_context_t;
/**
* video_buffer
*
* The video_buffer is a ring buffer, that can be used as a
* buffer for many workers while keeping the order.
*
* It is thread safe in a sensem that it is designed to work
* with one work coordinator, that allocates work slots for
* workers threads to work on and later collect the work
* product in the same order, as the slots were allocated.
*
*/
struct video_buffer;
typedef struct video_buffer video_buffer_t;
/**
* video_buffer_create:
* @num : Size of the buffer.
* @frame_size : Size of the target frame.
* @width : Width of the target frame.
* @height : Height of the target frame.
*
* Create a video_buffer.
*
* Returns: A video buffer.
*/
video_buffer_t *video_buffer_create(size_t num, int frame_size, int width, int height);
/**
* video_buffer_destroy:
* @video_buffer : video buffer.
*
* Destory a video_buffer.
*
* Does also free the buffer allocated with video_buffer_create().
* User has to shut down any external worker threads that may have
* a reference to this video_buffer.
*
**/
void video_buffer_destroy(video_buffer_t *video_buffer);
/**
* video_buffer_clear:
* @video_buffer : video buffer.
*
* Clears a video_buffer.
*
**/
void video_buffer_clear(video_buffer_t *video_buffer);
/**
* video_buffer_get_open_slot:
* @video_buffer : video buffer.
* @contex : sws context.
*
* Returns the next open context inside the ring buffer
* and it's index. The status of the slot will be marked as
* 'in progress' until slot is marked as finished with
* video_buffer_finish_slot();
*
**/
void video_buffer_get_open_slot(video_buffer_t *video_buffer, video_decoder_context_t **context);
/**
* video_buffer_return_open_slot:
* @video_buffer : video buffer.
* @contex : sws context.
*
* Marks the given sws context that is "in progress" as "open" again.
*
**/
void video_buffer_return_open_slot(video_buffer_t *video_buffer, video_decoder_context_t *context);
/**
* video_buffer_open_slot:
* @video_buffer : video buffer.
* @context : sws context.
*
* Sets the status of the given context from "finished" to "open".
* The slot is then available for producers to claim again with video_buffer_get_open_slot().
**/
void video_buffer_open_slot(video_buffer_t *video_buffer, video_decoder_context_t *context);
/**
* video_buffer_get_finished_slot:
* @video_buffer : video buffer.
* @context : sws context.
*
* Returns a reference for the next context inside
* the ring buffer. User needs to use video_buffer_open_slot()
* to open the slot in the ringbuffer for the next
* work assignment. User is free to re-allocate or
* re-use the context.
*
*/
void video_buffer_get_finished_slot(video_buffer_t *video_buffer, video_decoder_context_t **context);
/**
* video_buffer_finish_slot:
* @video_buffer : video buffer.
* @context : sws context.
*
* Sets the status of the given context from "in progress" to "finished".
* This is normally done by a producer. User can then retrieve the finished work
* context by calling video_buffer_get_finished_slot().
*/
void video_buffer_finish_slot(video_buffer_t *video_buffer, video_decoder_context_t *context);
/**
* video_buffer_wait_for_open_slot:
* @video_buffer : video buffer.
*
* Blocks until open slot is available.
*
* Returns true if the buffer has a open slot available.
*/
bool video_buffer_wait_for_open_slot(video_buffer_t *video_buffer);
/**
* video_buffer_wait_for_finished_slot:
* @video_buffer : video buffer.
*
* Blocks until finished slot is available.
*
* Returns true if the buffers next slot is finished and a
* context available.
*/
bool video_buffer_wait_for_finished_slot(video_buffer_t *video_buffer);
/**
* bool video_buffer_has_open_slot(video_buffer_t *video_buffer)
:
* @video_buffer : video buffer.
*
* Returns true if the buffer has a open slot available.
*/
bool video_buffer_has_open_slot(video_buffer_t *video_buffer);
/**
* video_buffer_has_finished_slot:
* @video_buffer : video buffer.
*
* Returns true if the buffers next slot is finished and a
* context available.
*/
bool video_buffer_has_finished_slot(video_buffer_t *video_buffer);
RETRO_END_DECLS
#endif