2 * Copyright 2013 Sveriges Television AB http://casparcg.com/
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Helge Norberg, helge.norberg@svt.se
22 #include "portaudio_consumer.h"
26 #include <tbb/atomic.h>
27 #include <tbb/concurrent_queue.h>
29 #include <boost/timer.hpp>
31 #include <portaudio.h>
33 #include <common/log/log.h>
34 #include <common/exception/exceptions.h>
35 #include <common/concurrency/future_util.h>
36 #include <common/diagnostics/graph.h>
38 #include <core/consumer/frame_consumer.h>
39 #include <core/mixer/audio/audio_util.h>
40 #include <core/video_format.h>
41 #include <core/parameters/parameters.h>
42 #include <core/mixer/read_frame.h>
44 namespace caspar { namespace portaudio {
46 #define PA_CHECK(err) if (err != paNoError) \
47 BOOST_THROW_EXCEPTION(caspar_exception() \
48 << msg_info(std::string("PortAudio error: ") \
49 + Pa_GetErrorText(err)))
51 typedef std::vector<int16_t, tbb::cache_aligned_allocator<int16_t>> audio_buffer_16;
56 unsigned long sample_frames_per_buffer,
57 const PaStreamCallbackTimeInfo* time_info,
58 PaStreamCallbackFlags status_flags,
61 struct portaudio_initializer
63 portaudio_initializer()
65 PA_CHECK(Pa_Initialize());
68 ~portaudio_initializer()
72 PA_CHECK(Pa_Terminate());
76 CASPAR_LOG_CURRENT_EXCEPTION();
81 struct portaudio_consumer : public core::frame_consumer
83 safe_ptr<diagnostics::graph> graph_;
84 boost::timer tick_timer_;
87 tbb::atomic<bool> started_;
88 tbb::concurrent_bounded_queue<std::shared_ptr<audio_buffer_16>> frames_in_queue_;
89 std::pair<std::shared_ptr<audio_buffer_16>, unsigned int> frame_being_consumed_;
90 std::shared_ptr<audio_buffer_16> to_deallocate_in_output_thread_;
92 core::video_format_desc format_desc_;
93 core::channel_layout channel_layout_;
95 std::shared_ptr<PaStream> stream_;
100 core::default_channel_layout_repository().get_by_name(L"STEREO"))
103 frames_in_queue_.set_capacity(2);
105 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
106 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
108 diagnostics::register_graph(graph_);
113 to_deallocate_in_output_thread_.swap(frame_being_consumed_.first);
114 frame_being_consumed_.first.reset();
115 frame_being_consumed_.second = 0;
117 return frames_in_queue_.try_pop(frame_being_consumed_.first);
122 unsigned int sample_frames_per_buffer,
123 PaStreamCallbackFlags status_flags)
125 if (!frame_being_consumed_.first)
130 auto needed = sample_frames_per_buffer * channel_layout_.num_channels;
132 while (needed > 0 && frame_being_consumed_.first)
134 auto start_index = frame_being_consumed_.second;
135 auto available = frame_being_consumed_.first->size() - start_index;
136 auto to_provide = std::min(needed, available);
140 frame_being_consumed_.first->data() + start_index,
141 to_provide * sizeof(int16_t));
143 output += to_provide;
144 needed -= to_provide;
145 available -= to_provide;
146 frame_being_consumed_.second += to_provide;
148 if (available == 0 && !promote_frame())
154 std::memset(output, 0, needed * sizeof(int16_t));
156 CASPAR_LOG(trace) << print() << L"late-frame: Inserted "
157 << needed << L" zero-samples";
158 graph_->set_tag("late-frame");
162 ~portaudio_consumer()
164 if (stream_ && started_)
165 PA_CHECK(Pa_StopStream(stream_.get()));
167 CASPAR_LOG(info) << print() << L" Successfully Uninitialized.";
170 virtual void initialize(const core::video_format_desc& format_desc, int channel_index) override
172 format_desc_ = format_desc;
173 channel_index_ = channel_index;
174 graph_->set_text(print());
176 static portaudio_initializer init;
178 auto device_index = Pa_GetDefaultOutputDevice();
180 if (device_index == paNoDevice)
181 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("No port audio device detected"));
183 auto device = Pa_GetDeviceInfo(device_index);
185 PaStreamParameters parameters;
186 parameters.channelCount = channel_layout_.num_channels;
187 parameters.device = device_index;
188 parameters.hostApiSpecificStreamInfo = nullptr;
189 parameters.sampleFormat = paInt16;
190 parameters.suggestedLatency = device->defaultLowOutputLatency;
194 PA_CHECK(Pa_OpenStream(
196 nullptr, // input config
198 format_desc.audio_sample_rate,
199 *std::min_element(format_desc.audio_cadence.begin(), format_desc.audio_cadence.end()),
203 stream_.reset(stream, [](PaStream* s) { Pa_CloseStream(s); });
205 CASPAR_LOG(info) << print() << " Sucessfully Initialized.";
208 virtual int64_t presentation_frame_age_millis() const override
210 auto info = Pa_GetStreamInfo(stream_.get());
215 return static_cast<int64_t>(info->outputLatency * 1000.0);
218 virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override
220 to_deallocate_in_output_thread_.reset();
221 std::shared_ptr<audio_buffer_16> buffer;
223 if (core::needs_rearranging(
224 frame->multichannel_view(),
226 channel_layout_.num_channels))
228 core::audio_buffer downmixed;
230 frame->multichannel_view().num_samples()
231 * channel_layout_.num_channels,
234 auto dest_view = core::make_multichannel_view<int32_t>(
235 downmixed.begin(), downmixed.end(), channel_layout_);
237 core::rearrange_or_rearrange_and_mix(
238 frame->multichannel_view(),
240 core::default_mix_config_repository());
242 buffer = std::make_shared<audio_buffer_16>(
243 core::audio_32_to_16(downmixed));
247 buffer = std::make_shared<audio_buffer_16>(
248 core::audio_32_to_16(frame->audio_data()));
251 if (!frames_in_queue_.try_push(buffer))
252 graph_->set_tag("dropped-frame");
256 PA_CHECK(Pa_StartStream(stream_.get()));
260 return wrap_as_future(true);
263 virtual std::wstring print() const override
265 return L"portaudio[" + boost::lexical_cast<std::wstring>(channel_index_) + L"|" + format_desc_.name + L"]";
268 virtual bool has_synchronization_clock() const override
273 virtual boost::property_tree::wptree info() const override
275 boost::property_tree::wptree info;
276 info.add(L"type", L"portaudio-consumer");
280 virtual size_t buffer_depth() const override
285 virtual int index() const override
292 const void*, // input
294 unsigned long sample_frames_per_buffer,
295 const PaStreamCallbackTimeInfo* time_info,
296 PaStreamCallbackFlags status_flags,
299 auto consumer = static_cast<portaudio_consumer*>(user_data);
301 consumer->write_samples(
302 static_cast<int16_t*>(output),
303 sample_frames_per_buffer,
309 safe_ptr<core::frame_consumer> create_consumer(const core::parameters& params)
311 if(params.size() < 1 || params[0] != L"AUDIO")
312 return core::frame_consumer::empty();
314 return make_safe<portaudio_consumer>();
317 safe_ptr<core::frame_consumer> create_consumer()
319 return make_safe<portaudio_consumer>();