summaryrefslogtreecommitdiff
path: root/src/codec/ffmpeg
diff options
context:
space:
mode:
Diffstat (limited to 'src/codec/ffmpeg')
-rw-r--r--src/codec/ffmpeg/common.c28
-rw-r--r--src/codec/ffmpeg/common.h4
-rw-r--r--src/codec/ffmpeg/decoder.c143
-rw-r--r--src/codec/ffmpeg/decoder.h1
-rw-r--r--src/codec/ffmpeg/demuxer.c1
5 files changed, 121 insertions, 56 deletions
diff --git a/src/codec/ffmpeg/common.c b/src/codec/ffmpeg/common.c
index e697a00..59f59b7 100644
--- a/src/codec/ffmpeg/common.c
+++ b/src/codec/ffmpeg/common.c
@@ -65,3 +65,31 @@ void camu_ff_common_init(void)
#endif
camu_ff_set_log_callback(av_log_callback);
}
+
+AVCodecContext *camu_ff_alloc_codec_context(const AVCodec *codec, AVCodecParameters *codecpar)
+{
+ AVCodecContext *codec_context = avcodec_alloc_context3(codec);
+ if (!codec_context) {
+ log_error("Failed to alloc codec context.");
+ return NULL;
+ }
+
+ s32 ret = avcodec_parameters_to_context(codec_context, codecpar);
+ if (ret < 0) {
+ log_error("Failed to copy codec parameters (%s).", av_err2str(ret));
+ return NULL;
+ }
+
+ return codec_context;
+}
+
+bool camu_ff_open_avcodec(AVCodecContext *codec_context, const AVCodec *codec, AVDictionary *opts)
+{
+ s32 ret = avcodec_open2(codec_context, codec, &opts);
+ if (ret < 0) {
+ log_error("Failed to open codec %s (%s).", codec->name, av_err2str(ret));
+ return false;
+ }
+ av_dict_free(&opts);
+ return true;
+}
diff --git a/src/codec/ffmpeg/common.h b/src/codec/ffmpeg/common.h
index 639ca38..26f807d 100644
--- a/src/codec/ffmpeg/common.h
+++ b/src/codec/ffmpeg/common.h
@@ -2,9 +2,13 @@
#include <al/types.h>
#include <libavutil/frame.h>
+#include <libavcodec/avcodec.h>
s64 camu_ff_frame_duration(AVFrame *frame);
// userdata, level, fmt, args
void camu_ff_set_log_callback(void (*callback)(void *, int, const char *, va_list));
void camu_ff_common_init(void);
+
+AVCodecContext *camu_ff_alloc_codec_context(const AVCodec *codec, AVCodecParameters *codecpar);
+bool camu_ff_open_avcodec(AVCodecContext *codec_context, const AVCodec *codec, AVDictionary *opts);
diff --git a/src/codec/ffmpeg/decoder.c b/src/codec/ffmpeg/decoder.c
index 7d50bca..3da0027 100644
--- a/src/codec/ffmpeg/decoder.c
+++ b/src/codec/ffmpeg/decoder.c
@@ -4,8 +4,11 @@
#include <libavutil/cpu.h>
#include "decoder.h"
+#include "common.h"
#ifdef CAMU_FF_DECODER_HWACCEL
+// If hwaccel throughput is worse then expected on linux, test with
+// your GPU set to "highest clocks"/power_dpm_force_performance_level = high.
#include "../../render/renderer.h"
static const char *hwdevices[] = {
#ifdef CAMU_HAVE_VULKAN_DECODE
@@ -23,31 +26,14 @@ static const char *hwdevices[] = {
};
#endif
-static bool alloc_codec_context_internal(struct camu_ff_decoder *av, const AVCodec *codec, AVCodecParameters *codecpar)
+static void log_codec_name(const AVCodec *codec, const char *hwdevice_name)
{
- if (!(av->codec_context = avcodec_alloc_context3(codec))) {
- log_error("Failed to alloc codec context.");
- return false;
- }
-
- if (avcodec_parameters_to_context(av->codec_context, codecpar) < 0) {
- log_error("Failed to copy codec parameters.");
- return false;
+ const char *name = codec->long_name ? codec->long_name : codec->name;
+ if (hwdevice_name) {
+ log_info("Codec: %s (via %s hwaccel).", name, hwdevice_name);
+ } else {
+ log_info("Codec: %s.", name);
}
-
- return true;
-}
-
-static bool open_avcodec_internal(struct camu_ff_decoder *av, const AVCodec *codec)
-{
- AVDictionary *opts = NULL;
- s32 ret = avcodec_open2(av->codec_context, codec, &opts);
- if (ret < 0) {
- log_error("Failed to open codec %s (%s).", codec->name, av_err2str(ret));
- return false;
- }
- av_dict_free(&opts);
- return true;
}
#ifdef CAMU_FF_DECODER_HWACCEL
@@ -93,39 +79,47 @@ static s32 init_hwframe_context(struct camu_ff_decoder *av, AVCodecContext *cont
return ret;
}
+static bool fallback_to_sw_codec(struct camu_ff_decoder *av, const AVCodec *codec)
+{
+ log_warn("Attempting software decoding fallback...");
+ // Remake AVCodecContext as a software decoder.
+ AVCodecParameters *codecpar = av->codecpar;
+ av->errored_hw_context = av->codec_context;
+ av->codec_context = camu_ff_alloc_codec_context(codec, codecpar);
+ if (!av->codec_context) {
+ return false;
+ }
+ // Note about thread_type choice in ff_decoder_init().
+ av->codec_context->thread_type = FF_THREAD_FRAME;
+ av->codec_context->thread_count = av->thread_count;
+ if (!camu_ff_open_avcodec(av->codec_context, codec, NULL)) {
+ return false;
+ }
+ log_codec_name(codec, NULL);
+ log_info("Using software fallback with %i threads for decoder.", av->thread_count);
+ return true;
+}
+
static enum AVPixelFormat get_hw_format(AVCodecContext *context, const enum AVPixelFormat *pix_fmts)
{
struct camu_ff_decoder *av = (struct camu_ff_decoder *)context->opaque;
- const AVCodec *codec = av->codec_context->codec;
- const char *hwdevice_name = av_hwdevice_get_type_name(av->hw_device_type);
-
const enum AVPixelFormat *fmt = pix_fmts;
for (; *fmt != AV_PIX_FMT_NONE; fmt++) {
if (*fmt == av->hw_pix_fmt) {
if (av->use_frames_context) {
init_hwframe_context(av, av->codec_context, av->hw_context);
}
- log_info("Using %s hardware decoding.", hwdevice_name);
- goto out;
+ return *fmt;
}
}
+ const char *hwdevice_name = av_hwdevice_get_type_name(av->hw_device_type);
log_error("Failed to get %s HW surface format.", hwdevice_name);
- // Remake AVCodecContext as a software decoder.
- AVCodecParameters *codecpar = av->codecpar;
- av->errored_hw_context = av->codec_context;
- alloc_codec_context_internal(av, codec, codecpar);
- // Note about thread_type choice in ff_decoder_init().
- av->codec_context->thread_type = FF_THREAD_FRAME;
- av->codec_context->thread_count = av->thread_count;
- if (open_avcodec_internal(av, codec)) {
- log_info("Software fallback, using %i threads for decoder.", av->thread_count);
- }
+ fallback_to_sw_codec(av, av->sw_codec);
-out:
- return *fmt;
+ return AV_PIX_FMT_NONE;
}
static s32 init_hwdevice_context(struct camu_ff_decoder *av, AVCodecContext *context)
@@ -137,14 +131,17 @@ static s32 init_hwdevice_context(struct camu_ff_decoder *av, AVCodecContext *con
if (av->hw_device_type == AV_HWDEVICE_TYPE_D3D11VA) {
log_info("av_hwdevice_ctx_create() returns.");
}
+ const char *hwdevice_name = av_hwdevice_get_type_name(av->hw_device_type);
if (ret < 0) {
- log_error("Failed to create specified HW device.");
+ log_error("Failed to create %s HW device.", hwdevice_name);
return ret;
}
context->hw_device_ctx = av_buffer_ref(av->hw_context);
+#ifdef CAMU_HUGE_VIDEO_BUFFER
// Note that context->extra_hw_frames has the ability to cause corruption.
- context->extra_hw_frames = 12;
+ context->extra_hw_frames = 48;
+#endif
return ret;
}
@@ -188,8 +185,12 @@ static bool get_hwdevice_config(struct camu_ff_decoder *av, const AVCodec *codec
return true;
}
}
- const char *hwdevice_name = av_hwdevice_get_type_name(hw_device_type);
- log_warn("No hw_config for device type %s (type: %d).", hwdevice_name, hw_device_type);
+ if (hw_device_type == AV_HWDEVICE_TYPE_NONE) {
+ log_warn("No hw_config for codec %s.", codec->name);
+ } else {
+ const char *hwdevice_name = av_hwdevice_get_type_name(hw_device_type);
+ log_warn("No hw_config for device type %s.", hwdevice_name);
+ }
return false;
}
@@ -243,10 +244,21 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend
log_error("Failed to find decoder.");
return false;
}
+#ifdef CAMU_FF_DECODER_HWACCEL
+ av->sw_codec = codec;
+ // @TODO: How do you correctly probe avcodec_get_hw_config() if find_decoder()
+ // always returns libdav1d first?
+ if (al_strscmp(codec->name, "libdav1d") == 0) {
+ if (!(codec = avcodec_find_decoder_by_name("av1"))) {
+ codec = av->sw_codec;
+ }
+ }
+#endif
bool is_video = codecpar->codec_type == AVMEDIA_TYPE_VIDEO && stream->duration > 0;
#ifdef CAMU_FF_DECODER_HWACCEL
+ bool hw_codec_selected = false;
if (is_video) {
collect_supported_hwaccels(av);
av->hw_device_type = AV_HWDEVICE_TYPE_NONE;
@@ -254,21 +266,22 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend
for (u32 i = 0; i < ARRAY_SIZE(hwdevices); i++) {
size_t length = sizeof(hwdevices[i]) - 1;
av->hw_device_type = hw_device_supported_by_name(av, hwdevices[i], length);
- if (!get_hwdevice_config(av, codec, av->hw_device_type)) {
- av->hw_device_type = AV_HWDEVICE_TYPE_NONE;
- } else {
+ if (get_hwdevice_config(av, codec, av->hw_device_type)) {
break;
+ } else {
+ av->hw_device_type = AV_HWDEVICE_TYPE_NONE;
}
}
if (av->hw_device_type == AV_HWDEVICE_TYPE_NONE) {
const AVCodec *hw_codec;
al_array_foreach(av->supported_hw_codecs, i, hw_codec) {
if (hw_codec->id == codecpar->codec_id) {
- // @TODO: hw_codec's without a hwdevice (v4l2m2m, cuvid, amf).
+ // @TODO: hw_codecs without a hwdevice (v4l2m2m, cuvid, amf).
size_t length = al_strlen(hw_codec->wrapper_name);
av->hw_device_type = hw_device_supported_by_name(av, hw_codec->wrapper_name, length);
if (get_hwdevice_config(av, hw_codec, av->hw_device_type)) {
codec = hw_codec;
+ hw_codec_selected = true;
break;
}
av->hw_device_type = AV_HWDEVICE_TYPE_NONE;
@@ -281,7 +294,8 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend
}
#endif
- if (!alloc_codec_context_internal(av, codec, codecpar)) {
+ av->codec_context = camu_ff_alloc_codec_context(codec, codecpar);
+ if (!av->codec_context) {
return false;
}
@@ -302,14 +316,18 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend
#endif
if (is_video) {
- av->thread_count = MIN(8, MAX(1, av_cpu_count() / 2));
+#ifdef CAMU_HUGE_VIDEO_BUFFER
+ av->thread_count = av_cpu_count() * 2;
+#else
+ av->thread_count = MIN(12, MAX(1, av_cpu_count()));
+#endif
#ifdef CAMU_FF_DECODER_HWACCEL
- if (av->hw_device_type == AV_HWDEVICE_TYPE_NONE) {
+ if (!(hw_codec_selected || av->hw_device_type != AV_HWDEVICE_TYPE_NONE)) {
#endif
// FF_THREAD_FRAME or FF_THREAD_SLICE.
// Both modes can get choked on certain videos, higher thread_count with
// FRAME seems like the safest default.
- // @TODO: Check ffmpeg source, SLICE + FRAME?
+ // @TODO: Check ffmpeg source, is SLICE + FRAME real?
// - https://ffmpeg.org/ffmpeg-codecs.html#Codec-Options
av->codec_context->thread_type = FF_THREAD_FRAME;
av->codec_context->thread_count = av->thread_count;
@@ -322,13 +340,26 @@ static bool ff_decoder_init(struct camu_decoder *dec, struct camu_renderer *rend
//av->codec_context->flags |= AV_CODEC_FLAG_BITEXACT;
//av->codec_context->flags2 |= AV_CODEC_FLAG2_FAST;
- if (!open_avcodec_internal(av, codec)) {
+ if (!camu_ff_open_avcodec(av->codec_context, codec, NULL)) {
+#ifdef CAMU_FF_DECODER_HWACCEL
+ if (hw_codec_selected && fallback_to_sw_codec(av, av->sw_codec)) {
+ codec = av->sw_codec;
+ hw_codec_selected = false;
+ } else {
+ return false;
+ }
+#else
return false;
+#endif
}
- log_info("Codec: %s (%s) %lldkbps.", codec->name,
- codec->long_name ? codec->long_name : codec->name,
- (av->codec_context->bit_rate > 0) ? av->codec_context->bit_rate / 1000 : 0);
+ const char *hwdevice_name = NULL;
+#ifdef CAMU_FF_DECODER_HWACCEL
+ if (av->hw_device_type != AV_HWDEVICE_TYPE_NONE) {
+ hwdevice_name = av_hwdevice_get_type_name(av->hw_device_type);
+ }
+#endif
+ log_codec_name(codec, hwdevice_name);
av->callback = callback;
av->userdata = userdata;
diff --git a/src/codec/ffmpeg/decoder.h b/src/codec/ffmpeg/decoder.h
index 0c3d4d7..a422941 100644
--- a/src/codec/ffmpeg/decoder.h
+++ b/src/codec/ffmpeg/decoder.h
@@ -15,6 +15,7 @@ struct camu_ff_decoder {
s32 thread_count;
#ifdef CAMU_FF_DECODER_HWACCEL
struct camu_renderer *renderer;
+ const AVCodec *sw_codec;
array(const AVCodec *) supported_hw_codecs;
array(enum AVHWDeviceType) supported_hw_devices;
AVBufferRef *hw_context;
diff --git a/src/codec/ffmpeg/demuxer.c b/src/codec/ffmpeg/demuxer.c
index 32068f3..c49ced1 100644
--- a/src/codec/ffmpeg/demuxer.c
+++ b/src/codec/ffmpeg/demuxer.c
@@ -158,6 +158,7 @@ static bool ff_demuxer_init(struct camu_demuxer *demux, struct cch_handle *handl
static bool subscribed_to_index(struct camu_demuxer *demux, s32 index)
{
+ al_assert(index < 64);
return (demux->subscribed >> index) & 1;
}