#include <common/concurrency/executor.h>\r
#include <common/diagnostics/graph.h>\r
#include <common/exception/exceptions.h>\r
+#include <common/memory/memcpy.h>\r
+#include <common/memory/memclr.h>\r
#include <common/utility/timer.h>\r
\r
#include <tbb/concurrent_queue.h>\r
\r
namespace caspar { \r
\r
+enum key\r
+{\r
+ external_key,\r
+ internal_key,\r
+ default_key\r
+};\r
+\r
+enum latency\r
+{\r
+ low_latency,\r
+ normal_latency,\r
+ default_latency\r
+};\r
+\r
+struct configuration\r
+{\r
+ size_t device_index;\r
+ bool embedded_audio;\r
+ key keyer;\r
+ latency latency;\r
+ \r
+ configuration()\r
+ : device_index(1)\r
+ , embedded_audio(false)\r
+ , keyer(default_key)\r
+ , latency(default_latency)\r
+ {}\r
+ configuration(const boost::property_tree::ptree& ptree)\r
+ : device_index(1)\r
+ , embedded_audio(false)\r
+ , keyer(default_key)\r
+ , latency(default_latency)\r
+ { \r
+ auto key_str = ptree.get("key", "default");\r
+ if(key_str == "internal")\r
+ keyer = internal_key;\r
+ else if(key_str == "external")\r
+ keyer = external_key;\r
+\r
+ auto latency_str = ptree.get("latency", "default");\r
+ if(latency_str == "normal")\r
+ latency = normal_latency;\r
+ else if(latency_str == "low")\r
+ latency = low_latency;\r
+\r
+ device_index = ptree.get("device", 0);\r
+ embedded_audio = ptree.get("embedded-audio", false);\r
+ }\r
+\r
+ configuration(const std::vector<std::wstring>& params)\r
+ : device_index(1)\r
+ , embedded_audio(false)\r
+ , keyer(default_key)\r
+ , latency(default_latency)\r
+ {\r
+ if(params.size() > 0)\r
+ device_index = lexical_cast_or_default<int>(params[0], device_index);\r
+\r
+ {\r
+ auto it = std::find(params.begin(), params.end(), L"INTERNAL_KEY");\r
+ if(it != params.end())\r
+ keyer = internal_key;\r
+ else\r
+ {\r
+ auto it = std::find(params.begin(), params.end(), L"EXTERNAL_KEY");\r
+ if(it != params.end())\r
+ keyer = external_key;\r
+ }\r
+ }\r
+ \r
+ embedded_audio = std::find(params.begin(), params.end(), L"EMBED_AUDIO") != params.end(); \r
+ }\r
+};\r
+\r
struct decklink_output : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
{ \r
+ static const size_t BUFFER_SIZE = 4;\r
+ \r
struct co_init\r
{\r
co_init(){CoInitialize(nullptr);}\r
~co_init(){CoUninitialize();}\r
} co_;\r
\r
- const decklink_consumer::configuration config_;\r
+ std::exception_ptr exception_;\r
+ const configuration config_;\r
\r
- std::wstring model_name_;\r
+ std::wstring model_name_;\r
tbb::atomic<bool> is_running_;\r
\r
std::shared_ptr<diagnostics::graph> graph_;\r
boost::timer perf_timer_;\r
\r
- std::array<std::pair<void*, CComPtr<IDeckLinkMutableVideoFrame>>, 3> reserved_frames_;\r
+ std::array<std::pair<void*, CComPtr<IDeckLinkMutableVideoFrame>>, BUFFER_SIZE+1> reserved_frames_;\r
boost::circular_buffer<std::vector<short>> audio_container_;\r
\r
CComPtr<IDeckLink> decklink_;\r
tbb::concurrent_bounded_queue<std::shared_ptr<const core::read_frame>> audio_frame_buffer_;\r
\r
public:\r
- decklink_output(const decklink_consumer::configuration& config, const core::video_format_desc& format_desc) \r
- : model_name_(L"DECKLINK")\r
+ decklink_output(const configuration& config, const core::video_format_desc& format_desc) \r
+ : model_name_(L"DECKLINK")\r
, config_(config)\r
, audio_container_(5)\r
, frames_scheduled_(0)\r
if(FAILED(output_->DoesSupportVideoMode(display_mode->GetDisplayMode(), bmdFormat8BitBGRA, bmdVideoOutputFlagDefault, &displayModeSupport, nullptr)))\r
BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Card does not support requested videoformat."));\r
\r
- if(config_.embed_audio)\r
+ if(config_.embedded_audio)\r
{\r
if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2, bmdAudioOutputStreamTimestamped)))\r
BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio output."));\r
CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";\r
}\r
\r
- if(config_.low_latency)\r
+ if(config_.latency == normal_latency)\r
+ {\r
+ configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);\r
+ CASPAR_LOG(info) << print() << L" Enabled normal-latency mode";\r
+ }\r
+ else if(config_.latency == low_latency)\r
+ { \r
configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);\r
+ CASPAR_LOG(info) << print() << L" Enabled low-latency mode";\r
+ }\r
+ else\r
+ CASPAR_LOG(info) << print() << L" Uses driver latency settings."; \r
\r
- if(FAILED(output_->EnableVideoOutput(display_mode->GetDisplayMode(), bmdVideoOutputFlagDefault))) \r
- BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));\r
- \r
- if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
- BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set playback completion callback."));\r
- \r
CComQIPtr<IDeckLinkKeyer> keyer = decklink_;\r
- if(config_.keyer == decklink_consumer::internal_key) \r
+ if(config_.keyer == internal_key) \r
{\r
if(FAILED(keyer->Enable(FALSE))) \r
CASPAR_LOG(error) << print() << L" Failed to enable internal keyer."; \r
else if(FAILED(keyer->SetLevel(255))) \r
CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
else\r
- CASPAR_LOG(info) << print() << L" Successfully configured internal keyer."; \r
+ CASPAR_LOG(info) << print() << L" Enabled internal keyer."; \r
}\r
- else if(config.keyer == decklink_consumer::external_key)\r
+ else if(config.keyer == external_key)\r
{\r
if(FAILED(keyer->Enable(TRUE))) \r
CASPAR_LOG(error) << print() << L" Failed to enable external keyer."; \r
else if(FAILED(keyer->SetLevel(255))) \r
CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";\r
else\r
- CASPAR_LOG(info) << print() << L" Successfully configured external keyer."; \r
+ CASPAR_LOG(info) << print() << L" Enabled external keyer."; \r
}\r
else\r
- CASPAR_LOG(info) << print() << L" Uses default keyer settings."; \r
+ CASPAR_LOG(info) << print() << L" Uses driver keyer settings."; \r
\r
+ if(FAILED(output_->EnableVideoOutput(display_mode->GetDisplayMode(), bmdVideoOutputFlagDefault))) \r
+ BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video output."));\r
\r
+ if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))\r
+ BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set playback completion callback."));\r
+ \r
for(size_t n = 0; n < reserved_frames_.size(); ++n)\r
{\r
if(FAILED(output_->CreateVideoFrame(format_desc_.width, format_desc_.height, format_desc_.size/format_desc_.height, bmdFormat8BitBGRA, bmdFrameFlagDefault, &reserved_frames_[n].second)))\r
BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to get frame bytes."));\r
}\r
\r
- auto buffer_size = static_cast<size_t>(frame_time_scale_/frame_duration_)/4;\r
- for(size_t n = 0; n < buffer_size; ++n)\r
+ CASPAR_LOG(info) << print() << L" Buffer-depth: " << BUFFER_SIZE;\r
+ \r
+ for(size_t n = 0; n < BUFFER_SIZE; ++n)\r
schedule_next_video(core::read_frame::empty());\r
\r
- video_frame_buffer_.set_capacity(buffer_size);\r
- audio_frame_buffer_.set_capacity(buffer_size);\r
- for(size_t n = 0; n < std::max<size_t>(2, buffer_size-2); ++n)\r
+ video_frame_buffer_.set_capacity(2);\r
+ audio_frame_buffer_.set_capacity(2);\r
+ \r
+ if(config_.embedded_audio)\r
+ output_->BeginAudioPreroll();\r
+ else\r
{\r
- video_frame_buffer_.try_push(core::read_frame::empty());\r
- if(config_.embed_audio)\r
- audio_frame_buffer_.try_push(core::read_frame::empty());\r
+ if(FAILED(output_->StartScheduledPlayback(0, frame_time_scale_, 1.0))) \r
+ BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
}\r
\r
- if(FAILED(output_->StartScheduledPlayback(0, frame_time_scale_, 1.0))) \r
- BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
- \r
CASPAR_LOG(info) << print() << L" Successfully initialized for " << format_desc_.name; \r
}\r
\r
if(output_ != nullptr) \r
{\r
output_->StopScheduledPlayback(0, nullptr, 0);\r
- if(config_.embed_audio)\r
+ if(config_.embedded_audio)\r
output_->DisableAudioOutput();\r
output_->DisableVideoOutput();\r
}\r
\r
virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped (void)\r
{\r
+ is_running_ = false;\r
return S_OK;\r
}\r
\r
- virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples (BOOL /*preroll*/)\r
+ virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples (BOOL preroll)\r
{\r
if(!is_running_)\r
return S_OK;\r
-\r
- std::shared_ptr<const core::read_frame> frame;\r
- audio_frame_buffer_.pop(frame);\r
- schedule_next_audio(safe_ptr<const core::read_frame>(frame));\r
+ \r
+ try\r
+ {\r
+ if(preroll)\r
+ {\r
+ if(FAILED(output_->StartScheduledPlayback(0, frame_time_scale_, 1.0)))\r
+ BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to schedule playback."));\r
+ }\r
+\r
+ std::shared_ptr<const core::read_frame> frame;\r
+ audio_frame_buffer_.pop(frame);\r
+ schedule_next_audio(safe_ptr<const core::read_frame>(frame));\r
+ \r
+ }\r
+ catch(...)\r
+ {\r
+ exception_ = std::current_exception();\r
+ return E_FAIL;\r
+ }\r
\r
return S_OK;\r
}\r
void schedule_next_video(const safe_ptr<const core::read_frame>& frame)\r
{\r
if(!frame->image_data().empty())\r
- std::copy(frame->image_data().begin(), frame->image_data().end(), static_cast<char*>(reserved_frames_.front().first));\r
+ fast_memcpy(reserved_frames_.front().first, frame->image_data().begin(), frame->image_data().size());\r
else\r
- std::fill_n(static_cast<int*>(reserved_frames_.front().first), 0, format_desc_.size/4);\r
+ fast_memclr(reserved_frames_.front().first, format_desc_.size);\r
\r
if(FAILED(output_->ScheduleVideoFrame(reserved_frames_.front().second, (frames_scheduled_++) * frame_duration_, frame_duration_, frame_time_scale_)))\r
CASPAR_LOG(error) << print() << L" Failed to schedule video.";\r
\r
std::rotate(reserved_frames_.begin(), reserved_frames_.begin() + 1, reserved_frames_.end());\r
- graph_->update_value("tick-time", static_cast<float>(perf_timer_.elapsed()/format_desc_.interval*0.5));\r
+ graph_->update_value("tick-time", static_cast<float>(perf_timer_.elapsed()/format_desc_.interval)*0.5f);\r
perf_timer_.restart();\r
}\r
\r
void send(const safe_ptr<const core::read_frame>& frame)\r
{\r
+ if(exception_ != nullptr)\r
+ std::rethrow_exception(exception_);\r
+\r
video_frame_buffer_.push(frame);\r
- if(config_.embed_audio)\r
+ if(config_.embedded_audio)\r
audio_frame_buffer_.push(frame);\r
}\r
\r
}\r
};\r
\r
-struct decklink_consumer::implementation\r
+struct decklink_consumer : public core::frame_consumer\r
{\r
std::unique_ptr<decklink_output> input_;\r
- decklink_consumer::configuration config_;\r
+ configuration config_;\r
\r
executor executor_;\r
public:\r
\r
- implementation(const decklink_consumer::configuration& config)\r
+ decklink_consumer(const configuration& config)\r
: config_(config)\r
- , executor_(L"DECKLINK[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]")\r
- {\r
- executor_.start();\r
- }\r
+ , executor_(L"DECKLINK[" + boost::lexical_cast<std::wstring>(config.device_index) + L"]", true){}\r
\r
- ~implementation()\r
+ ~decklink_consumer()\r
{\r
executor_.invoke([&]\r
{\r
{\r
input_->send(frame);\r
}\r
-\r
- size_t buffer_depth() const\r
- {\r
- return 1;\r
- }\r
-\r
+ \r
std::wstring print() const\r
{\r
return input_->print();\r
}\r
-};\r
+}; \r
\r
-decklink_consumer::decklink_consumer(const configuration& config) : impl_(new implementation(config)){}\r
-decklink_consumer::decklink_consumer(decklink_consumer&& other) : impl_(std::move(other.impl_)){}\r
-void decklink_consumer::initialize(const core::video_format_desc& format_desc){impl_->initialize(format_desc);}\r
-void decklink_consumer::send(const safe_ptr<const core::read_frame>& frame){impl_->send(frame);}\r
-size_t decklink_consumer::buffer_depth() const{return impl_->buffer_depth();}\r
-std::wstring decklink_consumer::print() const{return impl_->print();}\r
- \r
safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params) \r
{\r
if(params.size() < 1 || params[0] != L"DECKLINK")\r
return core::frame_consumer::empty();\r
\r
- decklink_consumer::configuration config;\r
-\r
- if(params.size() > 1) \r
- config.device_index = lexical_cast_or_default<int>(params[2], config.device_index);\r
-\r
- if(params.size() > 2)\r
- config.embed_audio = lexical_cast_or_default<bool>(params[3], config.embed_audio);\r
- \r
- if(params.size() > 3) \r
- {\r
- if(params[4] == L"INTERNAL_KEY")\r
- config.keyer = decklink_consumer::internal_key;\r
- else if(params[4] == L"EXTERNAL_KEY")\r
- config.keyer = decklink_consumer::external_key;\r
- }\r
+ return make_safe<decklink_consumer>(configuration(std::vector<std::wstring>(params.begin()+1, params.end())));\r
+}\r
\r
- return make_safe<decklink_consumer>(config);\r
+safe_ptr<core::frame_consumer> create_decklink_consumer_ptree(const boost::property_tree::ptree& ptree) \r
+{\r
+ return make_safe<decklink_consumer>(configuration(ptree));\r
}\r
\r
}
\ No newline at end of file