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/exception/win32_exception.h>
36 #include <common/concurrency/future_util.h>
37 #include <common/diagnostics/graph.h>
39 #include <core/consumer/frame_consumer.h>
40 #include <core/mixer/audio/audio_util.h>
41 #include <core/video_format.h>
42 #include <core/parameters/parameters.h>
43 #include <core/mixer/read_frame.h>
45 namespace caspar { namespace portaudio {
47 #define PA_CHECK(err) if (err != paNoError) \
48 BOOST_THROW_EXCEPTION(caspar_exception() \
49 << msg_info(std::string("PortAudio error: ") \
50 + Pa_GetErrorText(err)))
52 typedef std::vector<int16_t, tbb::cache_aligned_allocator<int16_t>> audio_buffer_16;
57 unsigned long sample_frames_per_buffer,
58 const PaStreamCallbackTimeInfo* time_info,
59 PaStreamCallbackFlags status_flags,
62 struct portaudio_initializer
64 portaudio_initializer()
66 PA_CHECK(Pa_Initialize());
69 ~portaudio_initializer()
73 PA_CHECK(Pa_Terminate());
77 CASPAR_LOG_CURRENT_EXCEPTION();
82 struct portaudio_consumer : public core::frame_consumer
84 safe_ptr<diagnostics::graph> graph_;
85 boost::timer tick_timer_;
88 tbb::atomic<bool> started_;
89 tbb::concurrent_bounded_queue<std::shared_ptr<audio_buffer_16>> frames_in_queue_;
90 std::pair<std::shared_ptr<audio_buffer_16>, unsigned int> frame_being_consumed_;
91 std::shared_ptr<audio_buffer_16> to_deallocate_in_output_thread_;
93 core::video_format_desc format_desc_;
94 core::channel_layout channel_layout_;
96 std::shared_ptr<PaStream> stream_;
101 core::default_channel_layout_repository().get_by_name(L"STEREO"))
104 frames_in_queue_.set_capacity(2);
106 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
107 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f));
109 diagnostics::register_graph(graph_);
114 to_deallocate_in_output_thread_.swap(frame_being_consumed_.first);
115 frame_being_consumed_.first.reset();
116 frame_being_consumed_.second = 0;
118 return frames_in_queue_.try_pop(frame_being_consumed_.first);
123 unsigned int sample_frames_per_buffer,
124 PaStreamCallbackFlags status_flags)
126 if (!frame_being_consumed_.first)
131 auto needed = sample_frames_per_buffer * channel_layout_.num_channels;
133 while (needed > 0 && frame_being_consumed_.first)
135 auto start_index = frame_being_consumed_.second;
136 auto available = frame_being_consumed_.first->size() - start_index;
137 auto to_provide = std::min(needed, available);
141 frame_being_consumed_.first->data() + start_index,
142 to_provide * sizeof(int16_t));
144 output += to_provide;
145 needed -= to_provide;
146 available -= to_provide;
147 frame_being_consumed_.second += to_provide;
149 if (available == 0 && !promote_frame())
155 std::memset(output, 0, needed * sizeof(int16_t));
157 CASPAR_LOG(trace) << print() << L"late-frame: Inserted "
158 << needed << L" zero-samples";
159 graph_->set_tag("late-frame");
163 ~portaudio_consumer()
165 if (stream_ && started_)
166 PA_CHECK(Pa_StopStream(stream_.get()));
168 CASPAR_LOG(info) << print() << L" Successfully Uninitialized.";
171 virtual void initialize(const core::video_format_desc& format_desc, int channel_index) override
173 format_desc_ = format_desc;
174 channel_index_ = channel_index;
175 graph_->set_text(print());
177 static portaudio_initializer init;
179 auto device_index = Pa_GetDefaultOutputDevice();
181 if (device_index == paNoDevice)
182 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("No port audio device detected"));
184 auto device = Pa_GetDeviceInfo(device_index);
186 PaStreamParameters parameters;
187 parameters.channelCount = channel_layout_.num_channels;
188 parameters.device = device_index;
189 parameters.hostApiSpecificStreamInfo = nullptr;
190 parameters.sampleFormat = paInt16;
191 parameters.suggestedLatency = device->defaultLowOutputLatency;
195 PA_CHECK(Pa_OpenStream(
197 nullptr, // input config
199 format_desc.audio_sample_rate,
200 *std::min_element(format_desc.audio_cadence.begin(), format_desc.audio_cadence.end()),
204 stream_.reset(stream, [](PaStream* s) { Pa_CloseStream(s); });
206 CASPAR_LOG(info) << print() << " Sucessfully Initialized.";
209 virtual int64_t presentation_frame_age_millis() const override
211 auto info = Pa_GetStreamInfo(stream_.get());
216 return static_cast<int64_t>(info->outputLatency * 1000.0);
219 virtual boost::unique_future<bool> send(const safe_ptr<core::read_frame>& frame) override
221 to_deallocate_in_output_thread_.reset();
222 std::shared_ptr<audio_buffer_16> buffer;
224 if (core::needs_rearranging(
225 frame->multichannel_view(),
227 channel_layout_.num_channels))
229 core::audio_buffer downmixed;
231 frame->multichannel_view().num_samples()
232 * channel_layout_.num_channels,
235 auto dest_view = core::make_multichannel_view<int32_t>(
236 downmixed.begin(), downmixed.end(), channel_layout_);
238 core::rearrange_or_rearrange_and_mix(
239 frame->multichannel_view(),
241 core::default_mix_config_repository());
243 buffer = std::make_shared<audio_buffer_16>(
244 core::audio_32_to_16(downmixed));
248 buffer = std::make_shared<audio_buffer_16>(
249 core::audio_32_to_16(frame->audio_data()));
252 if (!frames_in_queue_.try_push(buffer))
253 graph_->set_tag("dropped-frame");
257 PA_CHECK(Pa_StartStream(stream_.get()));
261 return wrap_as_future(true);
264 virtual std::wstring print() const override
266 return L"portaudio[" + boost::lexical_cast<std::wstring>(channel_index_) + L"|" + format_desc_.name + L"]";
269 virtual bool has_synchronization_clock() const override
274 virtual boost::property_tree::wptree info() const override
276 boost::property_tree::wptree info;
277 info.add(L"type", L"portaudio-consumer");
281 virtual size_t buffer_depth() const override
286 virtual int index() const override
293 const void*, // input
295 unsigned long sample_frames_per_buffer,
296 const PaStreamCallbackTimeInfo* time_info,
297 PaStreamCallbackFlags status_flags,
300 win32_exception::ensure_handler_installed_for_thread(
301 "portaudio-callback-thread");
302 auto consumer = static_cast<portaudio_consumer*>(user_data);
304 consumer->write_samples(
305 static_cast<int16_t*>(output),
306 sample_frames_per_buffer,
312 safe_ptr<core::frame_consumer> create_consumer(const core::parameters& params)
314 if(params.size() < 1 || params[0] != L"AUDIO")
315 return core::frame_consumer::empty();
317 return make_safe<portaudio_consumer>();
320 safe_ptr<core::frame_consumer> create_consumer()
322 return make_safe<portaudio_consumer>();