X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fdecklink%2Fproducer%2Fdecklink_producer.cpp;h=77ccd596f33458dec496245261964dec86be7ae2;hb=1dba809bb1d6a538271140c66d104b06045648b3;hp=9f38d044acc0f37a4837349e7f4c67dd02504e08;hpb=ddfc5dc989ae57e6fd2a88fd1d3104d2e0a56cc7;p=casparcg diff --git a/modules/decklink/producer/decklink_producer.cpp b/modules/decklink/producer/decklink_producer.cpp index 9f38d044a..5200a6900 100644 --- a/modules/decklink/producer/decklink_producer.cpp +++ b/modules/decklink/producer/decklink_producer.cpp @@ -1,314 +1,467 @@ -/* -* copyright (c) 2010 Sveriges Television AB -* -* This file is part of CasparCG. -* -* 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 . -* -*/ - -#include "../stdafx.h" - -#include "decklink_producer.h" - -#include "../interop/DeckLinkAPI_h.h" // TODO: Change this -#include "../util/util.h" // TODO: Change this - -#include -#include -#include -#include - -#include - -#include -#include - -#include - -#pragma warning(push) -#pragma warning(disable : 4996) - - #include - - #include - #include - -#pragma warning(push) - -#include - -namespace caspar { - -class decklink_input : public IDeckLinkInputCallback -{ - struct co_init - { - co_init(){CoInitialize(nullptr);} - ~co_init(){CoUninitialize();} - } co_; - - printer parent_printer_; - const core::video_format_desc format_desc_; - std::wstring model_name_; - const size_t device_index_; - - std::shared_ptr graph_; - timer perf_timer_; - - CComPtr decklink_; - CComQIPtr input_; - IDeckLinkDisplayMode* d_mode_; - - std::vector audio_data_; - - std::shared_ptr frame_factory_; - - tbb::concurrent_bounded_queue> frame_buffer_; - safe_ptr tail_; - -public: - - decklink_input(const core::video_format_desc& format_desc, size_t device_index, const std::shared_ptr& frame_factory, const printer& parent_printer) - : parent_printer_(parent_printer) - , format_desc_(format_desc) - , device_index_(device_index) - , model_name_(L"DECKLINK") - , frame_factory_(frame_factory) - , tail_(core::basic_frame::empty()) - { - frame_buffer_.set_capacity(4); - - CComPtr pDecklinkIterator; - if(FAILED(pDecklinkIterator.CoCreateInstance(CLSID_CDeckLinkIterator))) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " No Decklink drivers installed.")); - - size_t n = 0; - while(n < device_index_ && pDecklinkIterator->Next(&decklink_) == S_OK){++n;} - - if(n != device_index_ || !decklink_) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Decklink card not found.") << arg_name_info("device_index") << arg_value_info(boost::lexical_cast(device_index_))); - - input_ = decklink_; - - BSTR pModelName; - decklink_->GetModelName(&pModelName); - model_name_ = std::wstring(pModelName); - - graph_ = diagnostics::create_graph(boost::bind(&decklink_input::print, this)); - graph_->add_guide("tick-time", 0.5); - graph_->set_color("tick-time", diagnostics::color(0.1f, 0.7f, 0.8f)); - - unsigned long decklinkVideoFormat = GetDecklinkVideoFormat(format_desc.format); - if(decklinkVideoFormat == ULONG_MAX) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Card does not support requested videoformat.")); - - d_mode_ = get_display_mode((BMDDisplayMode)decklinkVideoFormat); - if(d_mode_ == nullptr) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Card does not support requested videoformat.")); - - BMDDisplayModeSupport displayModeSupport; - if(FAILED(input_->DoesSupportVideoMode((BMDDisplayMode)decklinkVideoFormat, bmdFormat8BitYUV, bmdVideoOutputFlagDefault, &displayModeSupport, nullptr))) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Card does not support requested videoformat.")); - - // NOTE: bmdFormat8BitARGB does not seem to work with Decklink HD Extreme 3D - if(FAILED(input_->EnableVideoInput((BMDDisplayMode)decklinkVideoFormat, bmdFormat8BitYUV, 0))) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable video input.")); - - if(FAILED(input_->EnableAudioInput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2))) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Could not enable audio input.")); - - if (FAILED(input_->SetCallback(this)) != S_OK) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to set input callback.")); - - if(FAILED(input_->StartStreams())) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + " Failed to start input stream.")); - - CASPAR_LOG(info) << print() << " successfully initialized decklink for " << format_desc_.name; - } - - ~decklink_input() - { - if(input_ != nullptr) - { - input_->StopStreams(); - input_->DisableVideoInput(); - } - } - - virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID, LPVOID*) {return E_NOINTERFACE;} - virtual ULONG STDMETHODCALLTYPE AddRef () {return 1;} - virtual ULONG STDMETHODCALLTYPE Release () {return 1;} - - virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(BMDVideoInputFormatChangedEvents /*notificationEvents*/, IDeckLinkDisplayMode* newDisplayMode, BMDDetectedVideoInputFormatFlags /*detectedSignalFlags*/) - { - d_mode_ = newDisplayMode; - return S_OK; - } - - // TODO: Enable audio input - virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(IDeckLinkVideoInputFrame* video, IDeckLinkAudioInputPacket* audio) - { - graph_->update_value("tick-time", static_cast(perf_timer_.elapsed()/format_desc_.interval*0.5)); - perf_timer_.reset(); - - if(!video) - return S_OK; - - void* bytes = nullptr; - if(FAILED(video->GetBytes(&bytes))) - return S_OK; - - core::pixel_format_desc desc; - desc.pix_fmt = core::pixel_format::ycbcr; - desc.planes.push_back(core::pixel_format_desc::plane(d_mode_->GetWidth(), d_mode_->GetHeight(), 1)); - desc.planes.push_back(core::pixel_format_desc::plane(d_mode_->GetWidth()/2, d_mode_->GetHeight(), 1)); - desc.planes.push_back(core::pixel_format_desc::plane(d_mode_->GetWidth()/2, d_mode_->GetHeight(), 1)); - auto frame = frame_factory_->create_frame(desc); - - unsigned char* data = reinterpret_cast(bytes); - int frame_size = (d_mode_->GetWidth() * 16 / 8) * d_mode_->GetHeight(); - - // Convert to planar YUV422 - unsigned char* y = frame->image_data(0).begin(); - unsigned char* cb = frame->image_data(1).begin(); - unsigned char* cr = frame->image_data(2).begin(); - - tbb::parallel_for(tbb::blocked_range(0, frame_size/4), [&](const tbb::blocked_range& r) - { - for(auto n = r.begin(); n != r.end(); ++n) - { - cb[n] = data[n*4+0]; - y [n*2+0] = data[n*4+1]; - cr[n] = data[n*4+2]; - y [n*2+1] = data[n*4+3]; - } - }); - - if(audio && SUCCEEDED(audio->GetBytes(&bytes))) - { - auto sample_frame_count = audio->GetSampleFrameCount(); - auto audio_data = reinterpret_cast(bytes); - audio_data_.insert(audio_data_.end(), audio_data, audio_data + sample_frame_count*2); - - if(audio_data_.size() > 3840) - { - frame->audio_data() = std::vector(audio_data_.begin(), audio_data_.begin() + 3840); - audio_data_.erase(audio_data_.begin(), audio_data_.begin() + 3840); - frame_buffer_.try_push(frame); - } - } - else - frame_buffer_.try_push(frame); - - return S_OK; - } - - IDeckLinkDisplayMode* get_display_mode(BMDDisplayMode mode) - { - CComPtr iterator; - IDeckLinkDisplayMode* d_mode; - - if (input_->GetDisplayModeIterator(&iterator) != S_OK) - return nullptr; - - while (iterator->Next(&d_mode) == S_OK) - { - if(d_mode->GetDisplayMode() == mode) - return d_mode; - } - return nullptr; - } - - safe_ptr get_frame() - { - frame_buffer_.try_pop(tail_); - return tail_; - } - - std::wstring print() const - { - return (parent_printer_ ? parent_printer_() + L"/" : L"") + L" [" + model_name_ + L"device:" + boost::lexical_cast(device_index_) + L"]"; - } -}; - -class decklink_producer : public core::frame_producer -{ - const size_t device_index_; - printer parent_printer_; - - std::unique_ptr input_; - - const core::video_format_desc format_desc_; - - executor executor_; -public: - - explicit decklink_producer(const core::video_format_desc& format_desc, size_t device_index) - : format_desc_(format_desc) - , device_index_(device_index){} - - ~decklink_producer() - { - executor_.invoke([this] - { - input_ = nullptr; - }); - } - - virtual void initialize(const safe_ptr& frame_factory) - { - executor_.start(); - executor_.invoke([=] - { - input_.reset(new decklink_input(format_desc_, device_index_, frame_factory, parent_printer_)); - }); - } - - virtual void set_parent_printer(const printer& parent_printer) { parent_printer_ = parent_printer;} - - virtual safe_ptr receive() - { - return input_->get_frame(); - } - - std::wstring print() const - { - return (parent_printer_ ? parent_printer_() + L"/" : L"") + (input_ ? input_->print() : L"Unknown Decklink [input-" + boost::lexical_cast(device_index_) + L"]"); - } -}; - -safe_ptr create_decklink_producer(const std::vector& params) -{ - if(params.empty() || !boost::iequals(params[0], "decklink")) - return core::frame_producer::empty(); - - size_t device_index = 1; - if(params.size() > 1) - { - try{device_index = boost::lexical_cast(params[1]);} - catch(boost::bad_lexical_cast&){} - } - - core::video_format_desc format_desc = core::video_format_desc::get(L"PAL"); - if(params.size() > 2) - { - format_desc = core::video_format_desc::get(params[2]); - format_desc = format_desc.format != core::video_format::invalid ? format_desc : core::video_format_desc::get(L"PAL"); - } - - return make_safe(format_desc, device_index); -} - -} \ No newline at end of file +/* +* Copyright (c) 2011 Sveriges Television AB +* +* 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 . +* +* Author: Robert Nagy, ronag89@gmail.com +*/ + +#include "../StdAfx.h" + +#include "decklink_producer.h" + +#include "../util/util.h" + +#include "../../ffmpeg/producer/filter/filter.h" +#include "../../ffmpeg/producer/util/util.h" +#include "../../ffmpeg/producer/muxer/frame_muxer.h" +#include "../../ffmpeg/producer/muxer/display_mode.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#if defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable : 4244) +#endif +extern "C" +{ + #define __STDC_CONSTANT_MACROS + #define __STDC_LIMIT_MACROS + #include +} +#if defined(_MSC_VER) +#pragma warning (pop) +#endif + +#include "../decklink_api.h" + +#include + +namespace caspar { namespace decklink { + +core::audio_channel_layout get_adjusted_channel_layout(core::audio_channel_layout layout) +{ + if (layout.num_channels <= 2) + layout.num_channels = 2; + else if (layout.num_channels <= 8) + layout.num_channels = 8; + else + layout.num_channels = 16; + + return layout; +} + +template +std::wstring to_string(const T& cadence) +{ + return boost::join(cadence | boost::adaptors::transformed([](size_t i) { return boost::lexical_cast(i); }), L", "); +} + +class decklink_producer : boost::noncopyable, public IDeckLinkInputCallback +{ + const int device_index_; + core::monitor::subject monitor_subject_; + spl::shared_ptr graph_; + caspar::timer tick_timer_; + + com_ptr decklink_ = get_device(device_index_); + com_iface_ptr input_ = iface_cast(decklink_); + com_iface_ptr attributes_ = iface_cast(decklink_); + + const std::wstring model_name_ = get_model_name(decklink_); + const std::wstring filter_; + + core::video_format_desc in_format_desc_; + core::video_format_desc out_format_desc_; + std::vector audio_cadence_ = out_format_desc_.audio_cadence; + boost::circular_buffer sync_buffer_ { audio_cadence_.size() }; + spl::shared_ptr frame_factory_; + core::audio_channel_layout channel_layout_; + ffmpeg::frame_muxer muxer_ { in_format_desc_.fps, frame_factory_, out_format_desc_, channel_layout_, filter_ }; + + core::constraints constraints_ { in_format_desc_.width, in_format_desc_.height }; + + tbb::concurrent_bounded_queue frame_buffer_; + + std::exception_ptr exception_; + +public: + decklink_producer( + const core::video_format_desc& in_format_desc, + int device_index, + const spl::shared_ptr& frame_factory, + const core::video_format_desc& out_format_desc, + const core::audio_channel_layout& channel_layout, + const std::wstring& filter) + : device_index_(device_index) + , filter_(filter) + , in_format_desc_(in_format_desc) + , out_format_desc_(out_format_desc) + , frame_factory_(frame_factory) + , channel_layout_(get_adjusted_channel_layout(channel_layout)) + { + frame_buffer_.set_capacity(2); + + graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f)); + graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.3f)); + graph_->set_color("frame-time", diagnostics::color(1.0f, 0.0f, 0.0f)); + graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f)); + graph_->set_color("output-buffer", diagnostics::color(0.0f, 1.0f, 0.0f)); + graph_->set_text(print()); + diagnostics::register_graph(graph_); + + bool will_attempt_dma; + auto display_mode = get_display_mode(input_, in_format_desc.format, bmdFormat8BitYUV, bmdVideoInputFlagDefault, will_attempt_dma); + + // NOTE: bmdFormat8BitARGB is currently not supported by any decklink card. (2011-05-08) + if(FAILED(input_->EnableVideoInput(display_mode, bmdFormat8BitYUV, 0))) + CASPAR_THROW_EXCEPTION(caspar_exception() + << msg_info(print() + L" Could not enable video input.") + << boost::errinfo_api_function("EnableVideoInput")); + + if(FAILED(input_->EnableAudioInput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, static_cast(channel_layout_.num_channels)))) + CASPAR_THROW_EXCEPTION(caspar_exception() + << msg_info(print() + L" Could not enable audio input.") + << boost::errinfo_api_function("EnableAudioInput")); + + if (FAILED(input_->SetCallback(this)) != S_OK) + CASPAR_THROW_EXCEPTION(caspar_exception() + << msg_info(print() + L" Failed to set input callback.") + << boost::errinfo_api_function("SetCallback")); + + if(FAILED(input_->StartStreams())) + CASPAR_THROW_EXCEPTION(caspar_exception() + << msg_info(print() + L" Failed to start input stream.") + << boost::errinfo_api_function("StartStreams")); + + CASPAR_LOG(info) << print() << L" Initialized"; + } + + ~decklink_producer() + { + if(input_ != nullptr) + { + input_->StopStreams(); + input_->DisableVideoInput(); + } + } + + core::constraints& pixel_constraints() + { + return constraints_; + } + + virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID, LPVOID*) {return E_NOINTERFACE;} + virtual ULONG STDMETHODCALLTYPE AddRef () {return 1;} + virtual ULONG STDMETHODCALLTYPE Release () {return 1;} + + virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(BMDVideoInputFormatChangedEvents /*notificationEvents*/, IDeckLinkDisplayMode* newDisplayMode, BMDDetectedVideoInputFormatFlags /*detectedSignalFlags*/) + { + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(IDeckLinkVideoInputFrame* video, IDeckLinkAudioInputPacket* audio) + { + if(!video) + return S_OK; + + try + { + graph_->set_value("tick-time", tick_timer_.elapsed()*out_format_desc_.fps*0.5); + tick_timer_.restart(); + + caspar::timer frame_timer; + + // Video + + void* video_bytes = nullptr; + if(FAILED(video->GetBytes(&video_bytes)) || !video_bytes) + return S_OK; + + auto video_frame = ffmpeg::create_frame(); + + video_frame->data[0] = reinterpret_cast(video_bytes); + video_frame->linesize[0] = video->GetRowBytes(); + video_frame->format = PIX_FMT_UYVY422; + video_frame->width = video->GetWidth(); + video_frame->height = video->GetHeight(); + video_frame->interlaced_frame = in_format_desc_.field_mode != core::field_mode::progressive; + video_frame->top_field_first = in_format_desc_.field_mode == core::field_mode::upper ? 1 : 0; + + monitor_subject_ + << core::monitor::message("/file/name") % model_name_ + << core::monitor::message("/file/path") % device_index_ + << core::monitor::message("/file/video/width") % video->GetWidth() + << core::monitor::message("/file/video/height") % video->GetHeight() + << core::monitor::message("/file/video/field") % u8(!video_frame->interlaced_frame ? "progressive" : (video_frame->top_field_first ? "upper" : "lower")) + << core::monitor::message("/file/audio/sample-rate") % 48000 + << core::monitor::message("/file/audio/channels") % 2 + << core::monitor::message("/file/audio/format") % u8(av_get_sample_fmt_name(AV_SAMPLE_FMT_S32)) + << core::monitor::message("/file/fps") % in_format_desc_.fps; + + // Audio + + auto audio_frame = ffmpeg::create_frame(); + audio_frame->format = AV_SAMPLE_FMT_S32; + core::mutable_audio_buffer audio_buf; + + if (audio) + { + void* audio_bytes = nullptr; + if (FAILED(audio->GetBytes(&audio_bytes)) || !audio_bytes) + return S_OK; + + + audio_frame->data[0] = reinterpret_cast(audio_bytes); + audio_frame->linesize[0] = audio->GetSampleFrameCount() * channel_layout_.num_channels * sizeof(int32_t); + audio_frame->nb_samples = audio->GetSampleFrameCount(); + } + else + { + audio_buf.resize(audio_cadence_.front() * channel_layout_.num_channels, 0); + audio_frame->data[0] = reinterpret_cast(audio_buf.data()); + audio_frame->linesize[0] = audio_cadence_.front() * channel_layout_.num_channels * sizeof(int32_t); + audio_frame->nb_samples = audio_cadence_.front(); + } + + // Note: Uses 1 step rotated cadence for 1001 modes (1602, 1602, 1601, 1602, 1601) + // This cadence fills the audio mixer most optimally. + + sync_buffer_.push_back(audio_frame->nb_samples); + if(!boost::range::equal(sync_buffer_, audio_cadence_)) + { + CASPAR_LOG(trace) << print() << L" Syncing audio. Expected cadence: " << to_string(audio_cadence_) << L" Got cadence: " << to_string(sync_buffer_); + return S_OK; + } + boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1); + + // PUSH + + muxer_.push_video(video_frame); + muxer_.push_audio(audio_frame); + + // POLL + + auto frame = core::draw_frame::late(); + if(!muxer_.empty()) + { + frame = std::move(muxer_.front()); + muxer_.pop(); + + if(!frame_buffer_.try_push(frame)) + { + auto dummy = core::draw_frame::empty(); + frame_buffer_.try_pop(dummy); + frame_buffer_.try_push(frame); + + graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame"); + } + } + + graph_->set_value("frame-time", frame_timer.elapsed()*out_format_desc_.fps*0.5); + monitor_subject_ << core::monitor::message("/profiler/time") % frame_timer.elapsed() % out_format_desc_.fps; + + graph_->set_value("output-buffer", static_cast(frame_buffer_.size())/static_cast(frame_buffer_.capacity())); + monitor_subject_ << core::monitor::message("/buffer") % frame_buffer_.size() % frame_buffer_.capacity(); + } + catch(...) + { + exception_ = std::current_exception(); + return E_FAIL; + } + + return S_OK; + } + + core::draw_frame get_frame() + { + if(exception_ != nullptr) + std::rethrow_exception(exception_); + + core::draw_frame frame = core::draw_frame::late(); + if(!frame_buffer_.try_pop(frame)) + graph_->set_tag(diagnostics::tag_severity::WARNING, "late-frame"); + graph_->set_value("output-buffer", static_cast(frame_buffer_.size())/static_cast(frame_buffer_.capacity())); + return frame; + } + + std::wstring print() const + { + return model_name_ + L" [" + boost::lexical_cast(device_index_) + L"|" + in_format_desc_.name + L"]"; + } + + core::monitor::subject& monitor_output() + { + return monitor_subject_; + } +}; + +class decklink_producer_proxy : public core::frame_producer_base +{ + std::unique_ptr producer_; + const uint32_t length_; + executor executor_; +public: + explicit decklink_producer_proxy( + const core::video_format_desc& in_format_desc, + const spl::shared_ptr& frame_factory, + const core::video_format_desc& out_format_desc, + const core::audio_channel_layout& channel_layout, + int device_index, + const std::wstring& filter_str, + uint32_t length) + : executor_(L"decklink_producer[" + boost::lexical_cast(device_index) + L"]") + , length_(length) + { + auto ctx = core::diagnostics::call_context::for_thread(); + executor_.invoke([=] + { + core::diagnostics::call_context::for_thread() = ctx; + com_initialize(); + producer_.reset(new decklink_producer(in_format_desc, device_index, frame_factory, out_format_desc, channel_layout, filter_str)); + }); + } + + ~decklink_producer_proxy() + { + executor_.invoke([=] + { + producer_.reset(); + com_uninitialize(); + }); + } + + core::monitor::subject& monitor_output() + { + return producer_->monitor_output(); + } + + // frame_producer + + core::draw_frame receive_impl() override + { + return producer_->get_frame(); + } + + core::constraints& pixel_constraints() override + { + return producer_->pixel_constraints(); + } + + uint32_t nb_frames() const override + { + return length_; + } + + std::wstring print() const override + { + return producer_->print(); + } + + std::wstring name() const override + { + return L"decklink"; + } + + boost::property_tree::wptree info() const override + { + boost::property_tree::wptree info; + info.add(L"type", L"decklink"); + return info; + } +}; + +void describe_producer(core::help_sink& sink, const core::help_repository& repo) +{ + sink.short_description(L"Allows video sources to be input from BlackMagic Design cards."); + sink.syntax(L"DECKLINK [device:int],DEVICE [device:int] {FILTER [filter:string]} {LENGTH [length:int]} {FORMAT [format:string]} {CHANNEL_LAYOUT [channel_layout:string]}"); + sink.para()->text(L"Allows video sources to be input from BlackMagic Design cards. Parameters:"); + sink.definitions() + ->item(L"device", L"The decklink device to stream the input from. See the Blackmagic control panel for the order of devices in your system.") + ->item(L"filter", L"If specified, sets an FFmpeg video filter to use.") + ->item(L"length", L"Optionally specify a limit on how many frames to produce.") + ->item(L"format", L"Specifies what video format to expect on the incoming SDI/HDMI signal. If not specified the video format of the channel is assumed.") + ->item(L"channel_layout", L"Specifies what audio channel layout to expect on the incoming SDI/HDMI signal. If not specified, stereo is assumed."); + sink.para()->text(L"Examples:"); + sink.example(L">> PLAY 1-10 DECKLINK DEVICE 2", L"Play using decklink device 2 expecting the video signal to have the same video format as the channel."); + sink.example(L">> PLAY 1-10 DECKLINK DEVICE 2 FORMAT PAL FILTER yadif=1:-1", L"Play using decklink device 2 expecting the video signal to be in PAL and deinterlace it."); + sink.example(L">> PLAY 1-10 DECKLINK DEVICE 2 LENGTH 1000", L"Play using decklink device 2 but only produce 1000 frames."); + sink.example(L">> PLAY 1-10 DECKLINK DEVICE 2 CHANNEL_LAYOUT smpte", L"Play using decklink device 2 and expect smpte surround sound."); +} + +spl::shared_ptr create_producer(const core::frame_producer_dependencies& dependencies, const std::vector& params) +{ + if(params.empty() || !boost::iequals(params.at(0), "decklink")) + return core::frame_producer::empty(); + + auto device_index = get_param(L"DEVICE", params, -1); + if(device_index == -1) + device_index = boost::lexical_cast(params.at(1)); + + auto filter_str = get_param(L"FILTER", params); + auto length = get_param(L"LENGTH", params, std::numeric_limits::max()); + auto in_format_desc = core::video_format_desc(get_param(L"FORMAT", params, L"INVALID")); + + if(in_format_desc.format == core::video_format::invalid) + in_format_desc = dependencies.format_desc; + + auto channel_layout_spec = get_param(L"CHANNEL_LAYOUT", params); + auto channel_layout = *core::audio_channel_layout_repository::get_default()->get_layout(L"stereo"); + + 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 not found.")); + + channel_layout = *found_layout; + } + + return create_destroy_proxy(spl::make_shared( + in_format_desc, + dependencies.frame_factory, + dependencies.format_desc, + channel_layout, + device_index, + filter_str, + length)); +} + +}}