recording: add h264/aac, better logging of output formats

This commit is contained in:
Megamouse 2023-10-25 20:30:19 +02:00
parent 434a63a98a
commit 0899723510
5 changed files with 116 additions and 25 deletions

2
3rdparty/ffmpeg vendored

@ -1 +1 @@
Subproject commit 9a2df87789ebfecf64d35d732e5847662fbd5520 Subproject commit 10d0ebc0b8c7c4f0b242c9998c8bdc4e55bb5067

View file

@ -230,7 +230,7 @@ public:
if (src_ch_cnt == static_cast<u32>(AudioChannelCnt::SURROUND_7_1)) if (src_ch_cnt == static_cast<u32>(AudioChannelCnt::SURROUND_7_1))
{ {
if (dst_ch_cnt== static_cast<u32>(AudioChannelCnt::SURROUND_5_1)) if (dst_ch_cnt == static_cast<u32>(AudioChannelCnt::SURROUND_5_1))
{ {
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, AudioChannelCnt::SURROUND_5_1>(sample_cnt, src, dst); AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, AudioChannelCnt::SURROUND_5_1>(sample_cnt, src, dst);
} }

View file

@ -27,7 +27,7 @@ struct cfg_recording final : cfg::node
{ {
node_audio(cfg::node* _this) : cfg::node(_this, "Audio") {} node_audio(cfg::node* _this) : cfg::node(_this, "Audio") {}
cfg::uint<0x10000, 0x17000> audio_codec{this, "AVCodecID", 86019}; // AVCodecID::AV_CODEC_ID_AC3 cfg::uint<0x10000, 0x17000> audio_codec{this, "AVCodecID", 86018}; // AVCodecID::AV_CODEC_ID_AAC
cfg::uint<0, 25000000> audio_bps{this, "Audio Bitrate", 320000}; cfg::uint<0, 25000000> audio_bps{this, "Audio Bitrate", 320000};
} audio{ this }; } audio{ this };

View file

@ -285,9 +285,39 @@ namespace utils
} }
}; };
static std::string channel_layout_name(const AVChannelLayout& ch_layout)
{
std::vector<char> ch_layout_buf(64);
int len = av_channel_layout_describe(&ch_layout, ch_layout_buf.data(), ch_layout_buf.size());
if (len < 0)
{
media_log.error("av_channel_layout_describe failed. Error: %d='%s'", len, av_error_to_string(len));
return {};
}
if (len > static_cast<int>(ch_layout_buf.size()))
{
// Try again with a bigger buffer
media_log.notice("av_channel_layout_describe needs a bigger buffer: len=%d", len);
ch_layout_buf.clear();
ch_layout_buf.resize(len);
len = av_channel_layout_describe(&ch_layout, ch_layout_buf.data(), ch_layout_buf.size());
if (len < 0)
{
media_log.error("av_channel_layout_describe failed. Error: %d='%s'", len, av_error_to_string(len));
return {};
}
}
return ch_layout_buf.data();
}
// check that a given sample format is supported by the encoder // check that a given sample format is supported by the encoder
static bool check_sample_fmt(const AVCodec* codec, enum AVSampleFormat sample_fmt) static bool check_sample_fmt(const AVCodec* codec, enum AVSampleFormat sample_fmt)
{ {
if (!codec) return false;
for (const AVSampleFormat* p = codec->sample_fmts; p && *p != AV_SAMPLE_FMT_NONE; p++) for (const AVSampleFormat* p = codec->sample_fmts; p && *p != AV_SAMPLE_FMT_NONE; p++)
{ {
if (*p == sample_fmt) if (*p == sample_fmt)
@ -301,7 +331,7 @@ namespace utils
// just pick the highest supported samplerate // just pick the highest supported samplerate
static int select_sample_rate(const AVCodec* codec) static int select_sample_rate(const AVCodec* codec)
{ {
if (!codec->supported_samplerates) if (!codec || !codec->supported_samplerates)
return 48000; return 48000;
int best_samplerate = 0; int best_samplerate = 0;
@ -315,21 +345,45 @@ namespace utils
return best_samplerate; return best_samplerate;
} }
// select layout with the highest channel count AVChannelLayout get_preferred_channel_layout(int channels)
{
switch (channels)
{
case 2:
return AV_CHANNEL_LAYOUT_STEREO;
case 6:
return AV_CHANNEL_LAYOUT_5POINT1;
case 8:
return AV_CHANNEL_LAYOUT_7POINT1;
default:
break;
}
return {};
}
static constexpr AVChannelLayout empty_ch_layout = {};
// select layout with the exact channel count
static const AVChannelLayout* select_channel_layout(const AVCodec* codec, int channels) static const AVChannelLayout* select_channel_layout(const AVCodec* codec, int channels)
{ {
constexpr AVChannelLayout empty_ch_layout = {}; if (!codec) return nullptr;
const AVChannelLayout preferred_ch_layout = get_preferred_channel_layout(channels);
const AVChannelLayout* found_ch_layout = nullptr;
for (const AVChannelLayout* ch_layout = codec->ch_layouts; for (const AVChannelLayout* ch_layout = codec->ch_layouts;
ch_layout && memcmp(ch_layout, &empty_ch_layout, sizeof(AVChannelLayout)) != 0; ch_layout && memcmp(ch_layout, &empty_ch_layout, sizeof(AVChannelLayout)) != 0;
ch_layout++) ch_layout++)
{ {
if (ch_layout->nb_channels == channels) media_log.notice("select_channel_layout: listing channel layout '%s' with %d channels", channel_layout_name(*ch_layout), ch_layout->nb_channels);
if (ch_layout->nb_channels == channels && memcmp(ch_layout, &preferred_ch_layout, sizeof(AVChannelLayout)) == 0)
{ {
return ch_layout; found_ch_layout = ch_layout;
} }
} }
return nullptr;
return found_ch_layout;
} }
audio_decoder::audio_decoder() audio_decoder::audio_decoder()
@ -830,26 +884,43 @@ namespace utils
void* opaque = nullptr; void* opaque = nullptr;
for (const AVOutputFormat* oformat = av_muxer_iterate(&opaque); !!oformat; oformat = av_muxer_iterate(&opaque)) for (const AVOutputFormat* oformat = av_muxer_iterate(&opaque); !!oformat; oformat = av_muxer_iterate(&opaque))
{ {
if (avformat_query_codec(oformat, video_codec, FF_COMPLIANCE_STRICT) == 1 && media_log.notice("video_encoder: Listing output format '%s' (video_codec=%d, audio_codec=%d)", oformat->name, static_cast<int>(oformat->video_codec), static_cast<int>(oformat->audio_codec));
avformat_query_codec(oformat, audio_codec, FF_COMPLIANCE_STRICT) == 1) if (avformat_query_codec(oformat, video_codec, FF_COMPLIANCE_NORMAL) == 1 &&
avformat_query_codec(oformat, audio_codec, FF_COMPLIANCE_NORMAL) == 1)
{ {
media_log.notice("video_encoder: Found output format '%s' (video_codec=%d, audio_codec=%d)", oformat->name, static_cast<int>(video_codec), static_cast<int>(audio_codec));
oformats.push_back(oformat); oformats.push_back(oformat);
} }
} }
// Fallback to first found format for (const AVOutputFormat* oformat : oformats)
if (!oformats.empty() && oformats.front())
{ {
const AVOutputFormat* oformat = oformats.front(); if (!oformat) continue;
media_log.notice("video_encoder: Falling back to output format '%s' (video_codec=%d, audio_codec=%d)", oformat->name, static_cast<int>(video_codec), static_cast<int>(audio_codec)); media_log.notice("video_encoder: Found compatible output format '%s' (video_codec=%d, audio_codec=%d)", oformat->name, static_cast<int>(oformat->video_codec), static_cast<int>(oformat->audio_codec));
}
// Select best match
for (const AVOutputFormat* oformat : oformats)
{
if (oformat && oformat->video_codec == video_codec && oformat->audio_codec == audio_codec)
{
media_log.notice("video_encoder: Using matching output format '%s' (video_codec=%d, audio_codec=%d)", oformat->name, static_cast<int>(oformat->video_codec), static_cast<int>(oformat->audio_codec));
return oformat;
}
}
// Fallback to first found format
if (const AVOutputFormat* oformat = oformats.empty() ? nullptr : oformats.front())
{
media_log.notice("video_encoder: Using suboptimal output format '%s' (video_codec=%d, audio_codec=%d)", oformat->name, static_cast<int>(oformat->video_codec), static_cast<int>(oformat->audio_codec));
return oformat; return oformat;
} }
return nullptr; return nullptr;
}; };
const AVOutputFormat* out_format = find_format(static_cast<AVCodecID>(m_video_codec_id), static_cast<AVCodecID>(m_audio_codec_id)); const AVCodecID video_codec = static_cast<AVCodecID>(m_video_codec_id);
const AVCodecID audio_codec = static_cast<AVCodecID>(m_audio_codec_id);
const AVOutputFormat* out_format = find_format(video_codec, audio_codec);
if (out_format) if (out_format)
{ {
@ -901,7 +972,7 @@ namespace utils
return; return;
} }
const auto create_context = [this, &av](AVCodecID codec_id, bool is_video) -> bool const auto create_context = [this, &av](bool is_video) -> bool
{ {
const std::string type = is_video ? "video" : "audio"; const std::string type = is_video ? "video" : "audio";
scoped_av::ctx& ctx = is_video ? av.video : av.audio; scoped_av::ctx& ctx = is_video ? av.video : av.audio;
@ -910,7 +981,7 @@ namespace utils
{ {
if (!(ctx.codec = avcodec_find_encoder(av.format_context->oformat->video_codec))) if (!(ctx.codec = avcodec_find_encoder(av.format_context->oformat->video_codec)))
{ {
media_log.error("video_encoder: avcodec_find_encoder for video failed. video_codev=%d", static_cast<int>(av.format_context->oformat->video_codec)); media_log.error("video_encoder: avcodec_find_encoder for video failed. video_codec=%d", static_cast<int>(av.format_context->oformat->video_codec));
return false; return false;
} }
} }
@ -945,13 +1016,13 @@ namespace utils
return true; return true;
}; };
if (!create_context(static_cast<AVCodecID>(m_video_codec_id), true)) if (!create_context(true))
{ {
has_error = true; has_error = true;
return; return;
} }
if (!create_context(static_cast<AVCodecID>(m_audio_codec_id), false)) if (!create_context(false))
{ {
has_error = true; has_error = true;
return; return;
@ -974,6 +1045,8 @@ namespace utils
{ {
if (const AVChannelLayout* ch_layout = select_channel_layout(av.audio.codec, m_channels)) if (const AVChannelLayout* ch_layout = select_channel_layout(av.audio.codec, m_channels))
{ {
media_log.notice("video_encoder: found channel layout '%s' with %d channels", channel_layout_name(*ch_layout), ch_layout->nb_channels);
if (int err = av_channel_layout_copy(&av.audio.context->ch_layout, ch_layout); err != 0) if (int err = av_channel_layout_copy(&av.audio.context->ch_layout, ch_layout); err != 0)
{ {
media_log.error("video_encoder: av_channel_layout_copy failed. Error: %d='%s'", err, av_error_to_string(err)); media_log.error("video_encoder: av_channel_layout_copy failed. Error: %d='%s'", err, av_error_to_string(err));
@ -983,9 +1056,23 @@ namespace utils
} }
else else
{ {
media_log.error("video_encoder: select_channel_layout returned nullptr"); media_log.notice("video_encoder: select_channel_layout returned nullptr, trying with own layout...");
has_error = true;
return; const AVChannelLayout new_ch_layout = get_preferred_channel_layout(m_channels);
if (memcmp(&new_ch_layout, &empty_ch_layout, sizeof(AVChannelLayout)) == 0)
{
media_log.error("video_encoder: unsupported audio channel count: %d", m_channels);
has_error = true;
return;
}
if (int err = av_channel_layout_copy(&av.audio.context->ch_layout, &new_ch_layout); err != 0)
{
media_log.error("video_encoder: av_channel_layout_copy failed. Error: %d='%s'", err, av_error_to_string(err));
has_error = true;
return;
}
} }
m_sample_rate = select_sample_rate(av.audio.codec); m_sample_rate = select_sample_rate(av.audio.codec);
@ -1050,6 +1137,9 @@ namespace utils
has_error = true; has_error = true;
return; return;
} }
// Log channel layout
media_log.notice("video_encoder: av_channel_layout='%s'", channel_layout_name(av.audio.frame->ch_layout));
} }
// select video parameters supported by the encoder // select video parameters supported by the encoder
@ -1326,6 +1416,7 @@ namespace utils
return; return;
} }
// NOTE: The ffmpeg channel layout should match our downmix channel layout
if (sample_fmt_is_planar) if (sample_fmt_is_planar)
{ {
const int channels = av.audio.frame->ch_layout.nb_channels; const int channels = av.audio.frame->ch_layout.nb_channels;

View file

@ -144,6 +144,6 @@ namespace utils
// Audio parameters // Audio parameters
u32 m_channels = 2; u32 m_channels = 2;
u32 m_audio_bitrate_bps = 320000; u32 m_audio_bitrate_bps = 320000;
s32 m_audio_codec_id = 86019; // AV_CODEC_ID_AC3 s32 m_audio_codec_id = 86018; // AV_CODEC_ID_AAC
}; };
} }