X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fdecklink%2Fproducer%2Fdecklink_producer.cpp;h=a6ead70661933b9bf96cc44f518ccd0ff0bc96da;hb=e0e1a2b1fdcab66e8da72e3eaa2db5564f8f9d72;hp=50940429427e27967881ac9c4b749fe80a5117d4;hpb=1d7b421d02c0aefa13834f195db0639c1def613f;p=casparcg diff --git a/modules/decklink/producer/decklink_producer.cpp b/modules/decklink/producer/decklink_producer.cpp index 509404294..a6ead7066 100644 --- a/modules/decklink/producer/decklink_producer.cpp +++ b/modules/decklink/producer/decklink_producer.cpp @@ -1,335 +1,502 @@ -/* -* 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 "../interop/DeckLinkAPI_h.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 - -#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 - -#pragma warning(push) -#pragma warning(disable : 4996) - - #include - - #include - #include - -#pragma warning(push) - -#include - -namespace caspar { namespace decklink { - -class decklink_producer : boost::noncopyable, public IDeckLinkInputCallback -{ - CComPtr decklink_; - CComQIPtr input_; - - const std::wstring model_name_; - const core::video_format_desc format_desc_; - const size_t device_index_; - - safe_ptr graph_; - boost::timer tick_timer_; - boost::timer frame_timer_; - - tbb::atomic flags_; - safe_ptr frame_factory_; - std::vector audio_cadence_; - - tbb::concurrent_bounded_queue> frame_buffer_; - - std::exception_ptr exception_; - - ffmpeg::frame_muxer muxer_; - - boost::circular_buffer sync_buffer_; - -public: - decklink_producer(const core::video_format_desc& format_desc, size_t device_index, const safe_ptr& frame_factory, const std::wstring& filter) - : decklink_(get_device(device_index)) - , input_(decklink_) - , model_name_(get_model_name(decklink_)) - , format_desc_(format_desc) - , device_index_(device_index) - , frame_factory_(frame_factory) - , audio_cadence_(frame_factory->get_video_format_desc().audio_cadence) - , muxer_(format_desc.fps, frame_factory, filter) - , sync_buffer_(format_desc.audio_cadence.size()) - { - flags_ = 0; - 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_); - - auto display_mode = get_display_mode(input_, format_desc_.format, bmdFormat8BitYUV, bmdVideoInputFlagDefault); - - // NOTE: bmdFormat8BitARGB is currently not supported by any decklink card. (2011-05-08) - if(FAILED(input_->EnableVideoInput(display_mode, bmdFormat8BitYUV, 0))) - BOOST_THROW_EXCEPTION(caspar_exception() - << msg_info(print() + L" Could not enable video input.") - << boost::errinfo_api_function("EnableVideoInput")); - - if(FAILED(input_->EnableAudioInput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, format_desc_.audio_channels))) - BOOST_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) - BOOST_THROW_EXCEPTION(caspar_exception() - << msg_info(print() + L" Failed to set input callback.") - << boost::errinfo_api_function("SetCallback")); - - if(FAILED(input_->StartStreams())) - BOOST_THROW_EXCEPTION(caspar_exception() - << msg_info(print() + L" Failed to start input stream.") - << boost::errinfo_api_function("StartStreams")); - } - - ~decklink_producer() - { - 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*/) - { - 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()*format_desc_.fps*0.5); - tick_timer_.restart(); - - frame_timer_.restart(); - - // PUSH - - void* bytes = nullptr; - if(FAILED(video->GetBytes(&bytes)) || !bytes) - return S_OK; - - safe_ptr av_frame(avcodec_alloc_frame(), av_free); - avcodec_get_frame_defaults(av_frame.get()); - - av_frame->data[0] = reinterpret_cast(bytes); - av_frame->linesize[0] = video->GetRowBytes(); - av_frame->format = PIX_FMT_UYVY422; - av_frame->width = video->GetWidth(); - av_frame->height = video->GetHeight(); - av_frame->interlaced_frame = format_desc_.field_mode != core::field_mode::progressive; - av_frame->top_field_first = format_desc_.field_mode == core::field_mode::upper ? 1 : 0; - - std::shared_ptr audio_buffer; - - // It is assumed that audio is always equal or ahead of video. - if(audio && SUCCEEDED(audio->GetBytes(&bytes)) && bytes) - { - auto sample_frame_count = audio->GetSampleFrameCount(); - auto audio_data = reinterpret_cast(bytes); - audio_buffer = std::make_shared(audio_data, audio_data + sample_frame_count*format_desc_.audio_channels); - } - else - audio_buffer = std::make_shared(audio_cadence_.front(), 0); - - // 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_buffer->size()); - if(!boost::range::equal(sync_buffer_, audio_cadence_)) - { - CASPAR_LOG(trace) << print() << L" Syncing audio."; - return S_OK; - } - - muxer_.push(audio_buffer); - muxer_.push(av_frame, flags_); - - boost::range::rotate(audio_cadence_, std::begin(audio_cadence_)+1); - - // POLL - - for(auto frame = muxer_.poll(); frame; frame = muxer_.poll()) - { - if(!frame_buffer_.try_push(make_safe_ptr(frame))) - graph_->set_tag("dropped-frame"); - } - - graph_->set_value("frame-time", frame_timer_.elapsed()*format_desc_.fps*0.5); - - graph_->set_value("output-buffer", static_cast(frame_buffer_.size())/static_cast(frame_buffer_.capacity())); - } - catch(...) - { - exception_ = std::current_exception(); - return E_FAIL; - } - - return S_OK; - } - - safe_ptr get_frame(int flags) - { - if(exception_ != nullptr) - std::rethrow_exception(exception_); - - flags_ = flags; - - safe_ptr frame = core::basic_frame::late(); - if(!frame_buffer_.try_pop(frame)) - graph_->set_tag("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"]"; - } -}; - -class decklink_producer_proxy : public core::frame_producer -{ - safe_ptr last_frame_; - com_context context_; - const uint32_t length_; -public: - - explicit decklink_producer_proxy(const safe_ptr& frame_factory, const core::video_format_desc& format_desc, size_t device_index, const std::wstring& filter_str, uint32_t length) - : context_(L"decklink_producer[" + boost::lexical_cast(device_index) + L"]") - , last_frame_(core::basic_frame::empty()) - , length_(length) - { - context_.reset([&]{return new decklink_producer(format_desc, device_index, frame_factory, filter_str);}); - } - - // frame_producer - - virtual safe_ptr receive(int flags) override - { - auto frame = context_->get_frame(flags); - if(frame != core::basic_frame::late()) - last_frame_ = frame; - return frame; - } - - virtual safe_ptr last_frame() const override - { - return last_frame_; - } - - virtual uint32_t nb_frames() const override - { - return length_; - } - - std::wstring print() const override - { - return context_->print(); - } - - virtual boost::property_tree::wptree info() const override - { - boost::property_tree::wptree info; - info.add(L"type", L"decklink-producer"); - return info; - } -}; - -safe_ptr create_producer(const safe_ptr& frame_factory, const std::vector& params) -{ - if(params.empty() || !boost::iequals(params[0], "decklink")) - return core::frame_producer::empty(); - - auto device_index = get_param(L"DEVICE", params, 1); - auto filter_str = get_param(L"FILTER", params); - auto length = get_param(L"LENGTH", params, std::numeric_limits::max()); - auto format_desc = core::video_format_desc(get_param(L"FORMAT", params, L"INVALID")); - - boost::replace_all(filter_str, L"DEINTERLACE", L"YADIF=0:-1"); - boost::replace_all(filter_str, L"DEINTERLACE_BOB", L"YADIF=1:-1"); - - if(format_desc.format == core::video_format::invalid) - format_desc = frame_factory->get_video_format_desc(); - - return create_producer_print_proxy( - create_producer_destroy_proxy( - make_safe(frame_factory, format_desc, device_index, filter_str, length))); -} - -}} \ 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 +#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_ = in_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_.framerate, + { ffmpeg::create_input_pad(in_format_desc_, channel_layout_.num_channels) }, + frame_factory_, + out_format_desc_, + channel_layout_, + filter_, + ffmpeg::filter::is_deinterlacing(filter_) + }; + + core::constraints constraints_ { in_format_desc_.width, in_format_desc_.height }; + + tbb::concurrent_bounded_queue frame_buffer_; + core::draw_frame last_frame_ = core::draw_frame::empty(); + + 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(4); + + 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")); + + // Wait for first frame until returning or give up after 2 seconds. + caspar::timer timeout_timer; + + while (frame_buffer_.size() < 1 && timeout_timer.elapsed() < 2.0) + boost::this_thread::sleep_for(boost::chrono::milliseconds(1)); + + 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) + { + ensure_gpf_handler_installed_for_thread("decklink-VideoInputFrameArrived"); + 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 = AVPixelFormat::AV_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; + video_frame->key_frame = 1; + + 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 + + std::shared_ptr audio_buffer; + void* audio_bytes = nullptr; + + // It is assumed that audio is always equal or ahead of video. + if (audio && SUCCEEDED(audio->GetBytes(&audio_bytes)) && audio_bytes) + { + auto sample_frame_count = audio->GetSampleFrameCount(); + auto audio_data = reinterpret_cast(audio_bytes); + + audio_buffer = std::make_shared( + audio_data, + audio_data + sample_frame_count * channel_layout_.num_channels); + } + else + audio_buffer = std::make_shared(audio_cadence_.front() * channel_layout_.num_channels, 0); + + // 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_buffer->size() / channel_layout_.num_channels); + 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({ audio_buffer }); + muxer_.push(static_cast>(video_frame)); + + // POLL + + for (auto frame = muxer_.poll(); frame != core::draw_frame::empty(); frame = muxer_.poll()) + { + 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 = last_frame_; + + if (!frame_buffer_.try_pop(frame)) + graph_->set_tag(diagnostics::tag_severity::WARNING, "late-frame"); + else + last_frame_ = 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"]"; + } + + boost::rational get_out_framerate() const + { + return muxer_.out_framerate(); + } + + 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; + } + + boost::rational get_out_framerate() const + { + return producer_->get_out_framerate(); + } +}; + +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; + } + + boost::ireplace_all(filter_str, L"DEINTERLACE_BOB", L"YADIF=1:-1"); + boost::ireplace_all(filter_str, L"DEINTERLACE_LQ", L"SEPARATEFIELDS"); + boost::ireplace_all(filter_str, L"DEINTERLACE", L"YADIF=0:-1"); + + auto producer = spl::make_shared( + in_format_desc, + dependencies.frame_factory, + dependencies.format_desc, + channel_layout, + device_index, + filter_str, + length); + + auto get_source_framerate = [=] { return producer->get_out_framerate(); }; + auto target_framerate = dependencies.format_desc.framerate; + + return core::create_destroy_proxy(core::create_framerate_producer( + producer, + get_source_framerate, + target_framerate, + dependencies.format_desc.field_mode, + dependencies.format_desc.audio_cadence)); +} +}}