diff --git a/Makefile.common b/Makefile.common index 5f10eaa0c3..ff9c38678a 100644 --- a/Makefile.common +++ b/Makefile.common @@ -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) diff --git a/cores/libretro-ffmpeg/Makefile.common b/cores/libretro-ffmpeg/Makefile.common index 75ee087a64..9d92187724 100644 --- a/cores/libretro-ffmpeg/Makefile.common +++ b/cores/libretro-ffmpeg/Makefile.common @@ -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 diff --git a/cores/libretro-ffmpeg/ffmpeg_core.c b/cores/libretro-ffmpeg/ffmpeg_core.c index 330a51dc6d..7e93f035ca 100644 --- a/cores/libretro-ffmpeg/ffmpeg_core.c +++ b/cores/libretro-ffmpeg/ffmpeg_core.c @@ -50,7 +50,7 @@ extern "C" { #include #include #include -#include "swsbuffer.h" +#include "video_buffer.h" #include #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 ); } diff --git a/cores/libretro-ffmpeg/swsbuffer.c b/cores/libretro-ffmpeg/swsbuffer.c deleted file mode 100644 index d480c91c3b..0000000000 --- a/cores/libretro-ffmpeg/swsbuffer.c +++ /dev/null @@ -1,185 +0,0 @@ -#include - -#include - -#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; -} diff --git a/cores/libretro-ffmpeg/swsbuffer.h b/cores/libretro-ffmpeg/swsbuffer.h deleted file mode 100644 index 1e44d17521..0000000000 --- a/cores/libretro-ffmpeg/swsbuffer.h +++ /dev/null @@ -1,166 +0,0 @@ -#ifndef __LIBRETRO_SDK_SWSBUFFER_H__ -#define __LIBRETRO_SDK_SWSBUFFER_H__ - -#include - -#include - -#include -#include - -#include -#include - -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 diff --git a/cores/libretro-ffmpeg/video_buffer.c b/cores/libretro-ffmpeg/video_buffer.c new file mode 100644 index 0000000000..d58bfb7559 --- /dev/null +++ b/cores/libretro-ffmpeg/video_buffer.c @@ -0,0 +1,244 @@ +#include + +#include + +#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; +} diff --git a/cores/libretro-ffmpeg/video_buffer.h b/cores/libretro-ffmpeg/video_buffer.h new file mode 100644 index 0000000000..6939835950 --- /dev/null +++ b/cores/libretro-ffmpeg/video_buffer.h @@ -0,0 +1,196 @@ +#ifndef __LIBRETRO_SDK_SWSBUFFER_H__ +#define __LIBRETRO_SDK_SWSBUFFER_H__ + +#include + +#include +#include + +#ifdef HAVE_SSA +#include +#endif + +#include +#include + +#include + +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