From 498d071f8e2c7c46a54ebb7add24d0a4bc43d1b7 Mon Sep 17 00:00:00 2001
From: Benjamin Radel <benjamin@radel.tk>
Date: Sat, 29 Jun 2024 00:57:24 +0200
Subject: [PATCH] Use new ffmpeg-7 API for channel_layout -> ch_layout

Adapt DCPoMatic for using the new ffmpeg 7 ch_layout API if detected.
---
 src/lib/audio_filter_graph.cc  | 25 ++++++++++++++++++++++++-
 src/lib/audio_filter_graph.h   |  4 ++++
 src/lib/ffmpeg_decoder.cc      |  4 ++++
 src/lib/ffmpeg_examiner.cc     | 11 ++++++++++-
 src/lib/ffmpeg_file_encoder.cc | 15 +++++++++++++--
 wscript                        | 12 ++++++++++++
 6 files changed, 67 insertions(+), 4 deletions(-)

diff --git a/src/lib/audio_filter_graph.cc b/src/lib/audio_filter_graph.cc
index 4e3052d57..fc506f1ef 100644
--- a/src/lib/audio_filter_graph.cc
+++ b/src/lib/audio_filter_graph.cc
@@ -49,9 +49,17 @@ AudioFilterGraph::AudioFilterGraph (int sample_rate, int channels)
 	   so we need to tell it we're using 16 channels if we are using more than 8.
 	*/
 	if (_channels > 8) {
+#ifdef DCPOMATIC_HAVE_AVCHANNELLAYOUT
+		av_channel_layout_default (&_channel_layout, 16);
+#else
 		_channel_layout = av_get_default_channel_layout (16);
+#endif
 	} else {
+#ifdef DCPOMATIC_HAVE_AVCHANNELLAYOUT
+		av_channel_layout_default (&_channel_layout, _channels);
+#else
 		_channel_layout = av_get_default_channel_layout (_channels);
+#endif
 	}
 
 	_in_frame = av_frame_alloc ();
