#include <common/utf.h>
#include <common/env.h>
#include <common/future.h>
+#include <common/param.h>
#include <core/consumer/frame_consumer.h>
#include <core/frame/frame.h>
+#include <core/frame/audio_channel_layout.h>
#include <core/mixer/audio/audio_util.h>
#include <core/mixer/audio/audio_mixer.h>
#include <core/video_format.h>
if(!context_)
CASPAR_THROW_EXCEPTION(invalid_operation() << msg_info("Failed to create audio context."));
-
+
if(alcMakeContextCurrent(context_) == ALC_FALSE)
CASPAR_THROW_EXCEPTION(invalid_operation() << msg_info("Failed to activate audio context."));
}
{
static std::unique_ptr<device> instance;
static boost::once_flag f = BOOST_ONCE_INIT;
-
+
boost::call_once(f, []{instance.reset(new device());});
}
struct oal_consumer : public core::frame_consumer
{
- core::monitor::subject monitor_subject_;
+ core::monitor::subject monitor_subject_;
- spl::shared_ptr<diagnostics::graph> graph_;
- boost::timer perf_timer_;
- tbb::atomic<int64_t> presentation_age_;
- int channel_index_ = -1;
-
- core::video_format_desc format_desc_;
+ spl::shared_ptr<diagnostics::graph> graph_;
+ boost::timer perf_timer_;
+ tbb::atomic<int64_t> presentation_age_;
+ int channel_index_ = -1;
- ALuint source_ = 0;
- std::vector<ALuint> buffers_;
+ core::video_format_desc format_desc_;
+ core::audio_channel_layout out_channel_layout_;
+ std::unique_ptr<core::audio_channel_remapper> channel_remapper_;
- executor executor_ { L"oal_consumer" };
+ ALuint source_ = 0;
+ std::vector<ALuint> buffers_;
+ int latency_millis_;
+
+ executor executor_ { L"oal_consumer" };
public:
- oal_consumer()
+ oal_consumer(const core::audio_channel_layout& out_channel_layout, int latency_millis)
+ : out_channel_layout_(out_channel_layout)
+ , latency_millis_(latency_millis)
{
presentation_age_ = 0;
init_device();
- graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
+ 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));
graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
diagnostics::register_graph(graph_);
~oal_consumer()
{
executor_.invoke([=]
- {
+ {
if(source_)
{
alSourceStop(source_);
// frame consumer
- void initialize(const core::video_format_desc& format_desc, int channel_index) override
+ void initialize(const core::video_format_desc& format_desc, const core::audio_channel_layout& channel_layout, int channel_index) override
{
- format_desc_ = format_desc;
+ format_desc_ = format_desc;
channel_index_ = channel_index;
+ if (out_channel_layout_ == core::audio_channel_layout::invalid())
+ out_channel_layout_ = channel_layout.num_channels == 2 ? channel_layout : *core::audio_channel_layout_repository::get_default()->get_layout(L"stereo");
+
+ out_channel_layout_.num_channels = 2;
+
+ channel_remapper_.reset(new core::audio_channel_remapper(channel_layout, out_channel_layout_));
graph_->set_text(print());
executor_.begin_invoke([=]
- {
+ {
buffers_.resize(format_desc_.fps > 30 ? 8 : 4);
alGenBuffers(static_cast<ALsizei>(buffers_.size()), buffers_.data());
alGenSources(1, &source_);
for(std::size_t n = 0; n < buffers_.size(); ++n)
{
- audio_buffer_16 audio(format_desc_.audio_cadence[n % format_desc_.audio_cadence.size()]*format_desc_.audio_channels, 0);
+ audio_buffer_16 audio(format_desc_.audio_cadence[n % format_desc_.audio_cadence.size()]*2, 0);
alBufferData(buffers_[n], AL_FORMAT_STEREO16, audio.data(), static_cast<ALsizei>(audio.size()*sizeof(int16_t)), format_desc_.audio_sample_rate);
alSourceQueueBuffers(source_, 1, &buffers_[n]);
}
-
+
alSourcei(source_, AL_LOOPING, AL_FALSE);
- alSourcePlay(source_);
+ alSourcePlay(source_);
});
}
// exhausted, which should not happen
executor_.begin_invoke([=]
{
- ALenum state;
+ ALenum state;
alGetSourcei(source_, AL_SOURCE_STATE,&state);
if(state != AL_PLAYING)
{
for(int n = 0; n < buffers_.size()-1; ++n)
- {
- ALuint buffer = 0;
+ {
+ ALuint buffer = 0;
alSourceUnqueueBuffers(source_, 1, &buffer);
if(buffer)
{
- std::vector<int16_t> audio(format_desc_.audio_cadence[n % format_desc_.audio_cadence.size()] * format_desc_.audio_channels, 0);
+ std::vector<int16_t> audio(format_desc_.audio_cadence[n % format_desc_.audio_cadence.size()] * 2, 0);
alBufferData(buffer, AL_FORMAT_STEREO16, audio.data(), static_cast<ALsizei>(audio.size()*sizeof(int16_t)), format_desc_.audio_sample_rate);
alSourceQueueBuffers(source_, 1, &buffer);
}
}
- alSourcePlay(source_);
- graph_->set_tag("late-frame");
+ alSourcePlay(source_);
+ graph_->set_tag(diagnostics::tag_severity::WARNING, "late-frame");
}
- auto audio = core::audio_32_to_16(frame.audio_data());
-
- ALuint buffer = 0;
+ auto audio = core::audio_32_to_16(channel_remapper_->mix_and_rearrange(frame.audio_data()));
+
+ ALuint buffer = 0;
alSourceUnqueueBuffers(source_, 1, &buffer);
if(buffer)
{
alSourceQueueBuffers(source_, 1, &buffer);
}
else
- graph_->set_tag("dropped-frame");
+ graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame");
- graph_->set_value("tick-time", perf_timer_.elapsed()*format_desc_.fps*0.5);
+ graph_->set_value("tick-time", perf_timer_.elapsed()*format_desc_.fps*0.5);
perf_timer_.restart();
- presentation_age_ = frame.get_age_millis() + delay_millis();
+ presentation_age_ = frame.get_age_millis() + latency_millis();
});
return make_ready_future(true);
}
-
+
std::wstring print() const override
{
return L"oal[" + boost::lexical_cast<std::wstring>(channel_index_) + L"|" + format_desc_.name + L"]";
info.add(L"type", L"system-audio");
return info;
}
-
+
bool has_synchronization_clock() const override
{
return false;
}
- int delay_millis() const
+ int latency_millis() const
{
- return 213;
+ return latency_millis_;
}
-
+
int buffer_depth() const override
{
- int delay_in_frames = static_cast<int>(delay_millis() / (1000.0 / format_desc_.fps));
-
+ int delay_in_frames = static_cast<int>(latency_millis() / (1000.0 / format_desc_.fps));
+
return delay_in_frames;
}
-
+
int index() const override
{
return 500;
void describe_consumer(core::help_sink& sink, const core::help_repository& repo)
{
sink.short_description(L"A system audio consumer.");
- sink.syntax(L"AUDIO");
+ sink.syntax(L"AUDIO {CHANNEL_LAYOUT [channel_layout:string]} {LATENCY [latency_millis:int|200]}");
sink.para()->text(L"Uses the system's default audio playback device.");
sink.para()->text(L"Examples:");
sink.example(L">> ADD 1 AUDIO");
+ sink.example(L">> ADD 1 AUDIO CHANNEL_LAYOUT matrix", L"Uses the matrix channel layout");
+ sink.example(L">> ADD 1 AUDIO LATENCY 500", L"Specifies that the system-audio chain: openal => driver => sound card => speaker output is 500ms");
}
-spl::shared_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params, core::interaction_sink*)
+spl::shared_ptr<core::frame_consumer> create_consumer(
+ const std::vector<std::wstring>& params, core::interaction_sink*, std::vector<spl::shared_ptr<core::video_channel>> channels)
{
if(params.size() < 1 || !boost::iequals(params.at(0), L"AUDIO"))
return core::frame_consumer::empty();
- return spl::make_shared<oal_consumer>();
+ auto channel_layout = core::audio_channel_layout::invalid();
+ auto channel_layout_spec = get_param(L"CHANNEL_LAYOUT", params);
+
+ if (!channel_layout_spec.empty())
+ {
+ auto found_layout = core::audio_channel_layout_repository::get_default()->get_layout(channel_layout_spec);
+
+ if (!found_layout)
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Channel layout " + channel_layout_spec + L" not found."));
+
+ channel_layout = *found_layout;
+ }
+
+ auto latency_millis = get_param(L"LATENCY", params, 200);
+
+ return spl::make_shared<oal_consumer>(channel_layout, latency_millis);
}
-spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(const boost::property_tree::wptree&, core::interaction_sink*)
+spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(
+ const boost::property_tree::wptree& ptree, core::interaction_sink*, std::vector<spl::shared_ptr<core::video_channel>> channels)
{
- return spl::make_shared<oal_consumer>();
+ auto channel_layout = core::audio_channel_layout::invalid();
+ auto channel_layout_spec = ptree.get_optional<std::wstring>(L"channel-layout");
+
+ if (channel_layout_spec)
+ {
+ CASPAR_SCOPED_CONTEXT_MSG("/channel-layout")
+
+ auto found_layout = core::audio_channel_layout_repository::get_default()->get_layout(*channel_layout_spec);
+
+ if (!found_layout)
+ CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Channel layout " + *channel_layout_spec + L" not found."));
+
+ channel_layout = *found_layout;
+ }
+
+ auto latency_millis = ptree.get(L"latency", 200);
+
+ return spl::make_shared<oal_consumer>(channel_layout, latency_millis);
}
}}