<ClInclude Include="stdafx.h" />\r
<ClInclude Include="utility\assert.h" />\r
<ClInclude Include="utility\base64.h" />\r
+ <ClInclude Include="utility\iterator.h" />\r
<ClInclude Include="utility\move_on_copy.h" />\r
<ClInclude Include="utility\param.h" />\r
<ClInclude Include="utility\string.h" />\r
<ClInclude Include="utility\base64.h">\r
<Filter>source\utility</Filter>\r
</ClInclude>\r
+ <ClInclude Include="utility\iterator.h">\r
+ <Filter>source\utility</Filter>\r
+ </ClInclude>\r
</ItemGroup>\r
</Project>
\ No newline at end of file
#pragma warning (disable : 4714) // marked as __forceinline not inlined\r
#pragma warning (disable : 4505) // unreferenced local function has been removed\r
#pragma warning (disable : 4481) // nonstandard extension used: override specifier 'override'\r
+#pragma warning (disable : 4996) // Function call with parameters that may be unsafe - this call relies on the caller to check that the passed values are correct\r
+\r
#endif\r
\r
return safe_ptr<T>(std::make_shared<T>(std::forward<P0>(p0), std::forward<P1>(p1), std::forward<P2>(p2), std::forward<P3>(p3), std::forward<P4>(p4), std::forward<P5>(p5), std::forward<P6>(p6)));\r
}\r
\r
+template<typename T, typename P0, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6, typename P7>\r
+safe_ptr<T> make_safe(P0&& p0, P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5, P6&& p6, P7&& p7)\r
+{\r
+ return safe_ptr<T>(std::make_shared<T>(std::forward<P0>(p0), std::forward<P1>(p1), std::forward<P2>(p2), std::forward<P3>(p3), std::forward<P4>(p4), std::forward<P5>(p5), std::forward<P6>(p6), std::forward<P7>(p7)));\r
+}\r
+\r
template<typename T>\r
safe_ptr<T>::safe_ptr() \r
: p_(make_safe<T>())\r
--- /dev/null
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* This file is part of CasparCG (www.casparcg.com).
+*
+* CasparCG is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* CasparCG is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#pragma once
+
+#include <boost/iterator/iterator_facade.hpp>
+
+namespace caspar {
+
+/**
+ * An iterator that will automatically skip elements based on the NextFinder
+ * policy.
+ */
+template<typename Value, typename NextFinder, typename Iter = Value*>
+class position_based_skip_iterator : public boost::iterator_facade<
+ position_based_skip_iterator<Value, NextFinder, Iter>,
+ Value,
+ boost::forward_traversal_tag>
+{
+ Iter pos_;
+ Iter end_;
+ NextFinder next_finder_;
+public:
+ position_based_skip_iterator()
+ {
+ }
+
+ position_based_skip_iterator(const Iter& position)
+ : pos_(position)
+ {
+ }
+
+ position_based_skip_iterator(
+ const Iter& position, const Iter& end, const NextFinder& next_finder)
+ : pos_(position), end_(end), next_finder_(next_finder)
+ {
+ }
+private:
+ friend class boost::iterator_core_access;
+
+ void increment()
+ {
+ pos_ = next_finder_.next(pos_, end_);
+ }
+
+ bool equal(const position_based_skip_iterator<Value, NextFinder, Iter>& other) const
+ {
+ return pos_ == other.pos_;
+ }
+
+ Value& dereference() const
+ {
+ return *pos_;
+ }
+};
+
+/**
+ * Simple NextFinder policy class which always steps a given distance.
+ */
+class constant_step_finder
+{
+ std::ptrdiff_t step_;
+ size_t num_steps_;
+ mutable size_t steps_made_;
+public:
+ constant_step_finder()
+ {
+ }
+
+ constant_step_finder(std::ptrdiff_t step, size_t num_steps)
+ : step_(step)
+ , num_steps_(num_steps)
+ , steps_made_(0)
+ {
+ }
+
+ template<typename Iter>
+ Iter next(const Iter& relative_to, const Iter& end) const
+ {
+ if (steps_made_ + 1 > num_steps_)
+ return end;
+
+ ++steps_made_;
+
+ return relative_to + step_;
+ }
+};
+
+}
\r
boost::unique_future<bool> result = caspar::wrap_as_future(true);\r
\r
- if(boost::range::equal(sync_buffer_, audio_cadence_) && audio_cadence_.front() * format_desc_.audio_channels == static_cast<size_t>(frame->audio_data().size()))\r
+ if(boost::range::equal(sync_buffer_, audio_cadence_) && audio_cadence_.front() * frame->num_channels() == static_cast<size_t>(frame->audio_data().size()))\r
{ \r
// Audio sent so far is in sync, now we can send the next chunk.\r
result = consumer_->send(frame);\r
else\r
CASPAR_LOG(trace) << print() << L" Syncing audio.";\r
\r
- sync_buffer_.push_back(static_cast<size_t>(frame->audio_data().size() / format_desc_.audio_channels));\r
+ sync_buffer_.push_back(static_cast<size_t>(frame->audio_data().size() / frame->num_channels()));\r
\r
return std::move(result);\r
}\r
<ClInclude Include="StdAfx.h" />\r
</ItemGroup>\r
<ItemGroup>\r
+ <ClCompile Include="mixer\audio\audio_util.cpp">\r
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">../../StdAfx.h</PrecompiledHeaderFile>\r
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../../StdAfx.h</PrecompiledHeaderFile>\r
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Develop|Win32'">../../StdAfx.h</PrecompiledHeaderFile>\r
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../../StdAfx.h</PrecompiledHeaderFile>\r
+ </ClCompile>\r
<ClCompile Include="mixer\gpu\fence.cpp">\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">../../StdAfx.h</PrecompiledHeaderFile>\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../../StdAfx.h</PrecompiledHeaderFile>\r
<ClCompile Include="monitor\monitor.cpp">\r
<Filter>source\monitor</Filter>\r
</ClCompile>\r
+ <ClCompile Include="mixer\audio\audio_util.cpp">\r
+ <Filter>source\mixer\audio</Filter>\r
+ </ClCompile>\r
</ItemGroup>\r
</Project>
\ No newline at end of file
#include <core/mixer/write_frame.h>\r
#include <core/producer/frame/frame_transform.h>\r
#include <common/diagnostics/graph.h>\r
+#include "audio_util.h"\r
\r
#include <tbb/cache_aligned_allocator.h>\r
\r
\r
struct audio_stream\r
{\r
- frame_transform prev_transform;\r
- audio_buffer_ps audio_data;\r
+ frame_transform prev_transform;\r
+ audio_buffer_ps audio_data;\r
};\r
\r
struct audio_mixer::implementation\r
std::vector<audio_item> items_;\r
std::vector<size_t> audio_cadence_;\r
video_format_desc format_desc_;\r
+ channel_layout channel_layout_;\r
float master_volume_;\r
float previous_master_volume_;\r
\r
audio_item item;\r
item.tag = frame.tag();\r
item.transform = transform_stack_.top();\r
- item.audio_data = std::move(frame.audio_data()); // Note: We don't need to care about upper/lower since audio_data is removed/moved from the last field.\r
+\r
+ if (needs_rearranging(frame.get_channel_layout(), channel_layout_))\r
+ {\r
+ auto src_view = frame.get_multichannel_view();\r
+ \r
+ audio_buffer rearranged_buffer;\r
+ rearranged_buffer.resize(\r
+ src_view.num_samples() * channel_layout_.num_channels);\r
+\r
+ auto dst_view = make_multichannel_view<int32_t>(\r
+ rearranged_buffer.begin(),\r
+ rearranged_buffer.end(),\r
+ channel_layout_);\r
+\r
+ bool rearrange_success = rearrange_or_rearrange_and_mix(\r
+ src_view, dst_view, default_mix_config_repository());\r
+\r
+ if (!rearrange_success)\r
+ {\r
+ failed_rearrange(item.tag, src_view.channel_layout());\r
+ }\r
+\r
+ item.audio_data = std::move(rearranged_buffer);\r
+ }\r
+ else\r
+ {\r
+ item.audio_data = std::move(frame.audio_data()); // Note: We don't need to care about upper/lower since audio_data is removed/moved from the last field.\r
+ }\r
\r
items_.push_back(std::move(item)); \r
}\r
master_volume_ = volume;\r
}\r
\r
- audio_buffer mix(const video_format_desc& format_desc)\r
+ audio_buffer mix(const video_format_desc& format_desc, const channel_layout& layout)\r
{ \r
if(format_desc_ != format_desc)\r
{\r
audio_streams_.clear();\r
audio_cadence_ = format_desc.audio_cadence;\r
format_desc_ = format_desc;\r
- } \r
+ channel_layout_ = layout;\r
+ }\r
\r
std::map<const void*, audio_stream> next_audio_streams;\r
\r
const float prev_volume = static_cast<float>(prev_transform.volume) * previous_master_volume_;\r
const float next_volume = static_cast<float>(next_transform.volume) * master_volume_;\r
\r
- auto alpha = (next_volume-prev_volume)/static_cast<float>(item.audio_data.size()/format_desc.audio_channels);\r
+ auto alpha = (next_volume-prev_volume)/static_cast<float>(item.audio_data.size()/channel_layout_.num_channels);\r
\r
for(size_t n = 0; n < item.audio_data.size(); ++n)\r
{\r
- auto sample_multiplier = (prev_volume + (n/format_desc_.audio_channels) * alpha);\r
+ auto sample_multiplier = (prev_volume + (n/channel_layout_.num_channels) * alpha);\r
next_audio.push_back(item.audio_data[n] * sample_multiplier);\r
}\r
\r
\r
size_t audio_size(size_t num_samples) const\r
{\r
- return num_samples * format_desc_.audio_channels;\r
+ return num_samples * channel_layout_.num_channels;\r
+ }\r
+\r
+ void failed_rearrange(const void* tag, const channel_layout& layout)\r
+ {\r
+ if (audio_streams_.find(tag) != audio_streams_.end())\r
+ return; // We don't want to flood the logs.\r
+\r
+ CASPAR_LOG(warning)\r
+ << L"[audio_mixer] Could not satisfactory down/upmix from " \r
+ << layout.name << L" to " << channel_layout_.name \r
+ << L" because no mix config was found for " \r
+ << layout.layout_type << L" => " << channel_layout_.layout_type \r
+ << L". This might cause audio to be lost.";\r
}\r
};\r
\r
void audio_mixer::visit(core::write_frame& frame){impl_->visit(frame);}\r
void audio_mixer::end(){impl_->end();}\r
void audio_mixer::set_master_volume(float volume) { impl_->set_master_volume(volume); }\r
-audio_buffer audio_mixer::operator()(const video_format_desc& format_desc){return impl_->mix(format_desc);}\r
+audio_buffer audio_mixer::operator()(const video_format_desc& format_desc, const channel_layout& layout){return impl_->mix(format_desc, layout);}\r
\r
}}
\ No newline at end of file
namespace core {\r
\r
struct video_format_desc;\r
+struct channel_layout;\r
\r
typedef std::vector<int32_t, tbb::cache_aligned_allocator<int32_t>> audio_buffer;\r
\r
\r
void set_master_volume(float volume);\r
\r
- audio_buffer operator()(const video_format_desc& format_desc);\r
+ audio_buffer operator()(const video_format_desc& format_desc, const channel_layout& layout);\r
\r
private:\r
struct implementation;\r
--- /dev/null
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* This file is part of CasparCG (www.casparcg.com).
+*
+* CasparCG is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* CasparCG is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
+*
+* Author: Helge Norberg, helge.norberg@svt.se
+*/
+
+#include "../../stdafx.h"
+
+#include "audio_util.h"
+
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/foreach.hpp>
+#include <boost/assign.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/property_tree/exceptions.hpp>
+
+namespace caspar { namespace core {
+
+bool channel_layout::operator==(const channel_layout& other) const
+{
+ return channel_names == other.channel_names
+ && num_channels == other.num_channels;
+}
+
+int channel_layout::channel_index(const std::wstring& channel_name) const
+{
+ auto iter =
+ std::find(channel_names.begin(), channel_names.end(), channel_name);
+
+ if (iter == channel_names.end())
+ return -1;
+
+ return iter - channel_names.begin();
+}
+
+bool channel_layout::has_channel(const std::wstring& channel_name) const
+{
+ return channel_index(channel_name) != -1;
+}
+
+bool channel_layout::no_channel_names() const
+{
+ return channel_names.empty();
+}
+
+bool needs_rearranging(
+ const channel_layout& source, const channel_layout& destination)
+{
+ if ((source.no_channel_names() || destination.no_channel_names())
+ && source.num_channels == destination.num_channels)
+ return false;
+
+ return !(source == destination);
+}
+
+struct channel_layout_repository::impl
+{
+ std::map<std::wstring, const channel_layout> layouts;
+ boost::mutex mutex;
+};
+
+channel_layout_repository::channel_layout_repository()
+ : impl_(new impl)
+{
+}
+
+channel_layout_repository::~channel_layout_repository()
+{
+}
+
+void channel_layout_repository::register_layout(const channel_layout& layout)
+{
+ boost::unique_lock<boost::mutex> lock(impl_->mutex);
+
+ impl_->layouts.erase(layout.name);
+ impl_->layouts.insert(std::make_pair(layout.name, layout));
+}
+
+const channel_layout& channel_layout_repository::get_by_name(
+ const std::wstring& layout_name) const
+{
+ boost::unique_lock<boost::mutex> lock(impl_->mutex);
+
+ auto iter = impl_->layouts.find(layout_name);
+
+ if (iter == impl_->layouts.end())
+ BOOST_THROW_EXCEPTION(invalid_argument()
+ << msg_info(narrow(layout_name) + " not found"));
+
+ return iter->second;
+}
+
+channel_layout create_layout_from_string(
+ const std::wstring& name,
+ const std::wstring& layout_type,
+ int num_channels,
+ const std::wstring& channels)
+{
+ channel_layout layout;
+
+ layout.name = boost::to_upper_copy(name);
+ layout.layout_type = boost::to_upper_copy(layout_type);
+ auto upper_channels = boost::to_upper_copy(channels);
+
+ if (channels.length() > 0)
+ boost::split(
+ layout.channel_names,
+ upper_channels,
+ boost::is_any_of(L"\t "),
+ boost::token_compress_on);
+
+ layout.num_channels = num_channels == -1
+ ? layout.channel_names.size() : num_channels;
+
+ return layout;
+}
+
+channel_layout create_unspecified_layout(int num_channels)
+{
+ channel_layout layout;
+
+ layout.name = L"UNORDERED" + boost::lexical_cast<std::wstring>(
+ num_channels) + L"CH";
+ layout.layout_type = L"UNORDERED";
+ layout.num_channels = num_channels;
+
+ return layout;
+}
+
+void register_default_channel_layouts(channel_layout_repository& repository)
+{
+ repository.register_layout(create_layout_from_string(
+ L"mono", L"1.0", 1, L"C"));
+ repository.register_layout(create_layout_from_string(
+ L"stereo", L"2.0", 2, L"L R"));
+ repository.register_layout(create_layout_from_string(
+ L"dts", L"5.1", 6, L"C L R Ls Rs LFE"));
+ repository.register_layout(create_layout_from_string(
+ L"dolbye", L"5.1+stereomix", 8, L"L R C LFE Ls Rs Lmix Rmix"
+ ));
+ repository.register_layout(create_layout_from_string(
+ L"dolbydigital", L"5.1", 6, L"L C R Ls Rs LFE"));
+ repository.register_layout(create_layout_from_string(
+ L"smpte", L"5.1", 6, L"L R C LFE Ls Rs"));
+ repository.register_layout(create_layout_from_string(
+ L"passthru", L"16ch", 16, L""));
+}
+
+void parse_channel_layouts(
+ channel_layout_repository& repository,
+ const boost::property_tree::wptree& layouts_element)
+{
+ BOOST_FOREACH(auto& layout, layouts_element)
+ {
+ repository.register_layout(create_layout_from_string(
+ layout.first,
+ layout.second.get<std::wstring>(L"type"),
+ layout.second.get<int>(L"num-channels"),
+ layout.second.get<std::wstring>(L"channels")));
+ }
+}
+
+channel_layout_repository& default_channel_layout_repository()
+{
+ static channel_layout_repository repository;
+
+ return repository;
+}
+
+struct mix_config_repository::impl
+{
+ std::map<std::wstring, std::map<std::wstring, const mix_config>> configs;
+ boost::mutex mutex;
+};
+
+mix_config_repository::mix_config_repository()
+ : impl_(new impl)
+{
+}
+
+mix_config_repository::~mix_config_repository()
+{
+}
+
+void mix_config_repository::register_mix_config(const mix_config& config)
+{
+ boost::unique_lock<boost::mutex> lock(impl_->mutex);
+
+ impl_->configs[config.from_layout_type].erase(config.to_layout_type);
+ impl_->configs[config.from_layout_type].insert(
+ std::make_pair(config.to_layout_type, config));
+}
+
+boost::optional<mix_config> mix_config_repository::get_mix_config(
+ const std::wstring& from_layout_type,
+ const std::wstring& to_layout_type) const
+{
+ boost::unique_lock<boost::mutex> lock(impl_->mutex);
+
+ auto iter = impl_->configs[from_layout_type].find(to_layout_type);
+
+ if (iter == impl_->configs[from_layout_type].end())
+ return boost::optional<mix_config>();
+
+ return iter->second;
+}
+
+mix_config create_mix_config_from_string(
+ const std::wstring& from_layout_type,
+ const std::wstring& to_layout_type,
+ mix_config::mix_strategy strategy,
+ const std::vector<std::wstring>& mappings)
+{
+ mix_config config;
+ config.from_layout_type = boost::to_upper_copy(from_layout_type);
+ config.to_layout_type = boost::to_upper_copy(to_layout_type);
+ config.strategy = strategy;
+
+ BOOST_FOREACH(auto& mapping, mappings)
+ {
+ auto upper_mapping = boost::to_upper_copy(mapping);
+ std::vector<std::wstring> words;
+ boost::split(
+ words,
+ upper_mapping,
+ boost::is_any_of(L"\t "),
+ boost::token_compress_on);
+
+ if (words.size() != 3)
+ BOOST_THROW_EXCEPTION(invalid_argument() << msg_info(
+ "mix_config mapping string must have 3 tokens"));
+
+ auto from = words.at(0);
+ auto to = words.at(1);
+ auto influence = boost::lexical_cast<double>(words.at(2));
+
+ config.destination_ch_by_source_ch.insert(std::make_pair(
+ from,
+ mix_config::destination(to, influence)));
+ }
+
+ return config;
+}
+
+void register_default_mix_configs(mix_config_repository& repository)
+{
+ using namespace boost::assign;
+
+ // From 1.0
+ repository.register_mix_config(create_mix_config_from_string(
+ L"1.0", L"2.0", mix_config::add, list_of
+ (L"C L 1.0")
+ (L"C R 1.0")
+ ));
+ repository.register_mix_config(create_mix_config_from_string(
+ L"1.0", L"5.1", mix_config::add, list_of
+ (L"C L 1.0")
+ (L"C R 1.0")
+ ));
+ repository.register_mix_config(create_mix_config_from_string(
+ L"1.0", L"5.1+stereomix", mix_config::add, list_of
+ (L"C L 1.0")
+ (L"C R 1.0")
+ (L"C Lmix 1.0")
+ (L"C Rmix 1.0")
+ ));
+ // From 2.0
+ repository.register_mix_config(create_mix_config_from_string(
+ L"2.0", L"1.0", mix_config::add, list_of
+ (L"L C 1.0")
+ (L"R C 1.0")
+ ));
+ repository.register_mix_config(create_mix_config_from_string(
+ L"2.0", L"5.1", mix_config::add, list_of
+ (L"L L 1.0")
+ (L"R R 1.0")
+ ));
+ repository.register_mix_config(create_mix_config_from_string(
+ L"2.0", L"5.1+stereomix", mix_config::add, list_of
+ (L"L L 1.0")
+ (L"R R 1.0")
+ (L"L Lmix 1.0")
+ (L"R Rmix 1.0")
+ ));
+ // From 5.1
+ repository.register_mix_config(create_mix_config_from_string(
+ L"5.1", L"1.0", mix_config::average, list_of
+ (L"L C 1.0")
+ (L"R C 1.0")
+ (L"C C 0.707")
+ (L"Ls C 0.707")
+ (L"Rs C 0.707")
+ ));
+ repository.register_mix_config(create_mix_config_from_string(
+ L"5.1", L"2.0", mix_config::average, list_of
+ (L"L L 1.0")
+ (L"R R 1.0")
+ (L"C L 0.707")
+ (L"C R 0.707")
+ (L"Ls L 0.707")
+ (L"Rs R 0.707")
+ ));
+ repository.register_mix_config(create_mix_config_from_string(
+ L"5.1", L"5.1+stereomix", mix_config::average, list_of
+ (L"L L 1.0")
+ (L"R R 1.0")
+ (L"C C 1.0")
+ (L"Ls Ls 1.0")
+ (L"Rs Rs 1.0")
+ (L"LFE LFE 1.0")
+
+ (L"L Lmix 1.0")
+ (L"R Rmix 1.0")
+ (L"C Lmix 0.707")
+ (L"C Rmix 0.707")
+ (L"Ls Lmix 0.707")
+ (L"Rs Rmix 0.707")
+ ));
+ // From 5.1+stereomix
+ repository.register_mix_config(create_mix_config_from_string(
+ L"5.1+stereomix", L"1.0", mix_config::add, list_of
+ (L"Lmix C 1.0")
+ (L"Rmix C 1.0")
+ ));
+ repository.register_mix_config(create_mix_config_from_string(
+ L"5.1+stereomix", L"2.0", mix_config::add, list_of
+ (L"Lmix L 1.0")
+ (L"Rmix R 1.0")
+ ));
+ repository.register_mix_config(create_mix_config_from_string(
+ L"5.1+stereomix", L"5.1", mix_config::add, list_of
+ (L"L L 1.0")
+ (L"R R 1.0")
+ (L"C C 1.0")
+ (L"Ls Ls 1.0")
+ (L"Rs Rs 1.0")
+ (L"LFE LFE 1.0")
+ ));
+}
+
+void parse_mix_configs(
+ mix_config_repository& repository,
+ const boost::property_tree::wptree& channel_mixings_element)
+{
+ BOOST_FOREACH(auto element, channel_mixings_element)
+ {
+ if (element.first != L"mix-config")
+ {
+ throw boost::property_tree::ptree_error(
+ "Expected mix-config element");
+ }
+
+ std::vector<std::wstring> mappings;
+
+ boost::transform(
+ element.second.get_child(L"mappings"),
+ std::insert_iterator<std::vector<std::wstring>>(
+ mappings, mappings.begin()),
+ [](
+ const std::pair<std::wstring,
+ boost::property_tree::wptree>& mapping)
+ {
+ return mapping.second.get_value<std::wstring>();
+ });
+
+ repository.register_mix_config(create_mix_config_from_string(
+ element.second.get<std::wstring>(L"from"),
+ element.second.get<std::wstring>(L"to"),
+ boost::to_upper_copy(element.second.get<std::wstring>(
+ L"mix", L"ADD")) == L"AVERAGE"
+ ? mix_config::average : mix_config::add,
+ mappings));
+ }
+}
+
+mix_config_repository& default_mix_config_repository()
+{
+ static mix_config_repository repository;
+
+ return repository;
+}
+
+channel_layout create_custom_channel_layout(
+ const std::wstring& custom_channel_order,
+ const channel_layout_repository& repository)
+{
+ std::vector<std::wstring> splitted;
+ boost::split(
+ splitted,
+ custom_channel_order,
+ boost::is_any_of(L":"),
+ boost::token_compress_on);
+
+ if (splitted.size() == 1) // Named layout
+ {
+ try
+ {
+ return repository.get_by_name(splitted[0]);
+ }
+ catch (const std::exception&)
+ {
+ CASPAR_LOG_CURRENT_EXCEPTION();
+ BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(
+ "CHANNEL_LAYOUT must be in a format like: "
+ "\"5.1:L R C LFE Ls Rs\" or like \"SMPTE\""));
+ }
+ }
+
+ if (splitted.size() != 2)
+ BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(
+ "CHANNEL_LAYOUT must be in a format like: "
+ "\"5.1:L R C LFE Ls Rs\" or like \"SMPTE\""));
+
+ // Custom layout
+ return create_layout_from_string(
+ custom_channel_order,
+ splitted[0],
+ -1,
+ splitted[1]);
+}
+
+
+}}
* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
*\r
* Author: Robert Nagy, ronag89@gmail.com\r
+* Author: Helge Norberg, helge.norberg@svt.se\r
*/\r
\r
#pragma once\r
\r
#include <algorithm>\r
#include <vector>\r
+#include <map>\r
+#include <string>\r
\r
#include <stdint.h>\r
\r
+#include <boost/range/iterator_range.hpp>\r
+#include <boost/range/algorithm/copy.hpp>\r
+#include <boost/range/combine.hpp>\r
+#include <boost/range/adaptors.hpp>\r
+#include <boost/property_tree/ptree.hpp>\r
+#include <boost/foreach.hpp>\r
+\r
#include <tbb/cache_aligned_allocator.h>\r
\r
+#include <common/exception/exceptions.h>\r
+#include <common/utility/iterator.h>\r
+#include <common/utility/string.h>\r
+#include <common/memory/safe_ptr.h>\r
+\r
namespace caspar { namespace core {\r
\r
template<typename T>\r
return output16;\r
}\r
\r
-}}
\ No newline at end of file
+\r
+struct channel_layout\r
+{\r
+ std::wstring name;\r
+ std::wstring layout_type;\r
+ std::vector<std::wstring> channel_names;\r
+ int num_channels;\r
+\r
+ bool operator==(const channel_layout& other) const;\r
+ int channel_index(const std::wstring& channel_name) const;\r
+ bool has_channel(const std::wstring& channel_name) const;\r
+ bool no_channel_names() const;\r
+};\r
+\r
+/**\r
+ * A multichannel view of an audio buffer where the samples has to be\r
+ * interleaved like this (given 4 channels):\r
+ *\r
+ * Memory Sample: 0 1 2 3 4 5 6 7 8 9 10 11\r
+ * Temporal Sample: 0 1 2\r
+ * Channel: 1 2 3 4 1 2 3 4 1 2 3 4\r
+ *\r
+ * Exposes each individual channel as an isolated iterable range.\r
+ */\r
+template<typename SampleT, typename Iter>\r
+class multichannel_view\r
+{\r
+ Iter begin_;\r
+ Iter end_;\r
+ channel_layout channel_layout_;\r
+ int num_channels_;\r
+public:\r
+ typedef position_based_skip_iterator<SampleT, constant_step_finder, Iter>\r
+ iter_t;\r
+\r
+ multichannel_view(\r
+ const Iter& begin, \r
+ const Iter& end, \r
+ const channel_layout& channel_layout)\r
+ : begin_(begin)\r
+ , end_(end)\r
+ , channel_layout_(channel_layout)\r
+ , num_channels_(channel_layout.num_channels)\r
+ {\r
+ }\r
+\r
+ multichannel_view(\r
+ const Iter& begin,\r
+ const Iter& end,\r
+ const channel_layout& channel_layout,\r
+ int num_channels)\r
+ : begin_(begin)\r
+ , end_(end)\r
+ , channel_layout_(channel_layout)\r
+ , num_channels_(num_channels)\r
+ {\r
+ }\r
+\r
+ Iter raw_begin() const\r
+ {\r
+ return begin_;\r
+ }\r
+\r
+ Iter raw_end() const\r
+ {\r
+ return end_;\r
+ }\r
+\r
+ int num_channels() const\r
+ {\r
+ return num_channels_;\r
+ }\r
+\r
+ int num_samples() const\r
+ {\r
+ return std::distance(begin_, end_) / num_channels();\r
+ }\r
+\r
+ const channel_layout& channel_layout() const\r
+ {\r
+ return channel_layout_;\r
+ }\r
+\r
+ boost::iterator_range<iter_t> channel(int channel) const\r
+ {\r
+ auto start_position = begin_ + channel;\r
+\r
+ return boost::iterator_range<iter_t>(\r
+ iter_t(\r
+ start_position,\r
+ end_,\r
+ constant_step_finder(\r
+ num_channels(), num_samples() - 1)),\r
+ iter_t(end_));\r
+ }\r
+\r
+ boost::iterator_range<iter_t> channel(\r
+ const std::wstring& channel_name) const\r
+ {\r
+ auto it = std::find(\r
+ channel_layout_.channel_names.begin(),\r
+ channel_layout_.channel_names.end(),\r
+ channel_name);\r
+\r
+ if (it == channel_layout_.channel_names.end())\r
+ BOOST_THROW_EXCEPTION(invalid_argument() \r
+ << msg_info("channel not found " + narrow(channel_name)));\r
+\r
+ auto index = it - channel_layout_.channel_names.begin();\r
+\r
+ return channel(index);\r
+ }\r
+};\r
+\r
+template<typename SampleT, typename Iter>\r
+multichannel_view<SampleT, Iter> make_multichannel_view(\r
+ const Iter& begin,\r
+ const Iter& end,\r
+ const channel_layout& channel_layout)\r
+{\r
+ return multichannel_view<SampleT, Iter>(begin, end, channel_layout);\r
+}\r
+\r
+template<typename SampleT, typename Iter>\r
+multichannel_view<SampleT, Iter> make_multichannel_view(\r
+ const Iter& begin,\r
+ const Iter& end,\r
+ const channel_layout& channel_layout,\r
+ int num_channels)\r
+{\r
+ return multichannel_view<SampleT, Iter>(\r
+ begin, end, channel_layout, num_channels);\r
+}\r
+\r
+struct mix_config\r
+{\r
+ struct destination\r
+ {\r
+ std::wstring channel_name;\r
+ double influence;\r
+\r
+ destination(const std::wstring& channel_name, double influence)\r
+ : channel_name(channel_name), influence(influence)\r
+ {\r
+ }\r
+ };\r
+\r
+ enum mix_strategy\r
+ {\r
+ add,\r
+ average\r
+ };\r
+\r
+ std::wstring from_layout_type;\r
+ std::wstring to_layout_type;\r
+ std::multimap<std::wstring, destination> destination_ch_by_source_ch;\r
+ mix_strategy strategy;\r
+};\r
+\r
+bool needs_rearranging(\r
+ const channel_layout& source, const channel_layout& destination);\r
+\r
+template<typename SrcView>\r
+bool needs_rearranging(\r
+ const SrcView& source,\r
+ const channel_layout& destination,\r
+ int destination_num_channels)\r
+{\r
+ return needs_rearranging(source.channel_layout(), destination) \r
+ || source.num_channels() != destination_num_channels;\r
+}\r
+\r
+template<\r
+ typename SrcSampleT,\r
+ typename DstSampleT,\r
+ typename SrcIter,\r
+ typename DstIter>\r
+void rearrange(\r
+ const multichannel_view<SrcSampleT, SrcIter>& source,\r
+ multichannel_view<DstSampleT, DstIter>& destination)\r
+{\r
+ if (source.channel_layout().no_channel_names()\r
+ || destination.channel_layout().no_channel_names())\r
+ {\r
+ int num_channels = std::min(\r
+ source.channel_layout().num_channels,\r
+ destination.channel_layout().num_channels);\r
+\r
+ for (int i = 0; i < num_channels; ++i)\r
+ boost::copy(source.channel(i), destination.channel(i).begin());\r
+ }\r
+ else\r
+ {\r
+ BOOST_FOREACH(\r
+ auto& source_channel_name,\r
+ source.channel_layout().channel_names)\r
+ {\r
+ if (source_channel_name.empty()\r
+ || !destination.channel_layout().has_channel(\r
+ source_channel_name))\r
+ continue;\r
+\r
+ boost::copy(\r
+ source.channel(source_channel_name),\r
+ destination.channel(source_channel_name).begin());\r
+ }\r
+ }\r
+}\r
+\r
+template<typename SampleT>\r
+struct add\r
+{\r
+ typedef SampleT result_type;\r
+\r
+ result_type operator()(SampleT lhs, SampleT rhs) const\r
+ {\r
+ int64_t result = static_cast<int64_t>(lhs) + static_cast<int64_t>(rhs);\r
+\r
+ if (result > std::numeric_limits<SampleT>::max())\r
+ return std::numeric_limits<SampleT>::max();\r
+ else if (result < std::numeric_limits<SampleT>::min())\r
+ return std::numeric_limits<SampleT>::min();\r
+ else\r
+ return static_cast<SampleT>(result);\r
+ }\r
+};\r
+\r
+template<typename SampleT>\r
+struct attenuate\r
+{\r
+ typedef SampleT result_type;\r
+ double volume;\r
+\r
+ attenuate(double volume)\r
+ : volume(volume)\r
+ {\r
+ }\r
+\r
+ result_type operator()(SampleT sample) const\r
+ {\r
+ return static_cast<SampleT>(sample * volume);\r
+ }\r
+};\r
+\r
+template<typename SampleT>\r
+struct average\r
+{\r
+ typedef SampleT result_type;\r
+ int num_samples_already;\r
+\r
+ average(int num_samples_already)\r
+ : num_samples_already(num_samples_already)\r
+ {\r
+ }\r
+\r
+ result_type operator()(SampleT lhs, SampleT rhs) const\r
+ {\r
+ int64_t total = lhs * num_samples_already + rhs;\r
+\r
+ return static_cast<SampleT>(total / (num_samples_already + 1));\r
+ }\r
+};\r
+\r
+template<typename F>\r
+struct tuple_to_args\r
+{\r
+ typedef typename F::result_type result_type;\r
+ F func;\r
+\r
+ tuple_to_args(F func)\r
+ : func(func)\r
+ {\r
+ }\r
+ \r
+ template<typename T>\r
+ result_type operator()(const T& tuple) const\r
+ {\r
+ return func(boost::get<0>(tuple), boost::get<1>(tuple));\r
+ }\r
+};\r
+\r
+template<typename F>\r
+tuple_to_args<F> make_tuple_to_args(const F& func)\r
+{\r
+ return tuple_to_args<F>(func);\r
+}\r
+\r
+template<\r
+ typename SrcSampleT,\r
+ typename DstSampleT,\r
+ typename SrcIter,\r
+ typename DstIter>\r
+void rearrange_and_mix(\r
+ const multichannel_view<SrcSampleT, SrcIter>& source,\r
+ multichannel_view<DstSampleT, DstIter>& destination,\r
+ const mix_config& config)\r
+{\r
+ using namespace boost;\r
+ using namespace boost::adaptors;\r
+ std::map<std::wstring, int> num_mixed_to_channel;\r
+\r
+ BOOST_FOREACH(auto& elem, config.destination_ch_by_source_ch)\r
+ {\r
+ auto& source_channel_name = elem.first;\r
+ auto& destination_channel_name = elem.second.channel_name;\r
+\r
+ if (!source.channel_layout().has_channel(source_channel_name) ||\r
+ !destination.channel_layout().has_channel(destination_channel_name))\r
+ continue;\r
+\r
+ auto source_channel = source.channel(source_channel_name);\r
+ auto destination_channel =\r
+ destination.channel(destination_channel_name);\r
+ auto influence = elem.second.influence;\r
+\r
+ if (num_mixed_to_channel[destination_channel_name] > 0)\r
+ { // mix\r
+ if (config.strategy == mix_config::add)\r
+ {\r
+ if (influence == 1.0)\r
+ { // No need to attenuate\r
+ copy(\r
+ combine(destination_channel, source_channel)\r
+ | transformed(make_tuple_to_args(\r
+ add<SrcSampleT>())),\r
+ destination_channel.begin());\r
+ }\r
+ else\r
+ {\r
+ copy(\r
+ combine(\r
+ destination_channel,\r
+ source_channel | transformed(\r
+ attenuate<SrcSampleT>(influence)))\r
+ | transformed(make_tuple_to_args(\r
+ add<SrcSampleT>())),\r
+ destination_channel.begin());\r
+ }\r
+ }\r
+ else\r
+ {\r
+ if (influence == 1.0)\r
+ { // No need to attenuate\r
+ copy(\r
+ combine(destination_channel, source_channel)\r
+ | transformed(make_tuple_to_args(\r
+ average<SrcSampleT>(num_mixed_to_channel[\r
+ destination_channel_name]))),\r
+ destination_channel.begin());\r
+ }\r
+ else\r
+ {\r
+ copy(\r
+ combine(\r
+ destination_channel,\r
+ source_channel | transformed(\r
+ attenuate<SrcSampleT>(influence)))\r
+ | transformed(make_tuple_to_args(\r
+ average<SrcSampleT>(num_mixed_to_channel[\r
+ destination_channel_name]))),\r
+ destination_channel.begin());\r
+ }\r
+ }\r
+ }\r
+ else\r
+ { // copy because the destination only contains zeroes\r
+ if (influence == 1.0)\r
+ { // No need to attenuate\r
+ copy(source_channel, destination_channel.begin());\r
+ }\r
+ else\r
+ {\r
+ copy(\r
+ source_channel | transformed(attenuate<SrcSampleT>(\r
+ influence)),\r
+ destination_channel.begin());\r
+ }\r
+ }\r
+\r
+ ++num_mixed_to_channel[destination_channel_name];\r
+ }\r
+}\r
+\r
+class channel_layout_repository\r
+{\r
+public:\r
+ channel_layout_repository();\r
+ ~channel_layout_repository();\r
+ void register_layout(const channel_layout& layout);\r
+ const channel_layout& get_by_name(const std::wstring& layout_name) const;\r
+private:\r
+ struct impl;\r
+ safe_ptr<impl> impl_;\r
+};\r
+\r
+channel_layout create_layout_from_string(\r
+ const std::wstring& name,\r
+ const std::wstring& layout_type,\r
+ int num_channels,\r
+ const std::wstring& channels);\r
+channel_layout create_unspecified_layout(int num_channels);\r
+void register_default_channel_layouts(channel_layout_repository& repository);\r
+void parse_channel_layouts(\r
+ channel_layout_repository& repository,\r
+ const boost::property_tree::wptree& layouts_element);\r
+channel_layout_repository& default_channel_layout_repository();\r
+\r
+class mix_config_repository\r
+{\r
+public:\r
+ mix_config_repository();\r
+ ~mix_config_repository();\r
+ void register_mix_config(const mix_config& config);\r
+ boost::optional<mix_config> get_mix_config(\r
+ const std::wstring& from_layout_type,\r
+ const std::wstring& to_layout_type) const;\r
+private:\r
+ struct impl;\r
+ safe_ptr<impl> impl_;\r
+};\r
+\r
+mix_config create_mix_config_from_string(\r
+ const std::wstring& from_layout_type,\r
+ const std::wstring& to_layout_type,\r
+ mix_config::mix_strategy strategy,\r
+ const std::vector<std::wstring>& mappings);\r
+void register_default_mix_configs(mix_config_repository& repository);\r
+void parse_mix_configs(\r
+ mix_config_repository& repository, \r
+ const boost::property_tree::wptree& channel_mixings_element);\r
+mix_config_repository& default_mix_config_repository();\r
+\r
+template<\r
+ typename SrcSampleT,\r
+ typename DstSampleT,\r
+ typename SrcIter,\r
+ typename DstIter>\r
+bool rearrange_or_rearrange_and_mix(\r
+ const multichannel_view<SrcSampleT, SrcIter>& source,\r
+ multichannel_view<DstSampleT, DstIter>& destination,\r
+ const mix_config_repository& repository)\r
+{\r
+ if (source.channel_layout().no_channel_names() \r
+ || destination.channel_layout().no_channel_names() \r
+ || source.channel_layout().layout_type\r
+ == destination.channel_layout().layout_type)\r
+ {\r
+ rearrange(source, destination);\r
+\r
+ return true;\r
+ }\r
+ else\r
+ {\r
+ auto mix_config = repository.get_mix_config(\r
+ source.channel_layout().layout_type,\r
+ destination.channel_layout().layout_type);\r
+\r
+ if (!mix_config)\r
+ {\r
+ rearrange(source, destination);\r
+\r
+ return false; // Non-satisfactory mixing, some channels might be\r
+ // lost\r
+ }\r
+\r
+ rearrange_and_mix(source, destination, *mix_config);\r
+\r
+ return true;\r
+ }\r
+}\r
+\r
+channel_layout create_custom_channel_layout(\r
+ const std::wstring& custom_channel_order,\r
+ const channel_layout_repository& repository);\r
+\r
+}}\r
#include <common/gl/gl_check.h>\r
#include <common/utility/tweener.h>\r
\r
+#include <core/mixer/audio/audio_util.h>\r
#include <core/mixer/read_frame.h>\r
#include <core/mixer/write_frame.h>\r
#include <core/producer/frame/basic_frame.h>\r
mutable tbb::spin_mutex format_desc_mutex_;\r
video_format_desc format_desc_;\r
safe_ptr<ogl_device> ogl_;\r
+ channel_layout audio_channel_layout_;\r
\r
audio_mixer audio_mixer_;\r
image_mixer image_mixer_;\r
executor executor_;\r
\r
public:\r
- implementation(const safe_ptr<diagnostics::graph>& graph, const safe_ptr<mixer::target_t>& target, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl) \r
+ implementation(const safe_ptr<diagnostics::graph>& graph, const safe_ptr<mixer::target_t>& target, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl, const channel_layout& audio_channel_layout) \r
: graph_(graph)\r
, target_(target)\r
, format_desc_(format_desc)\r
, ogl_(ogl)\r
+ , audio_channel_layout_(audio_channel_layout)\r
, image_mixer_(ogl)\r
, audio_mixer_(graph_)\r
, executor_(L"mixer")\r
}\r
\r
auto image = image_mixer_(format_desc_);\r
- auto audio = audio_mixer_(format_desc_);\r
+ auto audio = audio_mixer_(format_desc_, audio_channel_layout_);\r
image.wait();\r
\r
graph_->set_value("mix-time", mix_timer_.elapsed()*format_desc_.fps*0.5);\r
\r
- target_->send(std::make_pair(make_safe<read_frame>(ogl_, format_desc_.size, std::move(image.get()), std::move(audio)), packet.second)); \r
+ target_->send(std::make_pair(make_safe<read_frame>(ogl_, format_desc_.size, std::move(image.get()), std::move(audio), audio_channel_layout_), packet.second));\r
}\r
catch(...)\r
{\r
}); \r
}\r
\r
- safe_ptr<core::write_frame> create_frame(const void* tag, const core::pixel_format_desc& desc)\r
+ safe_ptr<core::write_frame> create_frame(\r
+ const void* tag,\r
+ const core::pixel_format_desc& desc,\r
+ const channel_layout& audio_channel_layout)\r
{ \r
- return make_safe<write_frame>(ogl_, tag, desc);\r
+ return make_safe<write_frame>(ogl_, tag, desc, audio_channel_layout);\r
}\r
\r
void set_blend_mode(int index, blend_mode::type value)\r
}\r
};\r
\r
-mixer::mixer(const safe_ptr<diagnostics::graph>& graph, const safe_ptr<target_t>& target, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl) \r
- : impl_(new implementation(graph, target, format_desc, ogl)){}\r
+mixer::mixer(const safe_ptr<diagnostics::graph>& graph, const safe_ptr<target_t>& target, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl, const channel_layout& audio_channel_layout) \r
+ : impl_(new implementation(graph, target, format_desc, ogl, audio_channel_layout)){}\r
void mixer::send(const std::pair<std::map<int, safe_ptr<core::basic_frame>>, std::shared_ptr<void>>& frames){ impl_->send(frames);}\r
core::video_format_desc mixer::get_video_format_desc() const { return impl_->get_video_format_desc(); }\r
-safe_ptr<core::write_frame> mixer::create_frame(const void* tag, const core::pixel_format_desc& desc){ return impl_->create_frame(tag, desc); } \r
+safe_ptr<core::write_frame> mixer::create_frame(const void* tag, const core::pixel_format_desc& desc, const channel_layout& audio_channel_layout){ return impl_->create_frame(tag, desc, audio_channel_layout); } \r
void mixer::set_blend_mode(int index, blend_mode::type value){impl_->set_blend_mode(index, value);}\r
void mixer::clear_blend_mode(int index) { impl_->clear_blend_mode(index); }\r
void mixer::clear_blend_modes() { impl_->clear_blend_modes(); }\r
class ogl_device;\r
struct frame_transform;\r
struct pixel_format;\r
+struct channel_layout;\r
\r
class mixer : public target<std::pair<std::map<int, safe_ptr<core::basic_frame>>, std::shared_ptr<void>>>\r
, public core::frame_factory\r
public: \r
typedef target<std::pair<safe_ptr<read_frame>, std::shared_ptr<void>>> target_t;\r
\r
- explicit mixer(const safe_ptr<diagnostics::graph>& graph, const safe_ptr<target_t>& target, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl);\r
+ explicit mixer(const safe_ptr<diagnostics::graph>& graph, const safe_ptr<target_t>& target, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl, const channel_layout& audio_channel_layout);\r
\r
// target\r
\r
\r
// mixer\r
\r
- safe_ptr<core::write_frame> create_frame(const void* tag, const core::pixel_format_desc& desc); \r
+ safe_ptr<core::write_frame> create_frame(const void* tag, const core::pixel_format_desc& desc, const channel_layout& audio_channel_layout); \r
\r
core::video_format_desc get_video_format_desc() const; // nothrow\r
void set_video_format_desc(const video_format_desc& format_desc);\r
safe_ptr<host_buffer> image_data_;\r
tbb::mutex mutex_;\r
audio_buffer audio_data_;\r
+ channel_layout audio_channel_layout_;\r
\r
public:\r
- implementation(const safe_ptr<ogl_device>& ogl, size_t size, safe_ptr<host_buffer>&& image_data, audio_buffer&& audio_data) \r
+ implementation(\r
+ const safe_ptr<ogl_device>& ogl,\r
+ size_t size,\r
+ safe_ptr<host_buffer>&& image_data,\r
+ audio_buffer&& audio_data,\r
+ const channel_layout& audio_channel_layout) \r
: ogl_(ogl)\r
, size_(size)\r
, image_data_(std::move(image_data))\r
- , audio_data_(std::move(audio_data)){} \r
+ , audio_data_(std::move(audio_data))\r
+ , audio_channel_layout_(audio_channel_layout)\r
+ {\r
+ } \r
\r
const boost::iterator_range<const uint8_t*> image_data()\r
{\r
}\r
};\r
\r
-read_frame::read_frame(const safe_ptr<ogl_device>& ogl, size_t size, safe_ptr<host_buffer>&& image_data, audio_buffer&& audio_data) \r
- : impl_(new implementation(ogl, size, std::move(image_data), std::move(audio_data))){}\r
+read_frame::read_frame(\r
+ const safe_ptr<ogl_device>& ogl,\r
+ size_t size,\r
+ safe_ptr<host_buffer>&& image_data,\r
+ audio_buffer&& audio_data,\r
+ const channel_layout& audio_channel_layout) \r
+ : impl_(new implementation(ogl, size, std::move(image_data), std::move(audio_data), audio_channel_layout))\r
+{\r
+}\r
+\r
read_frame::read_frame(){}\r
const boost::iterator_range<const uint8_t*> read_frame::image_data()\r
{\r
}\r
\r
size_t read_frame::image_size() const{return impl_ ? impl_->size_ : 0;}\r
+int read_frame::num_channels() const { return impl_ ? impl_->audio_channel_layout_.num_channels : 0; }\r
+const multichannel_view<const int32_t, boost::iterator_range<const int32_t*>::const_iterator> read_frame::multichannel_view() const\r
+{\r
+ return make_multichannel_view<const int32_t>(\r
+ impl_->audio_data().begin(),\r
+ impl_->audio_data().end(),\r
+ impl_->audio_channel_layout_);\r
+}\r
\r
//#include <tbb/scalable_allocator.h>\r
//#include <tbb/parallel_for.h>\r
#include <common/memory/safe_ptr.h>\r
\r
#include <core/mixer/audio/audio_mixer.h>\r
+#include <core/mixer/audio/audio_util.h>\r
\r
#include <boost/noncopyable.hpp>\r
#include <boost/range/iterator_range.hpp>\r
{\r
public:\r
read_frame();\r
- read_frame(const safe_ptr<ogl_device>& ogl, size_t size, safe_ptr<host_buffer>&& image_data, audio_buffer&& audio_data);\r
+ read_frame(\r
+ const safe_ptr<ogl_device>& ogl,\r
+ size_t size,\r
+ safe_ptr<host_buffer>&& image_data,\r
+ audio_buffer&& audio_data,\r
+ const channel_layout& audio_channel_layout);\r
\r
virtual const boost::iterator_range<const uint8_t*> image_data();\r
virtual const boost::iterator_range<const int32_t*> audio_data();\r
\r
virtual size_t image_size() const;\r
+ virtual int num_channels() const;\r
+ virtual const multichannel_view<const int32_t, boost::iterator_range<const int32_t*>::const_iterator> multichannel_view() const;\r
\r
private:\r
struct implementation;\r
\r
#include <core/producer/frame/frame_visitor.h>\r
#include <core/producer/frame/pixel_format.h>\r
+#include <core/mixer/audio/audio_util.h>\r
\r
#include <boost/lexical_cast.hpp>\r
\r
std::vector<safe_ptr<device_buffer>> textures_;\r
audio_buffer audio_data_;\r
const core::pixel_format_desc desc_;\r
+ const channel_layout channel_layout_;\r
const void* tag_;\r
core::field_mode::type mode_;\r
\r
- implementation(const void* tag)\r
- : tag_(tag)\r
+ implementation(const void* tag, const channel_layout& channel_layout)\r
+ : channel_layout_(channel_layout)\r
+ , tag_(tag)\r
{\r
}\r
\r
- implementation(const safe_ptr<ogl_device>& ogl, const void* tag, const core::pixel_format_desc& desc) \r
+ implementation(const safe_ptr<ogl_device>& ogl, const void* tag, const core::pixel_format_desc& desc, const channel_layout& channel_layout) \r
: ogl_(ogl)\r
, desc_(desc)\r
+ , channel_layout_(channel_layout)\r
, tag_(tag)\r
, mode_(core::field_mode::progressive)\r
{\r
}\r
};\r
\r
-write_frame::write_frame(const void* tag) : impl_(new implementation(tag)){}\r
-write_frame::write_frame(const safe_ptr<ogl_device>& ogl, const void* tag, const core::pixel_format_desc& desc) \r
- : impl_(new implementation(ogl, tag, desc)){}\r
+write_frame::write_frame(const void* tag, const channel_layout& channel_layout)\r
+ : impl_(new implementation(tag, channel_layout))\r
+{\r
+}\r
+write_frame::write_frame(\r
+ const safe_ptr<ogl_device>& ogl,\r
+ const void* tag,\r
+ const core::pixel_format_desc& desc,\r
+ const channel_layout& channel_layout)\r
+ : impl_(new implementation(ogl, tag, desc, channel_layout))\r
+{\r
+}\r
write_frame::write_frame(const write_frame& other) : impl_(new implementation(*other.impl_)){}\r
write_frame::write_frame(write_frame&& other) : impl_(std::move(other.impl_)){}\r
write_frame& write_frame::operator=(const write_frame& other)\r
audio_buffer& write_frame::audio_data() { return impl_->audio_data_; }\r
const void* write_frame::tag() const {return impl_->tag_;}\r
const core::pixel_format_desc& write_frame::get_pixel_format_desc() const{return impl_->desc_;}\r
+const channel_layout& write_frame::get_channel_layout() const{return impl_->channel_layout_;}\r
+multichannel_view<int32_t, audio_buffer::iterator> write_frame::get_multichannel_view()\r
+{\r
+ return make_multichannel_view<int32_t>(impl_->audio_data_.begin(), impl_->audio_data_.end(), impl_->channel_layout_);\r
+}\r
const std::vector<safe_ptr<device_buffer>>& write_frame::get_textures() const{return impl_->textures_;}\r
void write_frame::commit(size_t plane_index){impl_->commit(plane_index);}\r
void write_frame::commit(){impl_->commit();}\r
#include <core/producer/frame/basic_frame.h>\r
#include <core/video_format.h>\r
#include <core/mixer/audio/audio_mixer.h>\r
+#include <core/mixer/audio/audio_util.h>\r
\r
#include <boost/noncopyable.hpp>\r
#include <boost/range/iterator_range.hpp>\r
class write_frame : public core::basic_frame, boost::noncopyable\r
{\r
public: \r
- explicit write_frame(const void* tag);\r
- explicit write_frame(const safe_ptr<ogl_device>& ogl, const void* tag, const core::pixel_format_desc& desc);\r
+ explicit write_frame(const void* tag, const channel_layout& channel_layout);\r
+ explicit write_frame(const safe_ptr<ogl_device>& ogl, const void* tag, const core::pixel_format_desc& desc, const channel_layout& channel_layout);\r
\r
write_frame(const write_frame& other);\r
write_frame(write_frame&& other);\r
const void* tag() const;\r
\r
const core::pixel_format_desc& get_pixel_format_desc() const;\r
+ const channel_layout& get_channel_layout() const;\r
+ multichannel_view<int32_t, audio_buffer::iterator> get_multichannel_view();\r
\r
private:\r
friend class image_mixer;\r
#include "pixel_format.h"\r
\r
#include <common/memory/safe_ptr.h>\r
+#include <core/mixer/audio/audio_util.h>\r
\r
#include <boost/noncopyable.hpp>\r
\r
struct frame_factory : boost::noncopyable\r
{\r
virtual ~frame_factory(){}\r
- virtual safe_ptr<write_frame> create_frame(const void* video_stream_tag, const pixel_format_desc& desc) = 0; \r
- \r
+ virtual safe_ptr<write_frame> create_frame(\r
+ const void* video_stream_tag,\r
+ const pixel_format_desc& desc,\r
+ const channel_layout& audio_channel_layout = default_channel_layout_repository().get_by_name(L"STEREO")) = 0; \r
+\r
virtual video_format_desc get_video_format_desc() const = 0; // nothrow\r
};\r
\r
#include "producer/frame_producer.h"\r
#include "consumer/frame_consumer.h"\r
#include "mixer/mixer.h"\r
+#include "mixer/audio/audio_util.h"\r
#include "video_format.h"\r
#include "producer/frame/basic_frame.h"\r
#include "producer/frame/frame_transform.h"\r
, ogl_(ogl)\r
, format_desc_(render_video_mode)\r
, output_(new thumbnail_output(generate_delay_millis))\r
- , mixer_(new mixer(graph_, output_, format_desc_, ogl))\r
+ , mixer_(new mixer(\r
+ graph_,\r
+ output_,\r
+ format_desc_,\r
+ ogl,\r
+ default_channel_layout_repository().get_by_name(L"STEREO")))\r
, thumbnail_creator_(thumbnail_creator)\r
, monitor_(monitor_factory.create(\r
media_path,\r
#include "consumer/output.h"\r
#include "mixer/mixer.h"\r
#include "mixer/gpu/ogl_device.h"\r
+#include "mixer/audio/audio_util.h"\r
#include "producer/stage.h"\r
\r
#include <common/diagnostics/graph.h>\r
monitor::subject monitor_subject_;\r
\r
public:\r
- implementation(video_channel& self, int index, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl) \r
+ implementation(video_channel& self, int index, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl, const channel_layout& audio_channel_layout) \r
: self_(self)\r
, index_(index)\r
, format_desc_(format_desc)\r
, ogl_(ogl)\r
, output_(new caspar::core::output(graph_, format_desc, index))\r
- , mixer_(new caspar::core::mixer(graph_, output_, format_desc, ogl))\r
+ , mixer_(new caspar::core::mixer(graph_, output_, format_desc, ogl, audio_channel_layout))\r
, stage_(new caspar::core::stage(graph_, mixer_, format_desc)) \r
, monitor_subject_("/channel/" + boost::lexical_cast<std::string>(index))\r
{\r
}\r
};\r
\r
-video_channel::video_channel(int index, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl) \r
- : impl_(new implementation(*this, index, format_desc, ogl)){}\r
+video_channel::video_channel(int index, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl, const channel_layout& audio_channel_layout) \r
+ : impl_(new implementation(*this, index, format_desc, ogl, audio_channel_layout)){}\r
safe_ptr<stage> video_channel::stage() { return impl_->stage_;} \r
safe_ptr<mixer> video_channel::mixer() { return impl_->mixer_;} \r
safe_ptr<output> video_channel::output() { return impl_->output_;} \r
class output;\r
class ogl_device;\r
struct video_format_desc;\r
+struct channel_layout;\r
\r
class video_channel : boost::noncopyable\r
{\r
\r
// Constructors\r
\r
- explicit video_channel(int index, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl);\r
+ explicit video_channel(int index, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl, const channel_layout& audio_channel_layout);\r
\r
// Methods\r
\r
((w)*(h)*4),\\r
(name),\\r
(48000),\\r
- (2),\\r
(audio_samples)\\r
}\r
\r
std::wstring name; // name of output format\r
\r
size_t audio_sample_rate;\r
- size_t audio_channels;\r
std::vector<size_t> audio_cadence; // rotating optimal number of samples per frame\r
\r
static const video_format_desc& get(video_format::type format);\r
#include <common/memory/memcpy.h>\r
#include <common/memory/memshfl.h>\r
#include <common/utility/timer.h>\r
+#include <common/utility/param.h>\r
\r
#include <core/consumer/frame_consumer.h>\r
#include <core/mixer/audio/audio_util.h>\r
\r
#include <boost/timer.hpp>\r
#include <boost/range/algorithm.hpp>\r
+#include <boost/algorithm/string.hpp>\r
#include <boost/property_tree/ptree.hpp>\r
\r
#include <memory>\r
const unsigned int device_index_;\r
const core::video_format_desc format_desc_;\r
const int channel_index_;\r
+ const core::channel_layout channel_layout_;\r
\r
const std::wstring model_name_;\r
\r
\r
executor executor_;\r
public:\r
- bluefish_consumer(const core::video_format_desc& format_desc, unsigned int device_index, bool embedded_audio, bool key_only, int channel_index) \r
+ bluefish_consumer(\r
+ const core::video_format_desc& format_desc,\r
+ unsigned int device_index,\r
+ bool embedded_audio,\r
+ bool key_only,\r
+ int channel_index,\r
+ const core::channel_layout& channel_layout)\r
: blue_(create_blue(device_index))\r
, device_index_(device_index)\r
, format_desc_(format_desc) \r
, channel_index_(channel_index)\r
+ , channel_layout_(channel_layout)\r
, model_name_(get_card_desc(*blue_))\r
, vid_fmt_(get_video_mode(*blue_, format_desc))\r
, embedded_audio_(embedded_audio)\r
}\r
else\r
{\r
- if(BLUE_FAIL(set_card_property(blue_, EMBEDEDDED_AUDIO_OUTPUT, blue_emb_audio_enable | blue_emb_audio_group1_enable))) \r
+ ULONG audio_value =\r
+ EMBEDDED_AUDIO_OUTPUT | blue_emb_audio_group1_enable;\r
+\r
+ if (channel_layout_.num_channels > 4)\r
+ audio_value |= blue_emb_audio_group2_enable;\r
+\r
+ if (channel_layout_.num_channels > 8)\r
+ audio_value |= blue_emb_audio_group3_enable;\r
+\r
+ if (channel_layout_.num_channels > 12)\r
+ audio_value |= blue_emb_audio_group4_enable;\r
+\r
+ if(BLUE_FAIL(set_card_property(blue_, EMBEDEDDED_AUDIO_OUTPUT, audio_value))) \r
CASPAR_LOG(warning) << print() << TEXT(" Failed to enable embedded audio."); \r
CASPAR_LOG(info) << print() << TEXT(" Enabled embedded-audio.");\r
}\r
// Send and display\r
\r
if(embedded_audio_)\r
- { \r
- auto frame_audio = core::audio_32_to_24(frame->audio_data()); \r
- encode_hanc(reinterpret_cast<BLUE_UINT32*>(reserved_frames_.front()->hanc_data()), frame_audio.data(), frame->audio_data().size()/format_desc_.audio_channels, format_desc_.audio_channels);\r
+ {\r
+ auto src_view = frame->multichannel_view();\r
+\r
+ if (core::needs_rearranging(src_view, channel_layout_, channel_layout_.num_channels))\r
+ {\r
+ std::vector<int32_t> resulting_audio_data;\r
+ resulting_audio_data.resize(src_view.num_samples() * channel_layout_.num_channels, 0);\r
+\r
+ auto dest_view = core::make_multichannel_view<int32_t>(\r
+ resulting_audio_data.begin(), \r
+ resulting_audio_data.end(),\r
+ channel_layout_);\r
+\r
+ core::rearrange_or_rearrange_and_mix(\r
+ src_view,\r
+ dest_view,\r
+ core::default_mix_config_repository());\r
+\r
+ auto frame_audio = core::audio_32_to_24(resulting_audio_data);\r
+ encode_hanc(\r
+ reinterpret_cast<BLUE_UINT32*>(reserved_frames_.front()->hanc_data()),\r
+ frame_audio.data(),\r
+ src_view.num_samples(),\r
+ channel_layout_.num_channels);\r
+ }\r
+ else\r
+ {\r
+ auto frame_audio = core::audio_32_to_24(frame->audio_data());\r
+ encode_hanc(\r
+ reinterpret_cast<BLUE_UINT32*>(reserved_frames_.front()->hanc_data()),\r
+ frame_audio.data(),\r
+ src_view.num_samples(),\r
+ channel_layout_.num_channels);\r
+ }\r
\r
blue_->system_buffer_write_async(const_cast<uint8_t*>(reserved_frames_.front()->image_data()), \r
reserved_frames_.front()->image_size(), \r
void encode_hanc(BLUE_UINT32* hanc_data, void* audio_data, size_t audio_samples, size_t audio_nchannels)\r
{ \r
const auto sample_type = AUDIO_CHANNEL_24BIT | AUDIO_CHANNEL_LITTLEENDIAN;\r
- const auto emb_audio_flag = blue_emb_audio_enable | blue_emb_audio_group1_enable;\r
+ auto emb_audio_flag = blue_emb_audio_enable | blue_emb_audio_group1_enable;\r
+\r
+ if (audio_nchannels > 4)\r
+ emb_audio_flag |= blue_emb_audio_group2_enable;\r
+\r
+ if (audio_nchannels > 8)\r
+ emb_audio_flag |= blue_emb_audio_group3_enable;\r
+\r
+ if (audio_nchannels > 12)\r
+ emb_audio_flag |= blue_emb_audio_group4_enable;\r
\r
hanc_stream_info_struct hanc_stream_info;\r
memset(&hanc_stream_info, 0, sizeof(hanc_stream_info));\r
const bool key_only_;\r
std::vector<size_t> audio_cadence_;\r
core::video_format_desc format_desc_;\r
+ core::channel_layout channel_layout_;\r
+\r
public:\r
\r
- bluefish_consumer_proxy(size_t device_index, bool embedded_audio, bool key_only)\r
+ bluefish_consumer_proxy(\r
+ size_t device_index,\r
+ bool embedded_audio,\r
+ bool key_only,\r
+ const core::channel_layout& channel_layout)\r
: device_index_(device_index)\r
, embedded_audio_(embedded_audio)\r
, key_only_(key_only)\r
+ , channel_layout_(channel_layout)\r
{\r
}\r
\r
\r
virtual void initialize(const core::video_format_desc& format_desc, int channel_index) override\r
{\r
- consumer_.reset(new bluefish_consumer(format_desc, device_index_, embedded_audio_, key_only_, channel_index));\r
+ consumer_.reset(new bluefish_consumer(\r
+ format_desc,\r
+ device_index_,\r
+ embedded_audio_,\r
+ key_only_,\r
+ channel_index,\r
+ channel_layout_));\r
audio_cadence_ = format_desc.audio_cadence;\r
format_desc_ = format_desc;\r
CASPAR_LOG(info) << print() << L" Successfully Initialized."; \r
\r
virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override\r
{\r
- CASPAR_VERIFY(audio_cadence_.front() * format_desc_.audio_channels == static_cast<size_t>(frame->audio_data().size()));\r
+ CASPAR_VERIFY(audio_cadence_.front() * frame->num_channels() == static_cast<size_t>(frame->audio_data().size()));\r
boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);\r
\r
return consumer_->send(frame);\r
\r
const auto device_index = params.size() > 1 ? lexical_cast_or_default<int>(params[1], 1) : 1;\r
\r
- const auto embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
- const auto key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();\r
+ const auto embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
+ const auto key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();\r
+ const auto audio_layout = core::default_channel_layout_repository().get_by_name(\r
+ get_param(L"CHANNEL_LAYOUT", params, L"STEREO"));\r
\r
- return make_safe<bluefish_consumer_proxy>(device_index, embedded_audio, key_only);\r
+ return make_safe<bluefish_consumer_proxy>(device_index, embedded_audio, key_only, audio_layout);\r
}\r
\r
safe_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree) \r
const auto device_index = ptree.get(L"device", 1);\r
const auto embedded_audio = ptree.get(L"embedded-audio", false);\r
const auto key_only = ptree.get(L"key-only", false);\r
+ const auto audio_layout =\r
+ core::default_channel_layout_repository().get_by_name(\r
+ boost::to_upper_copy(ptree.get(L"channel-layout", L"STEREO")));\r
\r
- return make_safe<bluefish_consumer_proxy>(device_index, embedded_audio, key_only);\r
+ return make_safe<bluefish_consumer_proxy>(\r
+ device_index, embedded_audio, key_only, audio_layout);\r
}\r
\r
}}
\ No newline at end of file
#include <common/memory/memshfl.h>\r
\r
#include <core/consumer/frame_consumer.h>\r
+#include <core/mixer/audio/audio_util.h>\r
\r
#include <tbb/concurrent_queue.h>\r
#include <tbb/cache_aligned_allocator.h>\r
#include <boost/circular_buffer.hpp>\r
#include <boost/timer.hpp>\r
#include <boost/property_tree/ptree.hpp>\r
+#include <boost/algorithm/string.hpp>\r
\r
namespace caspar { namespace decklink { \r
\r
default_latency\r
};\r
\r
- size_t device_index;\r
- bool embedded_audio;\r
- keyer_t keyer;\r
- latency_t latency;\r
- bool key_only;\r
- size_t base_buffer_depth;\r
+ size_t device_index;\r
+ bool embedded_audio;\r
+ core::channel_layout audio_layout;\r
+ keyer_t keyer;\r
+ latency_t latency;\r
+ bool key_only;\r
+ size_t base_buffer_depth;\r
\r
configuration()\r
: device_index(1)\r
, embedded_audio(false)\r
+ , audio_layout(core::default_channel_layout_repository().get_by_name(L"STEREO"))\r
, keyer(default_keyer)\r
, latency(default_latency)\r
, key_only(false)\r
{\r
return base_buffer_depth + (latency == low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);\r
}\r
+\r
+ int num_out_channels() const\r
+ {\r
+ if (audio_layout.num_channels <= 2)\r
+ return 2;\r
+ \r
+ if (audio_layout.num_channels <= 8)\r
+ return 8;\r
+\r
+ return 16;\r
+ }\r
};\r
\r
class decklink_frame : public IDeckLinkVideoFrame\r
\r
void enable_audio()\r
{\r
- if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))\r
+ if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, config_.num_out_channels(), bmdAudioOutputStreamTimestamped)))\r
BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));\r
\r
if(FAILED(output_->SetAudioCallback(this)))\r
{\r
graph_->set_tag("late-frame");\r
video_scheduled_ += format_desc_.duration;\r
- audio_scheduled_ += reinterpret_cast<decklink_frame*>(completed_frame)->audio_data().size()/format_desc_.audio_channels;\r
+ auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);\r
+ audio_scheduled_ += dframe->audio_data().size()/config_.num_out_channels();\r
//++video_scheduled_;\r
//audio_scheduled_ += format_desc_.audio_cadence[0];\r
//++audio_scheduled_;\r
start_playback(); \r
}\r
else\r
- schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()] * format_desc_.audio_channels, 0)); \r
+ {\r
+ core::audio_buffer silent_audio(format_desc_.audio_cadence[preroll_count_ % format_desc_.audio_cadence.size()] * config_.num_out_channels(), 0);\r
+ auto view = core::make_multichannel_view<int32_t>(silent_audio.begin(), silent_audio.end(), config_.audio_layout, config_.num_out_channels());\r
+ schedule_next_audio(view);\r
+ }\r
}\r
else\r
{\r
while (audio_frame_buffer_.try_pop(frame))\r
{\r
send_completion_.try_completion();\r
- schedule_next_audio(frame->audio_data());\r
+ schedule_next_audio(frame->multichannel_view());\r
}\r
}\r
\r
unsigned long buffered;\r
output_->GetBufferedAudioSampleFrameCount(&buffered);\r
- graph_->set_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_cadence[0] * format_desc_.audio_channels * 2));\r
+ graph_->set_value("buffered-audio", static_cast<double>(buffered)/(format_desc_.audio_cadence[0] * config_.num_out_channels() * 2));\r
}\r
catch(...)\r
{\r
return S_OK;\r
}\r
\r
- template<typename T>\r
- void schedule_next_audio(const T& audio_data)\r
+ template<typename View>\r
+ void schedule_next_audio(const View& view)\r
{\r
- const int sample_frame_count = audio_data.size()/format_desc_.audio_channels;\r
+ const int sample_frame_count = view.num_samples();\r
+\r
+ if (core::needs_rearranging(\r
+ view, config_.audio_layout, config_.num_out_channels()))\r
+ {\r
+ std::vector<int32_t> resulting_audio_data;\r
+ resulting_audio_data.resize(\r
+ sample_frame_count * config_.num_out_channels());\r
+\r
+ auto dest_view = core::make_multichannel_view<int32_t>(\r
+ resulting_audio_data.begin(), \r
+ resulting_audio_data.end(),\r
+ config_.audio_layout,\r
+ config_.num_out_channels());\r
+\r
+ core::rearrange_or_rearrange_and_mix(\r
+ view, dest_view, core::default_mix_config_repository());\r
+\r
+ if (config_.audio_layout.num_channels == 1) // mono\r
+ boost::copy( // duplicate L to R\r
+ dest_view.channel(0),\r
+ dest_view.channel(1).begin());\r
\r
- audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));\r
+ audio_container_.push_back(std::move(resulting_audio_data));\r
+ }\r
+ else\r
+ {\r
+ audio_container_.push_back(\r
+ std::vector<int32_t>(view.raw_begin(), view.raw_end()));\r
+ }\r
\r
- if(FAILED(output_->ScheduleAudioSamples(audio_container_.back().data(), sample_frame_count, audio_scheduled_, format_desc_.audio_sample_rate, nullptr)))\r
+ if(FAILED(output_->ScheduleAudioSamples(\r
+ audio_container_.back().data(),\r
+ sample_frame_count,\r
+ audio_scheduled_,\r
+ format_desc_.audio_sample_rate,\r
+ nullptr)))\r
CASPAR_LOG(error) << print() << L" Failed to schedule audio.";\r
\r
audio_scheduled_ += sample_frame_count;\r
\r
virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override\r
{\r
- CASPAR_VERIFY(audio_cadence_.front() * format_desc_.audio_channels == static_cast<size_t>(frame->audio_data().size()));\r
+ CASPAR_VERIFY(audio_cadence_.front() * frame->num_channels() == static_cast<size_t>(frame->audio_data().size()));\r
boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1);\r
\r
return context_->send(frame);\r
config.device_index = ptree.get(L"device", config.device_index);\r
config.embedded_audio = ptree.get(L"embedded-audio", config.embedded_audio);\r
config.base_buffer_depth = ptree.get(L"buffer-depth", config.base_buffer_depth);\r
+ config.audio_layout =\r
+ core::default_channel_layout_repository().get_by_name(\r
+ boost::to_upper_copy(ptree.get(L"channel-layout", L"STEREO")));\r
\r
return make_safe<decklink_consumer_proxy>(config);\r
}\r
<ClCompile Include="interop\DeckLinkAPI_i.c">\r
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>\r
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Develop|Win32'">NotUsing</PrecompiledHeader>\r
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">NotUsing</PrecompiledHeader>\r
</ClCompile>\r
<ClCompile Include="producer\decklink_producer.cpp">\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
\r
#include <core/monitor/monitor.h>\r
#include <core/mixer/write_frame.h>\r
+#include <core/mixer/audio/audio_util.h>\r
#include <core/producer/frame/frame_transform.h>\r
#include <core/producer/frame/frame_factory.h>\r
\r
\r
tbb::concurrent_bounded_queue<safe_ptr<core::basic_frame>> frame_buffer_;\r
\r
- std::exception_ptr exception_; \r
+ std::exception_ptr exception_; \r
+ int num_input_channels_;\r
+ core::channel_layout audio_channel_layout_;\r
\r
public:\r
- decklink_producer(const core::video_format_desc& format_desc, size_t device_index, const safe_ptr<core::frame_factory>& frame_factory, const std::wstring& filter, std::size_t buffer_depth)\r
+ decklink_producer(\r
+ const core::video_format_desc& format_desc,\r
+ const core::channel_layout& audio_channel_layout,\r
+ size_t device_index,\r
+ const safe_ptr<core::frame_factory>& frame_factory,\r
+ const std::wstring& filter,\r
+ std::size_t buffer_depth)\r
: decklink_(get_device(device_index))\r
, input_(decklink_)\r
, attributes_(decklink_)\r
, filter_(filter)\r
, format_desc_(format_desc)\r
, audio_cadence_(format_desc.audio_cadence)\r
- , muxer_(format_desc.fps, frame_factory, false, filter)\r
+ , muxer_(format_desc.fps, frame_factory, false, audio_channel_layout, filter)\r
, sync_buffer_(format_desc.audio_cadence.size())\r
, frame_factory_(frame_factory)\r
+ , audio_channel_layout_(audio_channel_layout)\r
{ \r
hints_ = 0;\r
frame_buffer_.set_capacity(buffer_depth);\r
+\r
+ if (audio_channel_layout.num_channels <= 2)\r
+ num_input_channels_ = 2;\r
+ else if (audio_channel_layout.num_channels <= 8)\r
+ num_input_channels_ = 8;\r
+ else\r
+ num_input_channels_ = 16;\r
\r
graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f)); \r
graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));\r
<< msg_info(narrow(print()) + " Could not enable video input.")\r
<< boost::errinfo_api_function("EnableVideoInput"));\r
\r
- if(FAILED(input_->EnableAudioInput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, format_desc_.audio_channels))) \r
+ if(FAILED(input_->EnableAudioInput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, num_input_channels_))) \r
BOOST_THROW_EXCEPTION(caspar_exception() \r
<< msg_info(narrow(print()) + " Could not enable audio input.")\r
<< boost::errinfo_api_function("EnableAudioInput"));\r
{\r
auto sample_frame_count = audio->GetSampleFrameCount();\r
auto audio_data = reinterpret_cast<int32_t*>(bytes);\r
- audio_buffer = std::make_shared<core::audio_buffer>(audio_data, audio_data + sample_frame_count*format_desc_.audio_channels);\r
+\r
+ if (num_input_channels_ == audio_channel_layout_.num_channels)\r
+ {\r
+ audio_buffer = std::make_shared<core::audio_buffer>(\r
+ audio_data, \r
+ audio_data + sample_frame_count * num_input_channels_);\r
+ }\r
+ else\r
+ {\r
+ audio_buffer = std::make_shared<core::audio_buffer>();\r
+ audio_buffer->resize(sample_frame_count * audio_channel_layout_.num_channels, 0);\r
+ auto src_view = core::make_multichannel_view<int32_t>(\r
+ audio_data, \r
+ audio_data + sample_frame_count * num_input_channels_, \r
+ audio_channel_layout_, \r
+ num_input_channels_);\r
+ auto dst_view = core::make_multichannel_view<int32_t>(\r
+ audio_buffer->begin(),\r
+ audio_buffer->end(),\r
+ audio_channel_layout_);\r
+\r
+ core::rearrange(src_view, dst_view);\r
+ }\r
}\r
else \r
- audio_buffer = std::make_shared<core::audio_buffer>(audio_cadence_.front() * format_desc_.audio_channels, 0);\r
+ audio_buffer = std::make_shared<core::audio_buffer>(audio_cadence_.front() * audio_channel_layout_.num_channels, 0);\r
\r
// Note: Uses 1 step rotated cadence for 1001 modes (1602, 1602, 1601, 1602, 1601)\r
// This cadence fills the audio mixer most optimally.\r
\r
- sync_buffer_.push_back(audio_buffer->size() / format_desc_.audio_channels); \r
+ sync_buffer_.push_back(audio_buffer->size() / audio_channel_layout_.num_channels); \r
if(!boost::range::equal(sync_buffer_, audio_cadence_))\r
{\r
CASPAR_LOG(trace) << print() << L" Syncing audio.";\r
const uint32_t length_;\r
public:\r
\r
- explicit decklink_producer_proxy(const safe_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, size_t device_index, const std::wstring& filter_str, uint32_t length, std::size_t buffer_depth)\r
+ explicit decklink_producer_proxy(\r
+ const safe_ptr<core::frame_factory>& frame_factory,\r
+ const core::video_format_desc& format_desc,\r
+ const core::channel_layout& audio_channel_layout,\r
+ size_t device_index,\r
+ const std::wstring& filter_str,\r
+ uint32_t length,\r
+ std::size_t buffer_depth)\r
: context_(L"decklink_producer[" + boost::lexical_cast<std::wstring>(device_index) + L"]")\r
, last_frame_(core::basic_frame::empty())\r
, length_(length)\r
{\r
- context_.reset([&]{return new decklink_producer(format_desc, device_index, frame_factory, filter_str, buffer_depth);}); \r
+ context_.reset([&]{return new decklink_producer(format_desc, audio_channel_layout, device_index, frame_factory, filter_str, buffer_depth);}); \r
}\r
\r
// frame_producer\r
\r
- virtual safe_ptr<core::basic_frame> receive(int hints) override\r
+ virtual safe_ptr<core::basic_frame> receive(\r
+ int hints) override\r
{\r
auto frame = context_->get_frame(hints);\r
if(frame != core::basic_frame::late())\r
auto device_index = get_param(L"DEVICE", params, -1);\r
if(device_index == -1)\r
device_index = boost::lexical_cast<int>(params.at(1));\r
- auto filter_str = get_param(L"FILTER", params); \r
- auto length = get_param(L"LENGTH", params, std::numeric_limits<uint32_t>::max()); \r
- auto buffer_depth = get_param(L"BUFFER", params, 2); \r
- auto format_desc = core::video_format_desc::get(get_param(L"FORMAT", params, L"INVALID"));\r
+ auto filter_str = get_param(L"FILTER", params); \r
+ auto length = get_param(L"LENGTH", params, std::numeric_limits<uint32_t>::max()); \r
+ auto buffer_depth = get_param(L"BUFFER", params, 2); \r
+ auto format_desc = core::video_format_desc::get(get_param(L"FORMAT", params, L"INVALID"));\r
+ auto audio_layout = core::create_custom_channel_layout(\r
+ get_param(L"CHANNEL_LAYOUT", params, L"STEREO"),\r
+ core::default_channel_layout_repository());\r
\r
boost::replace_all(filter_str, L"DEINTERLACE", L"YADIF=0:-1");\r
boost::replace_all(filter_str, L"DEINTERLACE_BOB", L"YADIF=1:-1");\r
\r
return create_producer_print_proxy(\r
create_producer_destroy_proxy(\r
- make_safe<decklink_producer_proxy>(frame_factory, format_desc, device_index, filter_str, length, buffer_depth)));\r
+ make_safe<decklink_producer_proxy>(frame_factory, format_desc, audio_layout, device_index, filter_str, length, buffer_depth)));\r
}\r
\r
}}
\ No newline at end of file
\r
const std::shared_ptr<AVFormatContext> oc_;\r
const core::video_format_desc format_desc_;\r
+ const core::channel_layout channel_layout_;\r
\r
const safe_ptr<diagnostics::graph> graph_;\r
\r
bool key_only_;\r
\r
public:\r
- ffmpeg_consumer(const std::string& filename, const core::video_format_desc& format_desc, std::vector<option> options, bool key_only)\r
+ ffmpeg_consumer(const std::string& filename, const core::video_format_desc& format_desc, std::vector<option> options, bool key_only, const core::channel_layout& audio_channel_layout)\r
: filename_(filename)\r
, video_outbuf_(1920*1080*8)\r
, audio_outbuf_(10000)\r
, oc_(avformat_alloc_context(), av_free)\r
, format_desc_(format_desc)\r
+ , channel_layout_(audio_channel_layout)\r
, encode_executor_(print())\r
, in_frame_number_(0)\r
, out_frame_number_(0)\r
c->codec_id = output_format_.acodec;\r
c->codec_type = AVMEDIA_TYPE_AUDIO;\r
c->sample_rate = 48000;\r
- c->channels = 2;\r
+ c->channels = channel_layout_.num_channels;\r
c->sample_fmt = SAMPLE_FMT_S16;\r
\r
if(output_format_.vcodec == CODEC_ID_FLV1) \r
byte_vector convert_audio(core::read_frame& frame, AVCodecContext* c)\r
{\r
if(!swr_) \r
- swr_.reset(new audio_resampler(c->channels, format_desc_.audio_channels, \r
+ swr_.reset(new audio_resampler(c->channels, frame.num_channels(), \r
c->sample_rate, format_desc_.audio_sample_rate,\r
c->sample_fmt, AV_SAMPLE_FMT_S32));\r
\r
const std::wstring filename_;\r
const std::vector<option> options_;\r
const bool separate_key_;\r
+ core::video_format_desc format_desc_;\r
\r
std::unique_ptr<ffmpeg_consumer> consumer_;\r
std::unique_ptr<ffmpeg_consumer> key_only_consumer_;\r
\r
virtual void initialize(const core::video_format_desc& format_desc, int)\r
{\r
- consumer_.reset();\r
- key_only_consumer_.reset();\r
- consumer_.reset(new ffmpeg_consumer(narrow(filename_), format_desc, options_, false));\r
-\r
- if (separate_key_)\r
- {\r
- boost::filesystem::wpath fill_file(filename_);\r
- auto without_extension = fill_file.stem();\r
- auto key_file = env::media_folder() + without_extension + L"_A" + fill_file.extension();\r
- \r
- key_only_consumer_.reset(new ffmpeg_consumer(narrow(key_file), format_desc, options_, true));\r
- }\r
+ format_desc_ = format_desc;\r
}\r
\r
virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override\r
{\r
+ if (!consumer_)\r
+ do_initialize(frame->multichannel_view().channel_layout());\r
+\r
bool ready_for_frame = consumer_->ready_for_frame();\r
\r
if (ready_for_frame && separate_key_)\r
{\r
return 200;\r
}\r
+private:\r
+ void do_initialize(const core::channel_layout& channel_layout)\r
+ {\r
+ consumer_.reset();\r
+ key_only_consumer_.reset();\r
+ consumer_.reset(new ffmpeg_consumer(\r
+ narrow(filename_),\r
+ format_desc_,\r
+ options_,\r
+ false,\r
+ channel_layout));\r
+\r
+ if (separate_key_)\r
+ {\r
+ boost::filesystem::wpath fill_file(filename_);\r
+ auto without_extension = fill_file.stem();\r
+ auto key_file = env::media_folder() + without_extension + L"_A" + fill_file.extension();\r
+ \r
+ key_only_consumer_.reset(new ffmpeg_consumer(\r
+ narrow(key_file),\r
+ format_desc_,\r
+ options_,\r
+ true,\r
+ channel_layout));\r
+ }\r
+ }\r
}; \r
\r
safe_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params)\r
#include "../../ffmpeg_error.h"\r
\r
#include <core/video_format.h>\r
+#include <core/mixer/audio/audio_util.h>\r
\r
#include <tbb/cache_aligned_allocator.h>\r
\r
\r
const int64_t nb_frames_;\r
tbb::atomic<size_t> file_frame_number_;\r
+ core::channel_layout channel_layout_;\r
public:\r
- explicit implementation(const safe_ptr<AVFormatContext>& context, const core::video_format_desc& format_desc) \r
+ explicit implementation(const safe_ptr<AVFormatContext>& context, const core::video_format_desc& format_desc, const std::wstring& custom_channel_order) \r
: format_desc_(format_desc) \r
, codec_context_(open_codec(*context, AVMEDIA_TYPE_AUDIO, index_))\r
- , resampler_(format_desc.audio_channels, codec_context_->channels,\r
+ , resampler_(codec_context_->channels, codec_context_->channels,\r
format_desc.audio_sample_rate, codec_context_->sample_rate,\r
AV_SAMPLE_FMT_S32, codec_context_->sample_fmt)\r
, buffer1_(AVCODEC_MAX_AUDIO_FRAME_SIZE*2)\r
, nb_frames_(0)//context->streams[index_]->nb_frames)\r
- { \r
- file_frame_number_ = 0; \r
+ , channel_layout_(get_audio_channel_layout(*codec_context_, custom_channel_order))\r
+ {\r
+ file_frame_number_ = 0;\r
+\r
+ CASPAR_LOG(debug) << print() \r
+ << " Selected channel layout " << channel_layout_.name;\r
}\r
\r
void push(const std::shared_ptr<AVPacket>& packet)\r
}\r
};\r
\r
-audio_decoder::audio_decoder(const safe_ptr<AVFormatContext>& context, const core::video_format_desc& format_desc) : impl_(new implementation(context, format_desc)){}\r
+audio_decoder::audio_decoder(const safe_ptr<AVFormatContext>& context, const core::video_format_desc& format_desc, const std::wstring& custom_channel_order) : impl_(new implementation(context, format_desc, custom_channel_order)){}\r
void audio_decoder::push(const std::shared_ptr<AVPacket>& packet){impl_->push(packet);}\r
bool audio_decoder::ready() const{return impl_->ready();}\r
std::shared_ptr<core::audio_buffer> audio_decoder::poll(){return impl_->poll();}\r
uint32_t audio_decoder::nb_frames() const{return impl_->nb_frames();}\r
uint32_t audio_decoder::file_frame_number() const{return impl_->file_frame_number_;}\r
+const core::channel_layout& audio_decoder::channel_layout() const { return impl_->channel_layout_; }\r
std::wstring audio_decoder::print() const{return impl_->print();}\r
\r
}}
\ No newline at end of file
namespace core {\r
\r
struct video_format_desc;\r
+struct channel_layout;\r
\r
}\r
\r
class audio_decoder : boost::noncopyable\r
{\r
public:\r
- explicit audio_decoder(const safe_ptr<AVFormatContext>& context, const core::video_format_desc& format_desc);\r
+ explicit audio_decoder(const safe_ptr<AVFormatContext>& context, const core::video_format_desc& format_desc, const std::wstring& custom_channel_order);\r
\r
bool ready() const;\r
void push(const std::shared_ptr<AVPacket>& packet);\r
\r
uint32_t file_frame_number() const;\r
\r
+ const core::channel_layout& channel_layout() const;\r
+\r
std::wstring print() const;\r
private:\r
struct implementation;\r
uint32_t file_frame_number_;\r
\r
public:\r
- explicit ffmpeg_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::wstring& filename, const std::wstring& filter, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode)\r
+ explicit ffmpeg_producer(const safe_ptr<core::frame_factory>& frame_factory, const std::wstring& filename, const std::wstring& filter, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode, const std::wstring& custom_channel_order)\r
: filename_(filename)\r
, frame_factory_(frame_factory) \r
, format_desc_(frame_factory->get_video_format_desc())\r
}\r
}\r
\r
+ core::channel_layout audio_channel_layout = core::default_channel_layout_repository().get_by_name(L"STEREO");\r
+\r
if (!thumbnail_mode_)\r
{\r
try\r
{\r
- audio_decoder_.reset(new audio_decoder(input_.context(), frame_factory->get_video_format_desc()));\r
+ audio_decoder_.reset(new audio_decoder(input_.context(), frame_factory->get_video_format_desc(), custom_channel_order));\r
+ audio_channel_layout = audio_decoder_->channel_layout();\r
CASPAR_LOG(info) << print() << L" " << audio_decoder_->print();\r
}\r
catch(averror_stream_not_found&)\r
if(!video_decoder_ && !audio_decoder_)\r
BOOST_THROW_EXCEPTION(averror_stream_not_found() << msg_info("No streams found"));\r
\r
- muxer_.reset(new frame_muxer(fps_, frame_factory, thumbnail_mode_, filter));\r
+ muxer_.reset(new frame_muxer(fps_, frame_factory, thumbnail_mode_, audio_channel_layout, filter));\r
}\r
\r
// frame_producer\r
if(filename.empty())\r
return core::frame_producer::empty();\r
\r
- auto loop = boost::range::find(params, L"LOOP") != params.end();\r
- auto start = get_param(L"SEEK", params, static_cast<uint32_t>(0));\r
- auto length = get_param(L"LENGTH", params, std::numeric_limits<uint32_t>::max());\r
- auto filter_str = get_param(L"FILTER", params, L""); \r
- \r
+ auto loop = boost::range::find(params, L"LOOP") != params.end();\r
+ auto start = get_param(L"SEEK", params, static_cast<uint32_t>(0));\r
+ auto length = get_param(L"LENGTH", params, std::numeric_limits<uint32_t>::max());\r
+ auto filter_str = get_param(L"FILTER", params, L""); \r
+ auto custom_channel_order = get_param(L"CHANNEL_LAYOUT", params, L"");\r
+\r
boost::replace_all(filter_str, L"DEINTERLACE", L"YADIF=0:-1");\r
boost::replace_all(filter_str, L"DEINTERLACE_BOB", L"YADIF=1:-1");\r
\r
- return create_producer_destroy_proxy(make_safe<ffmpeg_producer>(frame_factory, filename, filter_str, loop, start, length, false));\r
+ return create_producer_destroy_proxy(make_safe<ffmpeg_producer>(frame_factory, filename, filter_str, loop, start, length, false, custom_channel_order));\r
}\r
\r
safe_ptr<core::frame_producer> create_thumbnail_producer(\r
auto length = std::numeric_limits<uint32_t>::max();\r
auto filter_str = L"";\r
\r
- return create_producer_destroy_proxy(make_safe<ffmpeg_producer>(frame_factory, filename, filter_str, loop, start, length, true));\r
+ return create_producer_destroy_proxy(make_safe<ffmpeg_producer>(frame_factory, filename, filter_str, loop, start, length, true, L""));\r
}\r
\r
}}
\ No newline at end of file
#include <core/producer/frame/pixel_format.h>\r
#include <core/producer/frame/frame_factory.h>\r
#include <core/mixer/write_frame.h>\r
+#include <core/mixer/audio/audio_util.h>\r
\r
#include <common/env.h>\r
#include <common/exception/exceptions.h>\r
const std::wstring filter_str_;\r
const bool thumbnail_mode_;\r
bool force_deinterlacing_;\r
+ const core::channel_layout audio_channel_layout_;\r
\r
- implementation(double in_fps, const safe_ptr<core::frame_factory>& frame_factory, const std::wstring& filter_str, bool thumbnail_mode)\r
+ implementation(\r
+ double in_fps,\r
+ const safe_ptr<core::frame_factory>& frame_factory,\r
+ const std::wstring& filter_str,\r
+ bool thumbnail_mode,\r
+ const core::channel_layout& audio_channel_layout)\r
: display_mode_(display_mode::invalid)\r
, in_fps_(in_fps)\r
, format_desc_(frame_factory->get_video_format_desc())\r
, filter_str_(filter_str)\r
, thumbnail_mode_(thumbnail_mode)\r
, force_deinterlacing_(false)\r
+ , audio_channel_layout_(audio_channel_layout)\r
{\r
video_streams_.push(std::queue<safe_ptr<write_frame>>());\r
audio_streams_.push(core::audio_buffer());\r
}\r
else if(video_frame == empty_video())\r
{\r
- video_streams_.back().push(make_safe<core::write_frame>(this));\r
+ video_streams_.back().push(make_safe<core::write_frame>(this, audio_channel_layout_));\r
display_mode_ = display_mode::simple;\r
}\r
else\r
if(video_frame->format == PIX_FMT_GRAY8 && format == CASPAR_PIX_FMT_LUMA)\r
av_frame->format = format;\r
\r
- video_streams_.back().push(make_write_frame(this, av_frame, frame_factory_, hints));\r
+ video_streams_.back().push(make_write_frame(this, av_frame, frame_factory_, hints, audio_channel_layout_));\r
}\r
}\r
\r
}\r
else if(audio == empty_audio())\r
{\r
- boost::range::push_back(audio_streams_.back(), core::audio_buffer(audio_cadence_.front() * format_desc_.audio_channels, 0));\r
+ boost::range::push_back(audio_streams_.back(), core::audio_buffer(audio_cadence_.front() * audio_channel_layout_.num_channels, 0));\r
}\r
else\r
{\r
boost::range::push_back(audio_streams_.back(), *audio);\r
}\r
\r
- if(audio_streams_.back().size() > 32*audio_cadence_.front() * format_desc_.audio_channels)\r
+ if(audio_streams_.back().size() > 32*audio_cadence_.front() * audio_channel_layout_.num_channels)\r
BOOST_THROW_EXCEPTION(invalid_operation() << source_info("frame_muxer") << msg_info("audio-stream overflow. This can be caused by incorrect frame-rate. Check clip meta-data."));\r
}\r
\r
switch(display_mode_)\r
{\r
case display_mode::duplicate: \r
- return audio_streams_.front().size()/2 >= audio_cadence_.front() * format_desc_.audio_channels;\r
+ return audio_streams_.front().size()/2 >= audio_cadence_.front() * audio_channel_layout_.num_channels;\r
default: \r
- return audio_streams_.front().size() >= audio_cadence_.front() * format_desc_.audio_channels;\r
+ return audio_streams_.front().size() >= audio_cadence_.front() * audio_channel_layout_.num_channels;\r
}\r
}\r
\r
\r
core::audio_buffer pop_audio()\r
{\r
- CASPAR_VERIFY(audio_streams_.front().size() >= audio_cadence_.front() * format_desc_.audio_channels);\r
+ CASPAR_VERIFY(audio_streams_.front().size() >= audio_cadence_.front() * audio_channel_layout_.num_channels);\r
\r
auto begin = audio_streams_.front().begin();\r
- auto end = begin + (audio_cadence_.front() * format_desc_.audio_channels);\r
+ auto end = begin + (audio_cadence_.front() * audio_channel_layout_.num_channels);\r
\r
core::audio_buffer samples(begin, end);\r
audio_streams_.front().erase(begin, end);\r
filter_.push(frame);\r
auto av_frame = filter_.poll();\r
if(av_frame) \r
- video_streams_.back().push(make_write_frame(this, make_safe_ptr(av_frame), frame_factory_, 0));\r
+ video_streams_.back().push(make_write_frame(this, make_safe_ptr(av_frame), frame_factory_, 0, audio_channel_layout_));\r
}\r
filter_ = filter(filter_str);\r
if (!thumbnail_mode_)\r
}\r
};\r
\r
-frame_muxer::frame_muxer(double in_fps, const safe_ptr<core::frame_factory>& frame_factory, bool thumbnail_mode, const std::wstring& filter)\r
- : impl_(new implementation(in_fps, frame_factory, filter, thumbnail_mode)){}\r
+frame_muxer::frame_muxer(\r
+ double in_fps,\r
+ const safe_ptr<core::frame_factory>& frame_factory,\r
+ bool thumbnail_mode,\r
+ const core::channel_layout& audio_channel_layout,\r
+ const std::wstring& filter)\r
+ : impl_(new implementation(in_fps, frame_factory, filter, thumbnail_mode, audio_channel_layout)){}\r
void frame_muxer::push(const std::shared_ptr<AVFrame>& video_frame, int hints){impl_->push(video_frame, hints);}\r
void frame_muxer::push(const std::shared_ptr<core::audio_buffer>& audio_samples){return impl_->push(audio_samples);}\r
std::shared_ptr<basic_frame> frame_muxer::poll(){return impl_->poll();}\r
class write_frame;\r
class basic_frame;\r
struct frame_factory;\r
+struct channel_layout;\r
\r
}\r
\r
class frame_muxer : boost::noncopyable\r
{\r
public:\r
- frame_muxer(double in_fps, const safe_ptr<core::frame_factory>& frame_factory, bool thumbnail_mode, const std::wstring& filter = L"");\r
+ frame_muxer(\r
+ double in_fps,\r
+ const safe_ptr<core::frame_factory>& frame_factory,\r
+ bool thumbnail_mode,\r
+ const core::channel_layout& audio_channel_layout,\r
+ const std::wstring& filter = L"");\r
\r
void push(const std::shared_ptr<AVFrame>& video_frame, int hints = 0);\r
void push(const std::shared_ptr<core::audio_buffer>& audio_samples);\r
#include <core/producer/frame/frame_factory.h>\r
#include <core/producer/frame_producer.h>\r
#include <core/mixer/write_frame.h>\r
+#include <core/mixer/audio/audio_util.h>\r
\r
#include <common/exception/exceptions.h>\r
#include <common/utility/assert.h>\r
\r
#include <boost/filesystem.hpp>\r
#include <boost/lexical_cast.hpp>\r
+#include <boost/algorithm/string.hpp>\r
\r
#if defined(_MSC_VER)\r
#pragma warning (push)\r
}\r
}\r
\r
-safe_ptr<core::write_frame> make_write_frame(const void* tag, const safe_ptr<AVFrame>& decoded_frame, const safe_ptr<core::frame_factory>& frame_factory, int hints)\r
+safe_ptr<core::write_frame> make_write_frame(const void* tag, const safe_ptr<AVFrame>& decoded_frame, const safe_ptr<core::frame_factory>& frame_factory, int hints, const core::channel_layout& audio_channel_layout)\r
{ \r
static tbb::concurrent_unordered_map<int64_t, tbb::concurrent_queue<std::shared_ptr<SwsContext>>> sws_contexts_;\r
\r
if(decoded_frame->width < 1 || decoded_frame->height < 1)\r
- return make_safe<core::write_frame>(tag);\r
+ return make_safe<core::write_frame>(tag, audio_channel_layout);\r
\r
const auto width = decoded_frame->width;\r
const auto height = decoded_frame->height;\r
\r
auto target_desc = get_pixel_format_desc(target_pix_fmt, width, height);\r
\r
- write = frame_factory->create_frame(tag, target_desc);\r
+ write = frame_factory->create_frame(tag, target_desc, audio_channel_layout);\r
write->set_type(get_mode(*decoded_frame));\r
\r
std::shared_ptr<SwsContext> sws_context;\r
}\r
else\r
{\r
- write = frame_factory->create_frame(tag, desc);\r
+ write = frame_factory->create_frame(tag, desc, audio_channel_layout);\r
write->set_type(get_mode(*decoded_frame));\r
\r
for(int n = 0; n < static_cast<int>(desc.planes.size()); ++n)\r
}\r
return L"";\r
}\r
+\r
+core::channel_layout get_audio_channel_layout(\r
+ const AVCodecContext& context, const std::wstring& custom_channel_order)\r
+{\r
+ if (!custom_channel_order.empty())\r
+ {\r
+ auto layout = core::create_custom_channel_layout(\r
+ custom_channel_order,\r
+ core::default_channel_layout_repository());\r
+\r
+ layout.num_channels = context.channels;\r
+\r
+ return layout;\r
+ }\r
+\r
+ int64_t ch_layout = context.channel_layout;\r
+\r
+ if (ch_layout == 0)\r
+ ch_layout = av_get_default_channel_layout(context.channels);\r
+\r
+ switch (ch_layout) // TODO: refine this auto-detection\r
+ {\r
+ case AV_CH_LAYOUT_MONO:\r
+ return core::default_channel_layout_repository().get_by_name(L"MONO");\r
+ case AV_CH_LAYOUT_STEREO:\r
+ return core::default_channel_layout_repository().get_by_name(L"STEREO");\r
+ case AV_CH_LAYOUT_5POINT1:\r
+ case AV_CH_LAYOUT_5POINT1_BACK:\r
+ return core::default_channel_layout_repository().get_by_name(L"SMPTE");\r
+ case AV_CH_LAYOUT_7POINT1:\r
+ return core::default_channel_layout_repository().get_by_name(L"DOLBYE");\r
+ }\r
+\r
+ return core::create_unspecified_layout(context.channels);\r
+}\r
+\r
//\r
//void av_dup_frame(AVFrame* frame)\r
//{\r
struct pixel_format_desc;\r
class write_frame;\r
struct frame_factory;\r
+struct channel_layout;\r
\r
}\r
\r
\r
core::field_mode::type get_mode(const AVFrame& frame);\r
int make_alpha_format(int format); // NOTE: Be careful about CASPAR_PIX_FMT_LUMA, change it to PIX_FMT_GRAY8 if you want to use the frame inside some ffmpeg function.\r
-safe_ptr<core::write_frame> make_write_frame(const void* tag, const safe_ptr<AVFrame>& decoded_frame, const safe_ptr<core::frame_factory>& frame_factory, int hints);\r
+safe_ptr<core::write_frame> make_write_frame(const void* tag, const safe_ptr<AVFrame>& decoded_frame, const safe_ptr<core::frame_factory>& frame_factory, int hints, const core::channel_layout& audio_channel_layout);\r
\r
safe_ptr<AVPacket> create_packet();\r
\r
bool is_valid_file(const std::wstring filename, const std::vector<std::wstring>& invalid_exts);\r
bool is_valid_file(const std::wstring filename);\r
\r
+core::channel_layout get_audio_channel_layout(const AVCodecContext& context, const std::wstring& custom_channel_order);\r
+\r
}}
\ No newline at end of file
\r
#include <core/producer/frame/frame_factory.h>\r
#include <core/mixer/write_frame.h>\r
+#include <core/mixer/audio/audio_util.h>\r
\r
#include <common/env.h>\r
\r
struct dummy_factory : public core::frame_factory\r
{\r
\r
- virtual safe_ptr<core::write_frame> create_frame(const void* video_stream_tag, const core::pixel_format_desc& desc) \r
+ virtual safe_ptr<core::write_frame> create_frame(const void* video_stream_tag, const core::pixel_format_desc& desc, const core::channel_layout& layout) \r
{\r
- return make_safe<core::write_frame>(nullptr);\r
+ return make_safe<core::write_frame>(nullptr, layout);\r
}\r
\r
virtual core::video_format_desc get_video_format_desc() const\r
core::audio_buffer temp;
core::video_format_desc format_desc_;
+ core::channel_layout channel_layout_;
public:
oal_consumer()
: container_(16)
, channel_index_(-1)
+ , channel_layout_(
+ core::default_channel_layout_repository().get_by_name(
+ L"STEREO"))
{
graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override
{
- auto buffer = std::make_shared<audio_buffer_16>(core::audio_32_to_16(frame->audio_data()));
+ std::shared_ptr<audio_buffer_16> buffer;
+
+ if (core::needs_rearranging(
+ frame->multichannel_view(),
+ channel_layout_,
+ channel_layout_.num_channels))
+ {
+ core::audio_buffer downmixed;
+ downmixed.resize(
+ frame->multichannel_view().num_samples()
+ * channel_layout_.num_channels,
+ 0);
+
+ auto dest_view = core::make_multichannel_view<int32_t>(
+ downmixed.begin(), downmixed.end(), channel_layout_);
+
+ core::rearrange_or_rearrange_and_mix(
+ frame->multichannel_view(),
+ dest_view,
+ core::default_mix_config_repository());
+
+ buffer = std::make_shared<audio_buffer_16>(
+ core::audio_32_to_16(downmixed));
+ }
+ else
+ {
+ buffer = std::make_shared<audio_buffer_16>(
+ core::audio_32_to_16(frame->audio_data()));
+ }
if (!input_.try_push(buffer))
graph_->set_tag("dropped-frame");
}\r
else\r
{\r
- SetReplyString(L"500 THUMBNAIL GENERATE ERROR\r\n");\r
+ SetReplyString(L"501 THUMBNAIL GENERATE ERROR\r\n");\r
return false;\r
}\r
}\r
}\r
else\r
{\r
- SetReplyString(L"500 THUMBNAIL GENERATE_ALL ERROR\r\n");\r
+ SetReplyString(L"501 THUMBNAIL GENERATE_ALL ERROR\r\n");\r
return false;\r
}\r
}\r
<channels>\r
<channel>\r
<video-mode>PAL</video-mode>\r
+ <channel-layout>stereo</channel-layout>\r
<consumers>\r
<screen>\r
<device>1</device>\r
<protocol>OSC</protocol>\r
</udp>\r
</controllers>\r
+ <audio>\r
+ <channel-layouts>\r
+ </channel-layouts>\r
+ <mix-configs>\r
+ </mix-configs>\r
+ </audio>\r
</configuration>\r
\r
<!--\r
<channels>\r
<channel>\r
<video-mode> PAL [PAL|NTSC|576p2500|720p2398|720p2400|720p2500|720p5000|720p2997|720p5994|720p3000|720p6000|1080p2398|1080p2400|1080i5000|1080i5994|1080i6000|1080p2500|1080p2997|1080p3000|1080p5000|1080p5994|1080p6000|2k2398|2k2400|2k2500|4k2398|4k2400|4k2500|4k2997|4k3000] </video-mode>\r
+ <channel-layout>stereo [mono|stereo|dts|dolbye|dolbydigital|smpte|passthru]</channel-layout>\r
<consumers>\r
<decklink>\r
<device>[1..]</device>\r
<embedded-audio>false [true|false]</embedded-audio>\r
+ <channel-layout>stereo [mono|stereo|dts|dolbye|dolbydigital|smpte|passthru]</channel-layout>\r
<latency>normal [normal|low|default]</latency>\r
<keyer>external [external|internal|default]</keyer>\r
<key-only>false [true|false]</key-only>\r
<bluefish>\r
<device>[1..]</device>\r
<embedded-audio>false [true|false]</embedded-audio>\r
+ <channel-layout>stereo [mono|stereo|dts|dolbye|dolbydigital|smpte|passthru]</channel-layout>\r
<key-only>false [true|false]</key-only>\r
</bluefish>\r
<system-audio></system-audio>\r
</consumers>\r
</channel>\r
</channels>\r
+<audio>\r
+ <channel-layouts>\r
+ <mono>\r
+ <type>1.0</type>\r
+ <num-channels>1</num-channels>\r
+ <channels>C</channels>\r
+ </mono>\r
+ <stereo>\r
+ <type>2.0</type>\r
+ <num-channels>2</num-channels>\r
+ <channels>L R</channels>\r
+ </stereo>\r
+ <dts>\r
+ <type>5.1</type>\r
+ <num-channels>6</num-channels>\r
+ <channels>C L R Ls Rs LFE</channels>\r
+ </dts>\r
+ <dolbye>\r
+ <type>5.1+stereomix</type>\r
+ <num-channels>8</num-channels>\r
+ <channels>L R C LFE Ls Rs Lmix Rmix</channels>\r
+ </dolbye>\r
+ <dolbydigital>\r
+ <type>5.1</type>\r
+ <num-channels>6</num-channels>\r
+ <channels>L C R Ls Rs LFE</channels>\r
+ </dolbydigital>\r
+ <smpte>\r
+ <type>5.1</type>\r
+ <num-channels>6</num-channels>\r
+ <channels>L R C LFE Ls Rs</channels>\r
+ </smpte>\r
+ <passthru>\r
+ <type>16ch</type>\r
+ <num-channels>16</num-channels>\r
+ <channels />\r
+ </passthru>\r
+ </channel-layouts>\r
+ <mix-configs>\r
+ <mix-config>\r
+ <from>1.0</from>\r
+ <to>2.0</to>\r
+ <mix>add</mix>\r
+ <mappings>\r
+ <mapping>C L 1.0</mapping>\r
+ <mapping>C R 1.0</mapping>\r
+ </mappings>\r
+ </mix-config>\r
+ <mix-config>\r
+ <from>1.0</from>\r
+ <to>5.1</to>\r
+ <mix>add</mix>\r
+ <mappings>\r
+ <mapping>C L 1.0</mapping>\r
+ <mapping>C R 1.0</mapping>\r
+ </mappings>\r
+ </mix-config>\r
+ <mix-config>\r
+ <from>1.0</from>\r
+ <to>5.1+stereomix</to>\r
+ <mix>add</mix>\r
+ <mappings>\r
+ <mapping>C L 1.0</mapping>\r
+ <mapping>C R 1.0</mapping>\r
+ <mapping>C Lmix 1.0</mapping>\r
+ <mapping>C Rmix 1.0</mapping>\r
+ </mappings>\r
+ </mix-config>\r
+ <mix-config>\r
+ <from>2.0</from>\r
+ <to>1.0</to>\r
+ <mix>add</mix>\r
+ <mappings>\r
+ <mapping>L C 1.0</mapping>\r
+ <mapping>R C 1.0</mapping>\r
+ </mappings>\r
+ </mix-config>\r
+ <mix-config>\r
+ <from>2.0</from>\r
+ <to>5.1</to>\r
+ <mix>add</mix>\r
+ <mappings>\r
+ <mapping>L L 1.0</mapping>\r
+ <mapping>R R 1.0</mapping>\r
+ </mappings>\r
+ </mix-config>\r
+ <mix-config>\r
+ <from>2.0</from>\r
+ <to>5.1+stereomix</to>\r
+ <mix>add</mix>\r
+ <mappings>\r
+ <mapping>L L 1.0</mapping>\r
+ <mapping>R R 1.0</mapping>\r
+ <mapping>L Lmix 1.0</mapping>\r
+ <mapping>R Rmix 1.0</mapping>\r
+ </mappings>\r
+ </mix-config>\r
+ <mix-config>\r
+ <from>5.1</from>\r
+ <to>1.0</to>\r
+ <mix>average</mix>\r
+ <mappings>\r
+ <mapping>L C 1.0</mapping>\r
+ <mapping>R C 1.0</mapping>\r
+ <mapping>C C 0.707</mapping>\r
+ <mapping>Ls C 0.707</mapping>\r
+ <mapping>Rs C 0.707</mapping>\r
+ </mappings>\r
+ </mix-config>\r
+ <mix-config>\r
+ <from>5.1</from>\r
+ <to>2.0</to>\r
+ <mix>average</mix>\r
+ <mappings>\r
+ <mapping>L L 1.0</mapping>\r
+ <mapping>R R 1.0</mapping>\r
+ <mapping>C L 0.707</mapping>\r
+ <mapping>C R 0.707</mapping>\r
+ <mapping>Ls L 0.707</mapping>\r
+ <mapping>Rs R 0.707</mapping>\r
+ </mappings>\r
+ </mix-config>\r
+ <mix-config>\r
+ <from>5.1</from>\r
+ <to>5.1+stereomix</to>\r
+ <mix>average</mix>\r
+ <mappings>\r
+ <mapping>L L 1.0</mapping>\r
+ <mapping>R R 1.0</mapping>\r
+ <mapping>C C 1.0</mapping>\r
+ <mapping>Ls Ls 1.0</mapping>\r
+ <mapping>Rs Rs 1.0</mapping>\r
+ <mapping>LFE LFE 1.0</mapping>\r
+\r
+ <mapping>L Lmix 1.0</mapping>\r
+ <mapping>R Rmix 1.0</mapping>\r
+ <mapping>C Lmix 0.707</mapping>\r
+ <mapping>C Rmix 0.707</mapping>\r
+ <mapping>Ls Lmix 0.707</mapping>\r
+ <mapping>Rs Rmix 0.707</mapping>\r
+ </mappings>\r
+ </mix-config>\r
+ <mix-config>\r
+ <from>5.1+stereomix</from>\r
+ <to>1.0</to>\r
+ <mix>add</mix>\r
+ <mappings>\r
+ <mapping>Lmix C 1.0</mapping>\r
+ <mapping>Rmix C 1.0</mapping>\r
+ </mappings>\r
+ </mix-config>\r
+ <mix-config>\r
+ <from>5.1+stereomix</from>\r
+ <to>2.0</to>\r
+ <mix>add</mix>\r
+ <mappings>\r
+ <mapping>Lmix L 1.0</mapping>\r
+ <mapping>Rmix R 1.0</mapping>\r
+ </mappings>\r
+ </mix-config>\r
+ <mix-config>\r
+ <from>5.1+stereomix</from>\r
+ <to>5.1</to>\r
+ <mix>add</mix>\r
+ <mappings>\r
+ <mapping>L L 1.0</mapping>\r
+ <mapping>R R 1.0</mapping>\r
+ <mapping>C C 1.0</mapping>\r
+ <mapping>Ls Ls 1.0</mapping>\r
+ <mapping>Rs Rs 1.0</mapping>\r
+ <mapping>LFE LFE 1.0</mapping>\r
+ </mappings>\r
+ </mix-config>\r
+ </mix-configs>\r
+</audio>\r
-->\r
#include <common/filesystem/polling_filesystem_monitor.h>\r
\r
#include <core/mixer/gpu/ogl_device.h>\r
+#include <core/mixer/audio/audio_util.h>\r
#include <core/video_channel.h>\r
#include <core/producer/stage.h>\r
#include <core/consumer/output.h>\r
: shutdown_server_now_(shutdown_server_now)\r
, ogl_(ogl_device::create())\r
{ \r
+ setup_audio(env::properties());\r
+\r
ffmpeg::init();\r
CASPAR_LOG(info) << L"Initialized ffmpeg module.";\r
\r
async_servers_.clear();\r
channels_.clear();\r
}\r
+\r
+ void setup_audio(const boost::property_tree::wptree& pt)\r
+ {\r
+ register_default_channel_layouts(default_channel_layout_repository());\r
+ register_default_mix_configs(default_mix_config_repository());\r
+ parse_channel_layouts(\r
+ default_channel_layout_repository(),\r
+ pt.get_child(L"configuration.audio.channel-layouts"));\r
+ parse_mix_configs(\r
+ default_mix_config_repository(),\r
+ pt.get_child(L"configuration.audio.mix-configs"));\r
+ }\r
\r
void setup_channels(const boost::property_tree::wptree& pt)\r
{ \r
auto format_desc = video_format_desc::get(widen(xml_channel.second.get(L"video-mode", L"PAL"))); \r
if(format_desc.format == video_format::invalid)\r
BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Invalid video-mode."));\r
+ auto audio_channel_layout = default_channel_layout_repository().get_by_name(\r
+ boost::to_upper_copy(xml_channel.second.get(L"channel-layout", L"STEREO")));\r
\r
- channels_.push_back(make_safe<video_channel>(channels_.size()+1, format_desc, ogl_));\r
+ channels_.push_back(make_safe<video_channel>(channels_.size()+1, format_desc, ogl_, audio_channel_layout));\r
\r
channels_.back()->monitor_output().link_target(&monitor_subject_);\r
\r
\r
// Dummy diagnostics channel\r
if(env::properties().get(L"configuration.channel-grid", false))\r
- channels_.push_back(make_safe<video_channel>(channels_.size()+1, core::video_format_desc::get(core::video_format::x576p2500), ogl_));\r
+ channels_.push_back(make_safe<video_channel>(channels_.size()+1, core::video_format_desc::get(core::video_format::x576p2500), ogl_, default_channel_layout_repository().get_by_name(L"STEREO")));\r
}\r
\r
void setup_controllers(const boost::property_tree::wptree& pt)\r