@@ -69,8 +77,11 @@ string
 AudioFilterGraph::src_parameters () const
 {
 	char layout[64];
+#ifdef DCPOMATIC_HAVE_AVCHANNELLAYOUT
+	av_channel_layout_describe (&_channel_layout, layout, sizeof(layout));
+#else
 	av_get_channel_layout_string (layout, sizeof(layout), 0, _channel_layout);
-
+#endif
 	char buffer[256];
 	snprintf (
 		buffer, sizeof(buffer), "time_base=1/1:sample_rate=%d:sample_fmt=%s:channel_layout=%s",
@@ -88,8 +99,12 @@ AudioFilterGraph::set_parameters (AVFilterContext* context) const
 	int r = av_opt_set_int_list (context, "sample_fmts", sample_fmts, AV_SAMPLE_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
 	DCPOMATIC_ASSERT (r >= 0);
 
+#ifdef DCPOMATIC_HAVE_AVCHANNELLAYOUT
+	r = av_opt_set_chlayout (context, "channel_layouts", &_channel_layout, AV_OPT_SEARCH_CHILDREN);
+#else
 	int64_t channel_layouts[] = { _channel_layout, -1 };
 	r = av_opt_set_int_list (context, "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN);
+#endif
 	DCPOMATIC_ASSERT (r >= 0);
 
 	int sample_rates[] = { _sample_rate, -1 };
@@ -114,7 +129,11 @@ void
 AudioFilterGraph::process (shared_ptr<AudioBuffers> buffers)
 {
 	DCPOMATIC_ASSERT (buffers->frames() > 0);
+#ifdef DCPOMATIC_HAVE_AVCHANNELLAYOUT
+	int const process_channels = _channel_layout.nb_channels;
+#else
 	int const process_channels = av_get_channel_layout_nb_channels (_channel_layout);
+#endif
 	DCPOMATIC_ASSERT (process_channels >= buffers->channels());
 
 	if (buffers->channels() < process_channels) {
@@ -144,8 +163,12 @@ AudioFilterGraph::process (shared_ptr<AudioBuffers> buffers)
 	_in_frame->nb_samples = buffers->frames ();
 	_in_frame->format = AV_SAMPLE_FMT_FLTP;
 	_in_frame->sample_rate = _sample_rate;
+#ifdef DCPOMATIC_HAVE_AVCHANNELLAYOUT
+	_in_frame->ch_layout = _channel_layout;
+#else
 	_in_frame->channel_layout = _channel_layout;
 	_in_frame->channels = process_channels;
+#endif
 
 	int r = av_buffersrc_write_frame (_buffer_src_context, _in_frame);
 
diff --git a/src/lib/audio_filter_graph.h b/src/lib/audio_filter_graph.h
index e5c55fa27..9bbd28949 100644
--- a/src/lib/audio_filter_graph.h
+++ b/src/lib/audio_filter_graph.h
@@ -47,7 +47,11 @@ protected:
 private:
 	int _sample_rate;
 	int _channels;
+#ifdef DCPOMATIC_HAVE_AVCHANNELLAYOUT
+	AVChannelLayout _channel_layout;
+#else
 	int64_t _channel_layout;
+#endif
 	AVFrame* _in_frame;
 };
 
diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc
index 45983795b..76e52e272 100644
--- a/src/lib/ffmpeg_decoder.cc
+++ b/src/lib/ffmpeg_decoder.cc
@@ -260,7 +260,11 @@ deinterleave_audio(AVFrame* frame)
 
 	/* XXX: can't we use swr_convert() to do the format conversion? */
 
+#ifdef DCPOMATIC_HAVE_AVCHANNELLAYOUT
+	int const channels = frame->ch_layout.nb_channels;
+#else
 	int const channels = frame->channels;
+#endif
 	int const frames = frame->nb_samples;
 	int const total_samples = frames * channels;
 	auto audio = make_shared<AudioBuffers>(channels, frames);
diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc
index 51ade8e89..0ed2aa5c1 100644
--- a/src/lib/ffmpeg_examiner.cc
+++ b/src/lib/ffmpeg_examiner.cc
@@ -76,10 +76,15 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c, shared_ptr<Jo
 			/* This is a hack; sometimes it seems that _audio_codec_context->channel_layout isn't set up,
 			   so bodge it here.  No idea why we should have to do this.
 			*/
-
+#ifdef DCPOMATIC_HAVE_AVCHANNELLAYOUT
+			if (s->codecpar->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) {
+				av_channel_layout_default (&s->codecpar->ch_layout, s->codecpar->ch_layout.nb_channels);
+			}
+#else
 			if (s->codecpar->channel_layout == 0) {
 				s->codecpar->channel_layout = av_get_default_channel_layout (s->codecpar->channels);
 			}
+#endif
 
 			DCPOMATIC_ASSERT (_format_context->duration != AV_NOPTS_VALUE);
 			DCPOMATIC_ASSERT (codec->name);
@@ -91,7 +96,11 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c, shared_ptr<Jo
 					s->id,
 					s->codecpar->sample_rate,
 					llrint ((double(_format_context->duration) / AV_TIME_BASE) * s->codecpar->sample_rate),
+#ifdef DCPOMATIC_HAVE_AVCHANNELLAYOUT
+					s->codecpar->ch_layout.nb_channels,
+#else
 					s->codecpar->channels,
+#endif
 					s->codecpar->bits_per_raw_sample ? s->codecpar->bits_per_raw_sample : s->codecpar->bits_per_coded_sample
 					)
 				);
diff --git a/src/lib/ffmpeg_file_encoder.cc b/src/lib/ffmpeg_file_encoder.cc
index 6d1ad68f7..196b8aa98 100644
--- a/src/lib/ffmpeg_file_encoder.cc
+++ b/src/lib/ffmpeg_file_encoder.cc
@@ -36,7 +36,6 @@ extern "C" {
 
 #include "i18n.h"
 
-
 using std::cout;
 using std::make_shared;
 using std::shared_ptr;
@@ -73,9 +72,12 @@ public:
 		_codec_context->bit_rate = channels * 128 * 1024;
 		_codec_context->sample_fmt = sample_format;
 		_codec_context->sample_rate = frame_rate;
+#ifdef DCPOMATIC_HAVE_AVCHANNELLAYOUT
+		av_channel_layout_default (&_codec_context->ch_layout, channels);
+#else
 		_codec_context->channel_layout = av_get_default_channel_layout (channels);
 		_codec_context->channels = channels;
-
+#endif
 		int r = avcodec_open2 (_codec_context, _codec, 0);
 		if (r < 0) {
 			throw EncodeError (N_("avcodec_open2"), N_("ExportAudioStream::ExportAudioStream"), r);
@@ -134,6 +136,11 @@ public:
 		auto frame = av_frame_alloc ();
 		DCPOMATIC_ASSERT (frame);
 
+#ifdef DCPOMATIC_HAVE_AVCHANNELLAYOUT
+		AVChannelLayout ch_layout;
+		av_channel_layout_default (&ch_layout, channels);
+#endif
+
 		int line_size;
 		int const buffer_size = av_samples_get_buffer_size (&line_size, channels, size, _codec_context->sample_fmt, 0);
 		DCPOMATIC_ASSERT (buffer_size >= 0);
@@ -143,7 +150,11 @@ public:
 
 		frame->nb_samples = size;
 		frame->format = _codec_context->sample_fmt;
+#ifdef DCPOMATIC_HAVE_AVCHANNELLAYOUT
+		frame->ch_layout = ch_layout;
+#else
 		frame->channels = channels;
+#endif
 		int r = avcodec_fill_audio_frame (frame, channels, _codec_context->sample_fmt, (const uint8_t *) samples, buffer_size, 0);
 		DCPOMATIC_ASSERT (r >= 0);
 
diff --git a/wscript b/wscript
index 744e45416..cb1ffadf7 100644
--- a/wscript
+++ b/wscript
@@ -501,6 +501,18 @@ def configure(conf):
                    define_name='DCPOMATIC_HAVE_AVCOMPONENTDESCRIPTOR_DEPTH_MINUS1',
                    mandatory=False)
 
+    # Check to see if we have the AVChannelLayout struct introduced in ffmpeg 7
+    conf.check_cxx(fragment="""
+                            extern "C" {\n
+                            #include <libavutil/channel_layout.h>\n
+                            }\n
+                            int main () { AVChannelLayout ch_layout = {}; }\n
+                            """,
+                   msg='Checking for AVChannelLayout',
+                   uselib='AVUTIL',
+                   define_name='DCPOMATIC_HAVE_AVCHANNELLAYOUT',
+                   mandatory=False)
+
     # See if we have av_register_all and avfilter_register_all
     conf.check_cxx(fragment="""
                             extern "C" {\n
-- 
2.45.2

