+
+core::audio_channel_layout get_audio_channel_layout(int num_channels, std::uint64_t layout, const std::wstring& channel_layout_spec)
+{
+ if (!channel_layout_spec.empty())
+ {
+ if (boost::contains(channel_layout_spec, L":")) // Custom on the fly layout specified.
+ {
+ std::vector<std::wstring> type_and_channel_order;
+ boost::split(type_and_channel_order, channel_layout_spec, boost::is_any_of(L":"), boost::algorithm::token_compress_off);
+ auto& type = type_and_channel_order.at(0);
+ auto& order = type_and_channel_order.at(1);
+
+ return core::audio_channel_layout(num_channels, std::move(type), order);
+ }
+ else // Preconfigured named channel layout selected.
+ {
+ auto channel_layout = core::audio_channel_layout_repository::get_default()->get_layout(channel_layout_spec);
+
+ if (!channel_layout)
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"No channel layout with name " + channel_layout_spec + L" registered"));
+
+ channel_layout->num_channels = num_channels;
+
+ return *channel_layout;
+ }
+ }
+
+ if (!layout)
+ {
+ if (num_channels == 1)
+ return core::audio_channel_layout(num_channels, L"mono", L"FC");
+ else if (num_channels == 2)
+ return core::audio_channel_layout(num_channels, L"stereo", L"FL FR");
+ else
+ return core::audio_channel_layout(num_channels, L"", L""); // Passthru without named channels as is.
+ }
+
+ // What FFmpeg calls "channel layout" is only the "layout type" of a channel layout in
+ // CasparCG where the channel layout supports different orders as well.
+ // The user needs to provide additional mix-configs in casparcg.config to support more
+ // than the most common (5.1, mono and stereo) types.
+
+ // Based on information in https://ffmpeg.org/ffmpeg-utils.html#Channel-Layout
+ switch (layout)
+ {
+ case AV_CH_LAYOUT_MONO:
+ return core::audio_channel_layout(num_channels, L"mono", L"FC");
+ case AV_CH_LAYOUT_STEREO:
+ return core::audio_channel_layout(num_channels, L"stereo", L"FL FR");
+ case AV_CH_LAYOUT_2POINT1:
+ return core::audio_channel_layout(num_channels, L"2.1", L"FL FR LFE");
+ case AV_CH_LAYOUT_SURROUND:
+ return core::audio_channel_layout(num_channels, L"3.0", L"FL FR FC");
+ case AV_CH_LAYOUT_2_1:
+ return core::audio_channel_layout(num_channels, L"3.0(back)", L"FL FR BC");
+ case AV_CH_LAYOUT_4POINT0:
+ return core::audio_channel_layout(num_channels, L"4.0", L"FL FR FC BC");
+ case AV_CH_LAYOUT_QUAD:
+ return core::audio_channel_layout(num_channels, L"quad", L"FL FR BL BR");
+ case AV_CH_LAYOUT_2_2:
+ return core::audio_channel_layout(num_channels, L"quad(side)", L"FL FR SL SR");
+ case AV_CH_LAYOUT_3POINT1:
+ return core::audio_channel_layout(num_channels, L"3.1", L"FL FR FC LFE");
+ case AV_CH_LAYOUT_5POINT0_BACK:
+ return core::audio_channel_layout(num_channels, L"5.0", L"FL FR FC BL BR");
+ case AV_CH_LAYOUT_5POINT0:
+ return core::audio_channel_layout(num_channels, L"5.0(side)", L"FL FR FC SL SR");
+ case AV_CH_LAYOUT_4POINT1:
+ return core::audio_channel_layout(num_channels, L"4.1", L"FL FR FC LFE BC");
+ case AV_CH_LAYOUT_5POINT1_BACK:
+ return core::audio_channel_layout(num_channels, L"5.1", L"FL FR FC LFE BL BR");
+ case AV_CH_LAYOUT_5POINT1:
+ return core::audio_channel_layout(num_channels, L"5.1(side)", L"FL FR FC LFE SL SR");
+ case AV_CH_LAYOUT_6POINT0:
+ return core::audio_channel_layout(num_channels, L"6.0", L"FL FR FC BC SL SR");
+ case AV_CH_LAYOUT_6POINT0_FRONT:
+ return core::audio_channel_layout(num_channels, L"6.0(front)", L"FL FR FLC FRC SL SR");
+ case AV_CH_LAYOUT_HEXAGONAL:
+ return core::audio_channel_layout(num_channels, L"hexagonal", L"FL FR FC BL BR BC");
+ case AV_CH_LAYOUT_6POINT1:
+ return core::audio_channel_layout(num_channels, L"6.1", L"FL FR FC LFE BC SL SR");
+ case AV_CH_LAYOUT_6POINT1_BACK:
+ return core::audio_channel_layout(num_channels, L"6.1(back)", L"FL FR FC LFE BL BR BC");
+ case AV_CH_LAYOUT_6POINT1_FRONT:
+ return core::audio_channel_layout(num_channels, L"6.1(front)", L"FL FR LFE FLC FRC SL SR");
+ case AV_CH_LAYOUT_7POINT0:
+ return core::audio_channel_layout(num_channels, L"7.0", L"FL FR FC BL BR SL SR");
+ case AV_CH_LAYOUT_7POINT0_FRONT:
+ return core::audio_channel_layout(num_channels, L"7.0(front)", L"FL FR FC FLC FRC SL SR");
+ case AV_CH_LAYOUT_7POINT1:
+ return core::audio_channel_layout(num_channels, L"7.1", L"FL FR FC LFE BL BR SL SR");
+ case AV_CH_LAYOUT_7POINT1_WIDE_BACK:
+ return core::audio_channel_layout(num_channels, L"7.1(wide)", L"FL FR FC LFE BL BR FLC FRC");
+ case AV_CH_LAYOUT_7POINT1_WIDE:
+ return core::audio_channel_layout(num_channels, L"7.1(wide-side)", L"FL FR FC LFE FLC FRC SL SR");
+ case AV_CH_LAYOUT_STEREO_DOWNMIX:
+ return core::audio_channel_layout(num_channels, L"downmix", L"DL DR");
+ default:
+ // Passthru
+ return core::audio_channel_layout(num_channels, L"", L"");
+ }
+}
+
+// av_get_default_channel_layout does not work for layouts not predefined in ffmpeg. This is needed to support > 8 channels.
+std::uint64_t create_channel_layout_bitmask(int num_channels)
+{
+ if (num_channels > 63)
+ CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info(L"FFmpeg cannot handle more than 63 audio channels"));
+
+ const auto ALL_63_CHANNELS = 0x7FFFFFFFFFFFFFFFULL;
+
+ auto to_shift = 63 - num_channels;
+ auto result = ALL_63_CHANNELS >> to_shift;
+
+ return static_cast<std::uint64_t>(result);
+}
+
+std::string to_string(const boost::rational<int>& framerate)
+{
+ return boost::lexical_cast<std::string>(framerate.numerator())
+ + "/" + boost::lexical_cast<std::string>(framerate.denominator()) + " (" + boost::lexical_cast<std::string>(static_cast<double>(framerate.numerator()) / static_cast<double>(framerate.denominator())) + ") fps";
+}
+
+std::vector<int> find_audio_cadence(const boost::rational<int>& framerate)
+{
+ static std::map<boost::rational<int>, std::vector<int>> CADENCES_BY_FRAMERATE = []
+ {
+ std::map<boost::rational<int>, std::vector<int>> result;
+
+ for (core::video_format format : enum_constants<core::video_format>())
+ {
+ core::video_format_desc desc(format);
+ boost::rational<int> format_rate(desc.time_scale, desc.duration);
+
+ result.insert(std::make_pair(format_rate, desc.audio_cadence));
+ }
+
+ return result;
+ }();
+
+ auto exact_match = CADENCES_BY_FRAMERATE.find(framerate);
+
+ if (exact_match != CADENCES_BY_FRAMERATE.end())
+ return exact_match->second;
+
+ boost::rational<int> closest_framerate_diff = std::numeric_limits<int>::max();
+ boost::rational<int> closest_framerate = 0;
+
+ for (auto format_framerate : CADENCES_BY_FRAMERATE | boost::adaptors::map_keys)
+ {
+ auto diff = boost::abs(framerate - format_framerate);
+
+ if (diff < closest_framerate_diff)
+ {
+ closest_framerate_diff = diff;
+ closest_framerate = format_framerate;
+ }
+ }
+
+ if (is_logging_quiet_for_thread())
+ CASPAR_LOG(debug) << "No exact audio cadence match found for framerate " << to_string(framerate)
+ << "\nClosest match is " << to_string(closest_framerate)
+ << "\nwhich is a " << to_string(closest_framerate_diff) << " difference.";
+ else
+ CASPAR_LOG(warning) << "No exact audio cadence match found for framerate " << to_string(framerate)
+ << "\nClosest match is " << to_string(closest_framerate)
+ << "\nwhich is a " << to_string(closest_framerate_diff) << " difference.";
+
+ return CADENCES_BY_FRAMERATE[closest_framerate];
+}
+