mirror of
https://github.com/libretro/RetroArch
synced 2025-01-31 15:32:59 +00:00
Merge pull request #9799 from hasenbanck/ffmpeg-hw
FFMPEG Core HW accelerated decoding
This commit is contained in:
commit
dda12da672
@ -19,6 +19,7 @@ extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libavutil/error.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/time.h>
|
||||
#include <libavutil/opt.h>
|
||||
@ -76,17 +77,19 @@ static retro_environment_t CORE_PREFIX(environ_cb);
|
||||
static retro_input_poll_t CORE_PREFIX(input_poll_cb);
|
||||
static retro_input_state_t CORE_PREFIX(input_state_cb);
|
||||
|
||||
#define LOG_ERR(msg) do { \
|
||||
log_cb(RETRO_LOG_ERROR, "[FFmpeg]: " msg "\n"); \
|
||||
} while(0)
|
||||
|
||||
/* FFmpeg context data. */
|
||||
static AVFormatContext *fctx;
|
||||
static AVCodecContext *vctx;
|
||||
static int video_stream;
|
||||
|
||||
static int video_stream_index;
|
||||
static enum AVColorSpace colorspace;
|
||||
|
||||
static enum AVPixelFormat pix_fmt;
|
||||
|
||||
static enum AVHWDeviceType hw_decoder;
|
||||
static bool force_sw_decoder;
|
||||
static int sw_decoder_threads;
|
||||
static bool hw_decoding_enabled;
|
||||
|
||||
#define MAX_STREAMS 8
|
||||
static AVCodecContext *actx[MAX_STREAMS];
|
||||
static AVCodecContext *sctx[MAX_STREAMS];
|
||||
@ -99,7 +102,6 @@ static int subtitle_streams_ptr;
|
||||
|
||||
#ifdef HAVE_SSA
|
||||
/* AAS/SSA subtitles. */
|
||||
|
||||
static ASS_Library *ass;
|
||||
static ASS_Renderer *ass_render;
|
||||
static ASS_Track *ass_track[MAX_STREAMS];
|
||||
@ -176,7 +178,7 @@ static struct
|
||||
{
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
|
||||
|
||||
double interpolate_fps;
|
||||
unsigned sample_rate;
|
||||
|
||||
@ -192,7 +194,7 @@ static void ass_msg_cb(int level, const char *fmt, va_list args, void *data)
|
||||
if (level < 6)
|
||||
{
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
log_cb(RETRO_LOG_INFO, "%s\n", buffer);
|
||||
log_cb(RETRO_LOG_INFO, "[FFMPEG] %s\n", buffer);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -254,7 +256,7 @@ void CORE_PREFIX(retro_get_system_av_info)(struct retro_system_av_info *info)
|
||||
info->timing.sample_rate = actx[0] ? media.sample_rate : 32000.0;
|
||||
|
||||
#ifdef HAVE_GL_FFT
|
||||
if (audio_streams_num > 0 && video_stream < 0)
|
||||
if (audio_streams_num > 0 && video_stream_index < 0)
|
||||
{
|
||||
width = fft_width;
|
||||
height = fft_height;
|
||||
@ -272,6 +274,9 @@ void CORE_PREFIX(retro_get_system_av_info)(struct retro_system_av_info *info)
|
||||
void CORE_PREFIX(retro_set_environment)(retro_environment_t cb)
|
||||
{
|
||||
static const struct retro_variable vars[] = {
|
||||
{ "ffmpeg_hw_decoder", "Use Hardware decoder (restart); auto|off|"
|
||||
"cuda|d3d11va|drm|dxva2|mediacodec|opencl|qsv|vaapi|vdpau|videotoolbox" },
|
||||
{ "ffmpeg_sw_decoder_threads", "Software decoder thread count (restart); 1|2|4|8" },
|
||||
#if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES)
|
||||
{ "ffmpeg_temporal_interp", "Temporal Interpolation; disabled|enabled" },
|
||||
#ifdef HAVE_GL_FFT
|
||||
@ -324,8 +329,10 @@ void CORE_PREFIX(retro_reset)(void)
|
||||
reset_triggered = true;
|
||||
}
|
||||
|
||||
static void check_variables(void)
|
||||
static void check_variables(bool firststart)
|
||||
{
|
||||
struct retro_variable hw_var = {0};
|
||||
struct retro_variable sw_threads_var = {0};
|
||||
struct retro_variable color_var = {0};
|
||||
#if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES)
|
||||
struct retro_variable var = {0};
|
||||
@ -386,6 +393,48 @@ static void check_variables(void)
|
||||
colorspace = AVCOL_SPC_UNSPECIFIED;
|
||||
slock_unlock(decode_thread_lock);
|
||||
}
|
||||
|
||||
if (firststart)
|
||||
{
|
||||
hw_var.key = "ffmpeg_hw_decoder";
|
||||
|
||||
force_sw_decoder = false;
|
||||
hw_decoder = AV_HWDEVICE_TYPE_NONE;
|
||||
|
||||
if (CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_GET_VARIABLE, &hw_var) && hw_var.value)
|
||||
{
|
||||
if (string_is_equal(hw_var.value, "off"))
|
||||
force_sw_decoder = true;
|
||||
else if (string_is_equal(hw_var.value, "cuda"))
|
||||
hw_decoder = AV_HWDEVICE_TYPE_CUDA;
|
||||
else if (string_is_equal(hw_var.value, "d3d11va"))
|
||||
hw_decoder = AV_HWDEVICE_TYPE_D3D11VA;
|
||||
else if (string_is_equal(hw_var.value, "drm"))
|
||||
hw_decoder = AV_HWDEVICE_TYPE_DRM;
|
||||
else if (string_is_equal(hw_var.value, "dxva2"))
|
||||
hw_decoder = AV_HWDEVICE_TYPE_DXVA2;
|
||||
else if (string_is_equal(hw_var.value, "mediacodec"))
|
||||
hw_decoder = AV_HWDEVICE_TYPE_MEDIACODEC;
|
||||
else if (string_is_equal(hw_var.value, "opencl"))
|
||||
hw_decoder = AV_HWDEVICE_TYPE_OPENCL;
|
||||
else if (string_is_equal(hw_var.value, "qsv"))
|
||||
hw_decoder = AV_HWDEVICE_TYPE_QSV;
|
||||
else if (string_is_equal(hw_var.value, "vaapi"))
|
||||
hw_decoder = AV_HWDEVICE_TYPE_VAAPI;
|
||||
else if (string_is_equal(hw_var.value, "vdpau"))
|
||||
hw_decoder = AV_HWDEVICE_TYPE_VDPAU;
|
||||
else if (string_is_equal(hw_var.value, "videotoolbox"))
|
||||
hw_decoder = AV_HWDEVICE_TYPE_VIDEOTOOLBOX;
|
||||
}
|
||||
}
|
||||
|
||||
sw_threads_var.key = "ffmpeg_sw_decoder_threads";
|
||||
if (CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_GET_VARIABLE, &sw_threads_var) && sw_threads_var.value)
|
||||
{
|
||||
slock_lock(decode_thread_lock);
|
||||
sw_decoder_threads = strtoul(sw_threads_var.value, NULL, 0);
|
||||
slock_unlock(decode_thread_lock);
|
||||
}
|
||||
}
|
||||
|
||||
static void seek_frame(int seek_frames)
|
||||
@ -410,7 +459,7 @@ static void seek_frame(int seek_frames)
|
||||
|
||||
if (seek_frames < 0)
|
||||
{
|
||||
log_cb(RETRO_LOG_INFO, "Resetting PTS.\n");
|
||||
log_cb(RETRO_LOG_INFO, "[FFMPEG] Resetting PTS.\n");
|
||||
frames[0].pts = 0.0;
|
||||
frames[1].pts = 0.0;
|
||||
}
|
||||
@ -448,7 +497,7 @@ void CORE_PREFIX(retro_run)(void)
|
||||
#endif
|
||||
|
||||
if (CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
|
||||
check_variables();
|
||||
check_variables(false);
|
||||
|
||||
#ifdef HAVE_GL_FFT
|
||||
if (fft_width != old_fft_width || fft_height != old_fft_height)
|
||||
@ -581,7 +630,7 @@ void CORE_PREFIX(retro_run)(void)
|
||||
|
||||
if (pts_bias < old_pts_bias - 1.0)
|
||||
{
|
||||
log_cb(RETRO_LOG_INFO, "Resetting PTS (bias).\n");
|
||||
log_cb(RETRO_LOG_INFO, "[FFMPEG] Resetting PTS (bias).\n");
|
||||
frames[0].pts = 0.0;
|
||||
frames[1].pts = 0.0;
|
||||
}
|
||||
@ -596,7 +645,7 @@ void CORE_PREFIX(retro_run)(void)
|
||||
|
||||
min_pts = frame_cnt / media.interpolate_fps + pts_bias;
|
||||
|
||||
if (video_stream >= 0)
|
||||
if (video_stream_index >= 0)
|
||||
{
|
||||
bool dupe = true; /* unused if GL enabled */
|
||||
|
||||
@ -670,7 +719,7 @@ void CORE_PREFIX(retro_run)(void)
|
||||
scond_signal(fifo_decode_cond);
|
||||
slock_unlock(fifo_lock);
|
||||
|
||||
frames[1].pts = av_q2d(fctx->streams[video_stream]->time_base) * pts;
|
||||
frames[1].pts = av_q2d(fctx->streams[video_stream_index]->time_base) * pts;
|
||||
}
|
||||
|
||||
#if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES)
|
||||
@ -753,19 +802,141 @@ void CORE_PREFIX(retro_run)(void)
|
||||
CORE_PREFIX(audio_batch_cb)(audio_buffer, to_read_frames);
|
||||
}
|
||||
|
||||
/* Try to initialize a specific HW decoder defined by type */
|
||||
static enum AVPixelFormat init_hw_decoder(struct AVCodecContext *ctx,
|
||||
const enum AVPixelFormat *pix_fmts,
|
||||
const enum AVHWDeviceType type)
|
||||
{
|
||||
int ret;
|
||||
enum AVPixelFormat decoder_pix_fmt = AV_PIX_FMT_NONE;
|
||||
struct AVCodec *codec = avcodec_find_decoder(fctx->streams[video_stream_index]->codec->codec_id);
|
||||
|
||||
for (int i = 0;; i++)
|
||||
{
|
||||
const AVCodecHWConfig *config = avcodec_get_hw_config(codec, i);
|
||||
if (!config)
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Codec %s is not supported by HW video decoder %s.\n",
|
||||
codec->name, av_hwdevice_get_type_name(type));
|
||||
break;
|
||||
}
|
||||
if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
|
||||
config->device_type == type)
|
||||
{
|
||||
log_cb(RETRO_LOG_INFO, "[FFMPEG] Selected HW decoder %s.\n",
|
||||
av_hwdevice_get_type_name(type));
|
||||
log_cb(RETRO_LOG_INFO, "[FFMPEG] Selected HW pixel format %s.\n",
|
||||
av_get_pix_fmt_name(config->pix_fmt));
|
||||
|
||||
enum AVPixelFormat device_pix_fmt = config->pix_fmt;
|
||||
|
||||
/* Look if codec can supports the pix format of the device */
|
||||
for (size_t i = 0; pix_fmts[i] != AV_PIX_FMT_NONE; i++)
|
||||
if (pix_fmts[i] == device_pix_fmt)
|
||||
{
|
||||
decoder_pix_fmt = pix_fmts[i];
|
||||
goto exit;
|
||||
}
|
||||
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Codec %s does not support device pixel format %s.\n",
|
||||
codec->name, av_get_pix_fmt_name(config->pix_fmt));
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
if (decoder_pix_fmt != AV_PIX_FMT_NONE)
|
||||
{
|
||||
AVBufferRef *hw_device_ctx;
|
||||
if ((ret = av_hwdevice_ctx_create(&hw_device_ctx,
|
||||
type, NULL, NULL, 0)) < 0)
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Failed to create specified HW device: %s\n", av_err2str(ret));
|
||||
decoder_pix_fmt = AV_PIX_FMT_NONE;
|
||||
}
|
||||
else
|
||||
ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
|
||||
}
|
||||
|
||||
return decoder_pix_fmt;
|
||||
}
|
||||
|
||||
/* Automatically try to find a suitable HW decoder */
|
||||
static enum AVPixelFormat auto_hw_decoder(AVCodecContext *ctx,
|
||||
const enum AVPixelFormat *pix_fmts)
|
||||
{
|
||||
int ret;
|
||||
enum AVPixelFormat decoder_pix_fmt = AV_PIX_FMT_NONE;
|
||||
enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;
|
||||
|
||||
while((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
|
||||
{
|
||||
decoder_pix_fmt = init_hw_decoder(ctx, pix_fmts, type);
|
||||
if (decoder_pix_fmt != AV_PIX_FMT_NONE)
|
||||
break;
|
||||
}
|
||||
|
||||
return decoder_pix_fmt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback used by ffmpeg to configure the pixelformat to use.
|
||||
* Used to initialize hw decoding if configured and accessible.
|
||||
*/
|
||||
static enum AVPixelFormat get_format(AVCodecContext *ctx,
|
||||
const enum AVPixelFormat *pix_fmts)
|
||||
{
|
||||
/* Look if we can reuse the current context */
|
||||
for (size_t i = 0; pix_fmts[i] != AV_PIX_FMT_NONE; i++)
|
||||
if (pix_fmts[i] == pix_fmt)
|
||||
{
|
||||
return pix_fmt;
|
||||
}
|
||||
|
||||
if (!force_sw_decoder)
|
||||
{
|
||||
if (hw_decoder == AV_HWDEVICE_TYPE_NONE)
|
||||
{
|
||||
pix_fmt = auto_hw_decoder(ctx, pix_fmts);
|
||||
}
|
||||
else
|
||||
pix_fmt = init_hw_decoder(ctx, pix_fmts, hw_decoder);
|
||||
}
|
||||
|
||||
/* Fallback to SW rendering */
|
||||
if (pix_fmt == AV_PIX_FMT_NONE)
|
||||
{
|
||||
log_cb(RETRO_LOG_INFO, "[FFMPEG] Using SW decoding.\n");
|
||||
|
||||
ctx->thread_type = FF_THREAD_FRAME;
|
||||
ctx->thread_count = sw_decoder_threads;
|
||||
|
||||
pix_fmt = fctx->streams[video_stream_index]->codec->pix_fmt;
|
||||
hw_decoding_enabled = false;
|
||||
}
|
||||
else
|
||||
hw_decoding_enabled = true;
|
||||
|
||||
return pix_fmt;
|
||||
}
|
||||
|
||||
static bool open_codec(AVCodecContext **ctx, unsigned index)
|
||||
{
|
||||
AVCodec *codec = avcodec_find_decoder(fctx->streams[index]->codec->codec_id);
|
||||
int ret;
|
||||
|
||||
AVCodec *codec = avcodec_find_decoder(fctx->streams[index]->codec->codec_id);
|
||||
if (!codec)
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "Couldn't find suitable decoder, exiting ... \n");
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Couldn't find suitable decoder\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
*ctx = fctx->streams[index]->codec;
|
||||
if (avcodec_open2(*ctx, codec, NULL) < 0)
|
||||
|
||||
if ((ret = avcodec_open2(*ctx, codec, NULL)) < 0)
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Could not open codec: %s\n", av_err2str(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -831,10 +1002,9 @@ static bool codec_id_is_ass(enum AVCodecID id)
|
||||
static bool open_codecs(void)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
decode_thread_lock = slock_new();
|
||||
|
||||
video_stream = -1;
|
||||
video_stream_index = -1;
|
||||
audio_streams_num = 0;
|
||||
subtitle_streams_num = 0;
|
||||
|
||||
@ -866,7 +1036,9 @@ static bool open_codecs(void)
|
||||
{
|
||||
if (!open_codec(&vctx, i))
|
||||
return false;
|
||||
video_stream = i;
|
||||
pix_fmt = AV_PIX_FMT_NONE;
|
||||
vctx->get_format = get_format;
|
||||
video_stream_index = i;
|
||||
}
|
||||
break;
|
||||
|
||||
@ -994,118 +1166,6 @@ static void set_colorspace(struct SwsContext *sws,
|
||||
}
|
||||
}
|
||||
|
||||
static bool decode_video(AVPacket *pkt, AVFrame *frame,
|
||||
AVFrame *conv, struct SwsContext *sws)
|
||||
{
|
||||
int got_ptr = 0;
|
||||
int ret = avcodec_decode_video2(vctx, frame, &got_ptr, pkt);
|
||||
|
||||
if (ret < 0)
|
||||
return false;
|
||||
|
||||
if (got_ptr)
|
||||
{
|
||||
set_colorspace(sws, media.width, media.height,
|
||||
av_frame_get_colorspace(frame), av_frame_get_color_range(frame));
|
||||
sws_scale(sws, (const uint8_t * const*)frame->data,
|
||||
frame->linesize, 0, media.height,
|
||||
conv->data, conv->linesize);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int16_t *decode_audio(AVCodecContext *ctx, AVPacket *pkt,
|
||||
AVFrame *frame, int16_t *buffer, size_t *buffer_cap,
|
||||
SwrContext *swr)
|
||||
{
|
||||
AVPacket pkt_tmp = *pkt;
|
||||
int got_ptr = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int64_t pts;
|
||||
size_t required_buffer;
|
||||
int ret = avcodec_decode_audio4(ctx, frame, &got_ptr, &pkt_tmp);
|
||||
|
||||
if (ret < 0)
|
||||
return buffer;
|
||||
|
||||
pkt_tmp.data += ret;
|
||||
pkt_tmp.size -= ret;
|
||||
|
||||
if (!got_ptr)
|
||||
break;
|
||||
|
||||
required_buffer = frame->nb_samples * sizeof(int16_t) * 2;
|
||||
if (required_buffer > *buffer_cap)
|
||||
{
|
||||
buffer = (int16_t*)av_realloc(buffer, required_buffer);
|
||||
*buffer_cap = required_buffer;
|
||||
}
|
||||
|
||||
swr_convert(swr,
|
||||
(uint8_t**)&buffer,
|
||||
frame->nb_samples,
|
||||
(const uint8_t**)frame->data,
|
||||
frame->nb_samples);
|
||||
|
||||
pts = av_frame_get_best_effort_timestamp(frame);
|
||||
slock_lock(fifo_lock);
|
||||
|
||||
while (!decode_thread_dead && fifo_write_avail(audio_decode_fifo) < required_buffer)
|
||||
{
|
||||
if (!main_sleeping)
|
||||
scond_wait(fifo_decode_cond, fifo_lock);
|
||||
else
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "Thread: Audio deadlock detected ...\n");
|
||||
fifo_clear(audio_decode_fifo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
decode_last_audio_time = pts * av_q2d(
|
||||
fctx->streams[audio_streams[audio_streams_ptr]]->time_base);
|
||||
|
||||
if (!decode_thread_dead)
|
||||
fifo_write(audio_decode_fifo, buffer, required_buffer);
|
||||
|
||||
scond_signal(fifo_cond);
|
||||
slock_unlock(fifo_lock);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void decode_thread_seek(double time)
|
||||
{
|
||||
int ret;
|
||||
int64_t seek_to = time * AV_TIME_BASE;
|
||||
|
||||
if (seek_to < 0)
|
||||
seek_to = 0;
|
||||
|
||||
decode_last_video_time = time;
|
||||
decode_last_audio_time = time;
|
||||
|
||||
ret = avformat_seek_file(fctx, -1, INT64_MIN, seek_to, INT64_MAX, 0);
|
||||
if (ret < 0)
|
||||
log_cb(RETRO_LOG_ERROR, "av_seek_frame() failed.\n");
|
||||
|
||||
if (actx[audio_streams_ptr])
|
||||
avcodec_flush_buffers(actx[audio_streams_ptr]);
|
||||
if (vctx)
|
||||
avcodec_flush_buffers(vctx);
|
||||
if (sctx[subtitle_streams_ptr])
|
||||
avcodec_flush_buffers(sctx[subtitle_streams_ptr]);
|
||||
#ifdef HAVE_SSA
|
||||
if (ass_track[subtitle_streams_ptr])
|
||||
ass_flush_events(ass_track[subtitle_streams_ptr]);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef HAVE_SSA
|
||||
/* Straight CPU alpha blending.
|
||||
* Should probably do in GL. */
|
||||
@ -1157,27 +1217,237 @@ static void render_ass_img(AVFrame *conv_frame, ASS_Image *img)
|
||||
}
|
||||
#endif
|
||||
|
||||
static void decode_video(AVCodecContext *ctx, AVPacket *pkt, AVFrame *conv_frame, size_t frame_size, struct SwsContext **sws, ASS_Track *ass_track_active)
|
||||
{
|
||||
int ret;
|
||||
AVFrame *frame = NULL;
|
||||
AVFrame *sw_frame = NULL;
|
||||
AVFrame *tmp_frame = NULL;
|
||||
|
||||
if ((ret = avcodec_send_packet(ctx, pkt)) < 0)
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Can't decode video packet: %s\n", av_err2str(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
while(true)
|
||||
{
|
||||
if (!(frame = av_frame_alloc()) || !(sw_frame = av_frame_alloc()))
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Can not alloc frames\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = avcodec_receive_frame(ctx, frame);
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
{
|
||||
av_frame_free(&frame);
|
||||
av_frame_free(&sw_frame);
|
||||
break;
|
||||
}
|
||||
else if (ret < 0)
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Error while reading video frame: %s\n", av_err2str(ret));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (hw_decoding_enabled)
|
||||
{
|
||||
/* Copy data from VRAM to RAM */
|
||||
if ((ret = av_hwframe_transfer_data(sw_frame, frame, 0)) < 0)
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Error transferring the data to system memory: %s\n", av_err2str(ret));
|
||||
goto fail;
|
||||
}
|
||||
tmp_frame = sw_frame;
|
||||
}
|
||||
else
|
||||
tmp_frame = frame;
|
||||
|
||||
*sws = sws_getCachedContext(*sws,
|
||||
media.width, media.height, tmp_frame->format,
|
||||
media.width, media.height, PIX_FMT_RGB32,
|
||||
SWS_BICUBIC, NULL, NULL, NULL);
|
||||
|
||||
set_colorspace(*sws, media.width, media.height,
|
||||
av_frame_get_colorspace(tmp_frame), av_frame_get_color_range(tmp_frame));
|
||||
|
||||
if ((ret = sws_scale(*sws, (const uint8_t * const*)tmp_frame->data,
|
||||
tmp_frame->linesize, 0, media.height,
|
||||
conv_frame->data, conv_frame->linesize)) < 0)
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Error while scaling image: %s\n", av_err2str(ret));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
size_t decoded_size;
|
||||
int64_t pts = av_frame_get_best_effort_timestamp(frame);
|
||||
double video_time = pts * av_q2d(fctx->streams[video_stream_index]->time_base);
|
||||
|
||||
#ifdef HAVE_SSA
|
||||
if (ass_render && ass_track_active)
|
||||
{
|
||||
int change = 0;
|
||||
ASS_Image *img = ass_render_frame(ass_render, 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(conv_frame, 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 = conv_frame->data[0];
|
||||
stride = conv_frame->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);
|
||||
|
||||
fail:
|
||||
av_frame_free(&frame);
|
||||
av_frame_free(&sw_frame);
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int16_t *decode_audio(AVCodecContext *ctx, AVPacket *pkt,
|
||||
AVFrame *frame, int16_t *buffer, size_t *buffer_cap,
|
||||
SwrContext *swr)
|
||||
{
|
||||
int ret;
|
||||
int64_t pts;
|
||||
size_t required_buffer;
|
||||
|
||||
if ((ret = avcodec_send_packet(ctx, pkt)) < 0)
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Can't decode audio packet: %s\n", av_err2str(ret));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
while(true)
|
||||
{
|
||||
ret = avcodec_receive_frame(ctx, frame);
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (ret < 0)
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Error while reading audio frame: %s\n", av_err2str(ret));
|
||||
break;
|
||||
}
|
||||
|
||||
required_buffer = frame->nb_samples * sizeof(int16_t) * 2;
|
||||
if (required_buffer > *buffer_cap)
|
||||
{
|
||||
buffer = (int16_t*)av_realloc(buffer, required_buffer);
|
||||
*buffer_cap = required_buffer;
|
||||
}
|
||||
|
||||
swr_convert(swr,
|
||||
(uint8_t**)&buffer,
|
||||
frame->nb_samples,
|
||||
(const uint8_t**)frame->data,
|
||||
frame->nb_samples);
|
||||
|
||||
pts = av_frame_get_best_effort_timestamp(frame);
|
||||
slock_lock(fifo_lock);
|
||||
|
||||
while (!decode_thread_dead && fifo_write_avail(audio_decode_fifo) < required_buffer)
|
||||
{
|
||||
if (!main_sleeping)
|
||||
scond_wait(fifo_decode_cond, fifo_lock);
|
||||
else
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Thread: Audio deadlock detected.\n");
|
||||
fifo_clear(audio_decode_fifo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
decode_last_audio_time = pts * av_q2d(
|
||||
fctx->streams[audio_streams[audio_streams_ptr]]->time_base);
|
||||
|
||||
if (!decode_thread_dead)
|
||||
fifo_write(audio_decode_fifo, buffer, required_buffer);
|
||||
|
||||
scond_signal(fifo_cond);
|
||||
slock_unlock(fifo_lock);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
static void decode_thread_seek(double time)
|
||||
{
|
||||
int64_t seek_to = time * AV_TIME_BASE;
|
||||
|
||||
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)
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] av_seek_frame() failed.\n");
|
||||
|
||||
if (actx[audio_streams_ptr])
|
||||
avcodec_flush_buffers(actx[audio_streams_ptr]);
|
||||
if (vctx)
|
||||
avcodec_flush_buffers(vctx);
|
||||
if (sctx[subtitle_streams_ptr])
|
||||
avcodec_flush_buffers(sctx[subtitle_streams_ptr]);
|
||||
#ifdef HAVE_SSA
|
||||
if (ass_track[subtitle_streams_ptr])
|
||||
ass_flush_events(ass_track[subtitle_streams_ptr]);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void decode_thread(void *data)
|
||||
{
|
||||
unsigned i;
|
||||
SwrContext *swr[audio_streams_num];
|
||||
struct SwrContext *swr[audio_streams_num];
|
||||
struct SwsContext *sws = NULL;
|
||||
AVFrame *aud_frame = NULL;
|
||||
AVFrame *vid_frame = NULL;
|
||||
void *conv_frame_buf = NULL;
|
||||
size_t frame_size = 0;
|
||||
int16_t *audio_buffer = NULL;
|
||||
size_t audio_buffer_cap = 0;
|
||||
AVFrame *conv_frame = NULL;
|
||||
struct SwsContext *sws = NULL;
|
||||
|
||||
|
||||
(void)data;
|
||||
|
||||
if (video_stream >= 0)
|
||||
sws = sws_getCachedContext(NULL,
|
||||
media.width, media.height, vctx->pix_fmt,
|
||||
media.width, media.height, PIX_FMT_RGB32,
|
||||
SWS_POINT, NULL, NULL, NULL);
|
||||
|
||||
for (i = 0; (int)i < audio_streams_num; i++)
|
||||
{
|
||||
swr[i] = swr_alloc();
|
||||
@ -1192,9 +1462,8 @@ static void decode_thread(void *data)
|
||||
}
|
||||
|
||||
aud_frame = av_frame_alloc();
|
||||
vid_frame = av_frame_alloc();
|
||||
|
||||
if (video_stream >= 0)
|
||||
if (video_stream_index >= 0)
|
||||
{
|
||||
frame_size = avpicture_get_size(PIX_FMT_RGB32, media.width, media.height);
|
||||
conv_frame = av_frame_alloc();
|
||||
@ -1253,65 +1522,13 @@ static void decode_thread(void *data)
|
||||
#endif
|
||||
slock_unlock(decode_thread_lock);
|
||||
|
||||
if (pkt.stream_index == video_stream)
|
||||
{
|
||||
if (decode_video(&pkt, vid_frame, conv_frame, sws))
|
||||
{
|
||||
size_t decoded_size;
|
||||
int64_t pts = av_frame_get_best_effort_timestamp(vid_frame);
|
||||
double video_time = pts * av_q2d(fctx->streams[video_stream]->time_base);
|
||||
|
||||
#ifdef HAVE_SSA
|
||||
if (ass_render && ass_track_active)
|
||||
{
|
||||
int change = 0;
|
||||
ASS_Image *img = ass_render_frame(ass_render, 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(conv_frame, 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 = conv_frame->data[0];
|
||||
stride = conv_frame->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);
|
||||
}
|
||||
}
|
||||
if (pkt.stream_index == video_stream_index)
|
||||
decode_video(vctx, &pkt, conv_frame, frame_size, &sws, ass_track_active);
|
||||
else if (pkt.stream_index == audio_stream && actx_active)
|
||||
{
|
||||
audio_buffer = decode_audio(actx_active, &pkt, aud_frame,
|
||||
audio_buffer, &audio_buffer_cap,
|
||||
swr[audio_stream_ptr]);
|
||||
audio_buffer, &audio_buffer_cap,
|
||||
swr[audio_stream_ptr]);
|
||||
}
|
||||
else if (pkt.stream_index == subtitle_stream && sctx_active)
|
||||
{
|
||||
@ -1324,7 +1541,7 @@ static void decode_thread(void *data)
|
||||
{
|
||||
if (avcodec_decode_subtitle2(sctx_active, &sub, &finished, &pkt) < 0)
|
||||
{
|
||||
log_cb(RETRO_LOG_ERROR, "Decode subtitles failed.\n");
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Decode subtitles failed.\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1344,15 +1561,15 @@ static void decode_thread(void *data)
|
||||
av_free_packet(&pkt);
|
||||
}
|
||||
|
||||
if (sws)
|
||||
sws_freeContext(sws);
|
||||
sws = NULL;
|
||||
sws_freeContext(sws);
|
||||
|
||||
for (i = 0; (int)i < audio_streams_num; i++)
|
||||
swr_free(&swr[i]);
|
||||
|
||||
if (vctx->hw_device_ctx)
|
||||
av_buffer_unref(&vctx->hw_device_ctx);
|
||||
|
||||
av_frame_free(&aud_frame);
|
||||
av_frame_free(&vid_frame);
|
||||
av_frame_free(&conv_frame);
|
||||
av_freep(&conv_frame_buf);
|
||||
av_freep(&audio_buffer);
|
||||
@ -1398,7 +1615,7 @@ static void context_reset(void)
|
||||
unsigned i;
|
||||
|
||||
#ifdef HAVE_GL_FFT
|
||||
if (audio_streams_num > 0 && video_stream < 0)
|
||||
if (audio_streams_num > 0 && video_stream_index < 0)
|
||||
{
|
||||
fft = fft_new(11, hw_render.get_proc_address);
|
||||
if (fft)
|
||||
@ -1555,8 +1772,10 @@ void CORE_PREFIX(retro_unload_game)(void)
|
||||
|
||||
bool CORE_PREFIX(retro_load_game)(const struct retro_game_info *info)
|
||||
{
|
||||
int ret;
|
||||
bool is_fft = false;
|
||||
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
|
||||
|
||||
struct retro_input_descriptor desc[] = {
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Seek -10 seconds" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Seek +60 seconds" },
|
||||
@ -1575,41 +1794,42 @@ bool CORE_PREFIX(retro_load_game)(const struct retro_game_info *info)
|
||||
|
||||
if (!CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
|
||||
{
|
||||
LOG_ERR("Cannot set pixel format.");
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Cannot set pixel format.");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (avformat_open_input(&fctx, info->path, NULL, NULL) < 0)
|
||||
if ((ret = avformat_open_input(&fctx, info->path, NULL, NULL)) < 0)
|
||||
{
|
||||
LOG_ERR("Failed to open input.");
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Failed to open input: %s\n", av_err2str(ret));
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (avformat_find_stream_info(fctx, NULL) < 0)
|
||||
if ((ret = avformat_find_stream_info(fctx, NULL)) < 0)
|
||||
{
|
||||
LOG_ERR("Failed to find stream info.");
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Failed to find stream info: %s\n", av_err2str(ret));
|
||||
goto error;
|
||||
}
|
||||
|
||||
log_cb(RETRO_LOG_INFO, "[FFMPEG] Media information:\n");
|
||||
av_dump_format(fctx, 0, info->path, 0);
|
||||
|
||||
if (!open_codecs())
|
||||
{
|
||||
LOG_ERR("Failed to find codec.");
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Failed to find codec.");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!init_media_info())
|
||||
{
|
||||
LOG_ERR("Failed to init media info.");
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Failed to init media info.");
|
||||
goto error;
|
||||
}
|
||||
|
||||
#ifdef HAVE_GL_FFT
|
||||
is_fft = video_stream < 0 && audio_streams_num > 0;
|
||||
is_fft = video_stream_index < 0 && audio_streams_num > 0;
|
||||
#endif
|
||||
|
||||
if (video_stream >= 0 || is_fft)
|
||||
if (video_stream_index >= 0 || is_fft)
|
||||
{
|
||||
video_decode_fifo = fifo_new(media.width
|
||||
* media.height * sizeof(uint32_t) * 32);
|
||||
@ -1629,13 +1849,13 @@ 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_ERR("Cannot initialize HW render.");
|
||||
log_cb(RETRO_LOG_ERROR, "[FFMPEG] Cannot initialize HW render.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (audio_streams_num > 0)
|
||||
{
|
||||
unsigned buffer_seconds = video_stream >= 0 ? 20 : 1;
|
||||
unsigned buffer_seconds = video_stream_index >= 0 ? 20 : 1;
|
||||
audio_decode_fifo = fifo_new(buffer_seconds * media.sample_rate * sizeof(int16_t) * 2);
|
||||
}
|
||||
|
||||
@ -1647,7 +1867,7 @@ bool CORE_PREFIX(retro_load_game)(const struct retro_game_info *info)
|
||||
decode_thread_dead = false;
|
||||
slock_unlock(fifo_lock);
|
||||
|
||||
check_variables();
|
||||
check_variables(true);
|
||||
|
||||
decode_thread_handle = sthread_create(decode_thread, NULL);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user