]> git.sesse.net Git - casparcg/blobdiff - modules/oal/consumer/oal_consumer.cpp
set svn:eol-style native on .h and .cpp files
[casparcg] / modules / oal / consumer / oal_consumer.cpp
index 2fc418acf188d60d8c6cafcd584d4c0516b1690e..e65ef37ca4fa526128380f41a98f0099e1e01b32 100644 (file)
-/*\r
-* copyright (c) 2010 Sveriges Television AB <info@casparcg.com>\r
-*\r
-*  This file is part of CasparCG.\r
-*\r
-*    CasparCG is free software: you can redistribute it and/or modify\r
-*    it under the terms of the GNU General Public License as published by\r
-*    the Free Software Foundation, either version 3 of the License, or\r
-*    (at your option) any later version.\r
-*\r
-*    CasparCG is distributed in the hope that it will be useful,\r
-*    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
-*    GNU General Public License for more details.\r
-\r
-*    You should have received a copy of the GNU General Public License\r
-*    along with CasparCG.  If not, see <http://www.gnu.org/licenses/>.\r
-*\r
-*/\r
\r
-#include "oal_consumer.h"\r
-\r
-#include <common/diagnostics/graph.h>\r
-#include <common/log/log.h>\r
-#include <common/utility/timer.h>\r
-\r
-#include <core/video_format.h>\r
-\r
-#include <core/mixer/read_frame.h>\r
-\r
-#include <SFML/Audio.hpp>\r
-\r
-#include <boost/circular_buffer.hpp>\r
-#include <boost/timer.hpp>\r
-\r
-#include <tbb/concurrent_queue.h>\r
-\r
-namespace caspar {\r
-\r
-struct oal_consumer::implementation : public sf::SoundStream, boost::noncopyable\r
-{\r
-       safe_ptr<diagnostics::graph> graph_;\r
-       boost::timer perf_timer_;\r
-\r
-       tbb::concurrent_bounded_queue<std::vector<short>> input_;\r
-       boost::circular_buffer<std::vector<short>> container_;\r
-       tbb::atomic<bool> is_running_;\r
-\r
-       core::video_format_desc format_desc_;\r
-       int preroll_count_;\r
-public:\r
-       implementation(const core::video_format_desc& format_desc) \r
-               : graph_(diagnostics::create_graph(narrow(print())))\r
-               , container_(5)\r
-               , format_desc_(format_desc)\r
-               , preroll_count_(0)\r
-       {\r
-               graph_->add_guide("tick-time", 0.5);\r
-               graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f));\r
-               is_running_ = true;\r
-               input_.set_capacity(CONSUMER_BUFFER_DEPTH-2);\r
-               \r
-               sf::SoundStream::Initialize(2, 48000);\r
-               Play();         \r
-               CASPAR_LOG(info) << print() << " Sucessfully initialized.";\r
-       }\r
-\r
-       ~implementation()\r
-       {\r
-               is_running_ = false;\r
-               input_.try_push(std::vector<short>());\r
-               input_.try_push(std::vector<short>());\r
-               Stop();\r
-               CASPAR_LOG(info) << print() << L" Shutting down.";      \r
-       }\r
-       \r
-       void send(const safe_ptr<core::read_frame>& frame)\r
-       {                       \r
-               if(preroll_count_ < input_.capacity())\r
-               {\r
-                       while(input_.try_push(std::vector<int16_t>(format_desc_.audio_samples_per_frame, 0)))\r
-                               ++preroll_count_;\r
-               }\r
-\r
-               input_.push(std::vector<int16_t>(frame->audio_data().begin(), frame->audio_data().end()));      \r
-       }\r
-\r
-       size_t buffer_depth() const{return 3;}\r
-\r
-       virtual bool OnGetData(sf::SoundStream::Chunk& data)\r
-       {               \r
-               std::vector<short> audio_data;          \r
-               input_.pop(audio_data);\r
-                               \r
-               container_.push_back(std::move(audio_data));\r
-               data.Samples = container_.back().data();\r
-               data.NbSamples = container_.back().size();      \r
-               \r
-               graph_->update_value("tick-time", perf_timer_.elapsed()*format_desc_.fps*0.5);          \r
-               perf_timer_.restart();\r
-\r
-               return is_running_;\r
-       }\r
-\r
-       std::wstring print() const\r
-       {\r
-               return L"oal[" + format_desc_.name + L"]";\r
-       }\r
-};\r
-\r
-oal_consumer::oal_consumer(){}\r
-oal_consumer::oal_consumer(oal_consumer&& other) : impl_(std::move(other.impl_)){}\r
-void oal_consumer::send(const safe_ptr<core::read_frame>& frame){impl_->send(frame);}\r
-size_t oal_consumer::buffer_depth() const{return impl_->buffer_depth();}\r
-void oal_consumer::initialize(const core::video_format_desc& format_desc){impl_.reset(new implementation(format_desc));}\r
-std::wstring oal_consumer::print() const { return impl_->print(); }\r
-const core::video_format_desc& oal_consumer::get_video_format_desc() const{return impl_->format_desc_;}\r
-\r
-safe_ptr<core::frame_consumer> create_oal_consumer(const std::vector<std::wstring>& params)\r
-{\r
-       if(params.size() < 1 || params[0] != L"OAL")\r
-               return core::frame_consumer::empty();\r
-\r
-       return make_safe<oal_consumer>();\r
-}\r
-}\r
+/*
+* 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: Robert Nagy, ronag89@gmail.com
+*/
+
+#include "oal_consumer.h"
+
+#include <common/except.h>
+#include <common/executor.h>
+#include <common/diagnostics/graph.h>
+#include <common/log.h>
+#include <common/utf.h>
+#include <common/env.h>
+
+#include <core/consumer/frame_consumer.h>
+#include <core/frame/frame.h>
+#include <core/mixer/audio/audio_util.h>
+#include <core/mixer/audio/audio_mixer.h>
+#include <core/video_format.h>
+
+#include <boost/circular_buffer.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/timer.hpp>
+#include <boost/foreach.hpp>
+#include <boost/thread/once.hpp>
+
+#include <tbb/concurrent_queue.h>
+
+#include <al/alc.h>
+#include <al/al.h>
+
+#include <array>
+
+namespace caspar { namespace oal {
+
+typedef std::vector<int16_t, tbb::cache_aligned_allocator<int16_t>> audio_buffer_16;
+
+class device
+{
+       ALCdevice*                                                                                      device_;
+       ALCcontext*                                                                                     context_;
+
+public:
+       device()
+               : device_(0)
+               , context_(0)
+       {
+               device_ = alcOpenDevice(nullptr);
+
+               if(!device_)
+                       CASPAR_THROW_EXCEPTION(invalid_operation() << msg_info("Failed to initialize audio device."));
+
+               context_ = alcCreateContext(device_, nullptr);
+
+               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."));
+       }
+
+       ~device()
+       {
+               alcMakeContextCurrent(nullptr);
+
+               if(context_)
+                       alcDestroyContext(context_);
+
+               if(device_)
+                       alcCloseDevice(device_);
+       }
+
+       ALCdevice* get()
+       {
+               return device_;
+       }
+};
+
+void init_device()
+{
+       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
+{
+       spl::shared_ptr<diagnostics::graph>                                     graph_;
+       boost::timer                                                                            perf_timer_;
+       int                                                                                                     channel_index_;
+       
+       core::video_format_desc                                                         format_desc_;
+
+       ALuint                                                                                          source_;
+       std::array<ALuint, 3>                                                           buffers_;
+
+       executor                                                                                        executor_;
+
+public:
+       oal_consumer() 
+               : channel_index_(-1)
+               , source_(0)
+               , executor_(L"oal_consumer")
+       {
+               buffers_.assign(0);
+
+               init_device();
+
+               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_.begin_invoke([=]
+               {               
+                       if(source_)
+                       {
+                               alSourceStop(source_);
+                               alDeleteSources(1, &source_);
+                       }
+
+                       BOOST_FOREACH(auto& buffer, buffers_)
+                       {
+                               if(buffer)
+                                       alDeleteBuffers(1, &buffer);
+                       };
+               });
+       }
+
+       // frame consumer
+
+       void initialize(const core::video_format_desc& format_desc, int channel_index) override
+       {
+               format_desc_    = format_desc;          
+               channel_index_  = channel_index;
+               graph_->set_text(print());
+               
+               executor_.begin_invoke([=]
+               {               
+                       alGenBuffers(static_cast<ALsizei>(buffers_.size()), buffers_.data());
+                       alGenSources(1, &source_);
+
+                       for(std::size_t n = 0; n < buffers_.size(); ++n)
+                       {
+                               std::vector<int16_t> audio(format_desc_.audio_cadence[n % format_desc_.audio_cadence.size()], 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_);  
+               });
+       }
+       
+       bool send(core::const_frame frame) override
+       {                       
+               executor_.begin_invoke([=]
+               {
+                       ALenum state; 
+                       alGetSourcei(source_, AL_SOURCE_STATE,&state);
+                       if(state != AL_PLAYING)
+                       {
+                               for(int n = 0; n < buffers_.size()-1; ++n)
+                               {                                       
+                                       ALuint buffer = 0;  
+                                       alSourceUnqueueBuffers(source_, 1, &buffer);
+                                       if(buffer)
+                                       {
+                                               std::vector<int16_t> audio(format_desc_.audio_cadence[n % format_desc_.audio_cadence.size()], 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");  
+                       }
+
+                       auto audio = core::audio_32_to_16(frame.audio_data());
+                       
+                       ALuint buffer = 0;  
+                       alSourceUnqueueBuffers(source_, 1, &buffer);
+                       if(buffer)
+                       {
+                               alBufferData(buffer, AL_FORMAT_STEREO16, audio.data(), static_cast<ALsizei>(audio.size()*sizeof(int16_t)), format_desc_.audio_sample_rate);
+                               alSourceQueueBuffers(source_, 1, &buffer);
+                       }
+                       else
+                               graph_->set_tag("dropped-frame");
+
+                       graph_->set_value("tick-time", perf_timer_.elapsed()*format_desc_.fps*0.5);             
+                       perf_timer_.restart();
+               });
+
+               return true;
+       }
+       
+       std::wstring print() const override
+       {
+               return L"oal[" + boost::lexical_cast<std::wstring>(channel_index_) + L"|" + format_desc_.name + L"]";
+       }
+
+       std::wstring name() const override
+       {
+               return L"system-audio";
+       }
+
+       boost::property_tree::wptree info() const override
+       {
+               boost::property_tree::wptree info;
+               info.add(L"type", L"system-audio");
+               return info;
+       }
+       
+       bool has_synchronization_clock() const override
+       {
+               return false;
+       }
+       
+       int buffer_depth() const override
+       {
+               return 3;
+       }
+               
+       int index() const override
+       {
+               return 500;
+       }
+
+       void subscribe(const monitor::observable::observer_ptr& o) override
+       {
+       }
+
+       void unsubscribe(const monitor::observable::observer_ptr& o) override
+       {
+       }       
+};
+
+spl::shared_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params)
+{
+       if(params.size() < 1 || params[0] != L"AUDIO")
+               return core::frame_consumer::empty();
+
+       return spl::make_shared<oal_consumer>();
+}
+
+spl::shared_ptr<core::frame_consumer> create_consumer()
+{
+       return spl::make_shared<oal_consumer>();
+}
+
+}}