Merge branch 'no-video-fifo' of git://github.com/hasenbanck/RetroArch

This commit is contained in:
twinaphex 2019-12-23 19:04:54 +01:00
commit 7aa914fde7
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