X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fdecklink%2Fproducer%2Fdecklink_producer.cpp;h=5200a6900c72f5c4568faad00657d7ed0d343dac;hb=1dba809bb1d6a538271140c66d104b06045648b3;hp=331aa3f818332731d07a4fa351fed23b2d3373da;hpb=ff484db284f973c33bacf87c464e185c4f88a2d6;p=casparcg diff --git a/modules/decklink/producer/decklink_producer.cpp b/modules/decklink/producer/decklink_producer.cpp index 331aa3f81..5200a6900 100644 --- a/modules/decklink/producer/decklink_producer.cpp +++ b/modules/decklink/producer/decklink_producer.cpp @@ -19,11 +19,10 @@ * Author: Robert Nagy, ronag89@gmail.com */ -#include "../stdafx.h" +#include "../StdAfx.h" #include "decklink_producer.h" -#include "../interop/DeckLinkAPI_h.h" #include "../util/util.h" #include "../../ffmpeg/producer/filter/filter.h" @@ -36,20 +35,25 @@ #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) @@ -65,70 +69,76 @@ extern "C" #pragma warning (pop) #endif -#pragma warning(push) -#pragma warning(disable : 4996) - - #include - - #include - #include - -#pragma warning(push) +#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 { - monitor::basic_subject event_subject_; + const int device_index_; + core::monitor::subject monitor_subject_; spl::shared_ptr graph_; - boost::timer tick_timer_; + caspar::timer tick_timer_; - CComPtr decklink_; - CComQIPtr input_; - CComQIPtr attributes_; + 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_; - const int device_index_; + const std::wstring model_name_ = get_model_name(decklink_); const std::wstring filter_; - std::vector audio_cadence_; - boost::circular_buffer sync_buffer_; - ffmpeg::frame_muxer muxer_; - - spl::shared_ptr frame_factory_; core::video_format_desc in_format_desc_; core::video_format_desc out_format_desc_; - core::constraints constraints_; + 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_; + 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 std::wstring& filter) - : decklink_(get_device(device_index)) - , input_(decklink_) - , attributes_(decklink_) - , model_name_(get_model_name(decklink_)) - , device_index_(device_index) + 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) - , constraints_(in_format_desc.width, in_format_desc.height) - , muxer_(in_format_desc.fps, frame_factory, out_format_desc, filter) - , audio_cadence_(out_format_desc.audio_cadence) - , sync_buffer_(out_format_desc.audio_cadence.size()) , 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("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)); @@ -136,7 +146,8 @@ public: graph_->set_text(print()); diagnostics::register_graph(graph_); - auto display_mode = get_display_mode(input_, in_format_desc.format, bmdFormat8BitYUV, bmdVideoInputFlagDefault); + 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))) @@ -144,8 +155,8 @@ public: << msg_info(print() + L" Could not enable video input.") << boost::errinfo_api_function("EnableVideoInput")); - if(FAILED(input_->EnableAudioInput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, static_cast(in_format_desc.audio_channels)))) - CASPAR_THROW_EXCEPTION(caspar_exception() + 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")); @@ -195,7 +206,7 @@ public: graph_->set_value("tick-time", tick_timer_.elapsed()*out_format_desc_.fps*0.5); tick_timer_.restart(); - boost::timer frame_timer; + caspar::timer frame_timer; // Video @@ -213,46 +224,57 @@ public: 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; - event_subject_ << monitor::event("file/name") % model_name_ - << monitor::event("file/path") % device_index_ - << monitor::event("file/video/width") % video->GetWidth() - << monitor::event("file/video/height") % video->GetHeight() - << monitor::event("file/video/field") % u8(!video_frame->interlaced_frame ? "progressive" : (video_frame->top_field_first ? "upper" : "lower")) - << monitor::event("file/audio/sample-rate") % 48000 - << monitor::event("file/audio/channels") % 2 - << monitor::event("file/audio/format") % u8(av_get_sample_fmt_name(AV_SAMPLE_FMT_S32)) - << monitor::event("file/fps") % in_format_desc_.fps; + 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; - if(FAILED(audio->GetBytes(&audio_bytes)) || !audio_bytes) - return S_OK; - auto audio_frame = ffmpeg::create_frame(); + audio_frame->format = AV_SAMPLE_FMT_S32; + core::mutable_audio_buffer audio_buf; - audio_frame->data[0] = reinterpret_cast(audio_bytes); - audio_frame->linesize[0] = audio->GetSampleFrameCount()*out_format_desc_.audio_channels*sizeof(int32_t); - audio_frame->nb_samples = audio->GetSampleFrameCount(); - audio_frame->format = AV_SAMPLE_FMT_S32; + 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->GetSampleFrameCount()); + sync_buffer_.push_back(audio_frame->nb_samples); if(!boost::range::equal(sync_buffer_, audio_cadence_)) { - CASPAR_LOG(trace) << print() << L" Syncing audio."; + 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); + muxer_.push_video(video_frame); + muxer_.push_audio(audio_frame); // POLL @@ -268,15 +290,15 @@ public: frame_buffer_.try_pop(dummy); frame_buffer_.try_push(frame); - graph_->set_tag("dropped-frame"); + graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame"); } } graph_->set_value("frame-time", frame_timer.elapsed()*out_format_desc_.fps*0.5); - event_subject_ << monitor::event("profiler/time") % frame_timer.elapsed() % out_format_desc_.fps; + 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())); - event_subject_ << monitor::event("buffer") % frame_buffer_.size() % frame_buffer_.capacity(); + monitor_subject_ << core::monitor::message("/buffer") % frame_buffer_.size() % frame_buffer_.capacity(); } catch(...) { @@ -294,7 +316,7 @@ public: core::draw_frame frame = core::draw_frame::late(); if(!frame_buffer_.try_pop(frame)) - graph_->set_tag("late-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; } @@ -304,14 +326,9 @@ public: return model_name_ + L" [" + boost::lexical_cast(device_index_) + L"|" + in_format_desc_.name + L"]"; } - void subscribe(const monitor::observable::observer_ptr& o) + core::monitor::subject& monitor_output() { - event_subject_.subscribe(o); - } - - void unsubscribe(const monitor::observable::observer_ptr& o) - { - event_subject_.unsubscribe(o); + return monitor_subject_; } }; @@ -321,18 +338,23 @@ class decklink_producer_proxy : public core::frame_producer_base 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, - int device_index, - const std::wstring& filter_str, uint32_t length) + 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([=] { - CoInitialize(nullptr); - producer_.reset(new decklink_producer(in_format_desc, device_index, frame_factory, out_format_desc, filter_str)); + 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)); }); } @@ -341,18 +363,13 @@ public: executor_.invoke([=] { producer_.reset(); - CoUninitialize(); + com_uninitialize(); }); } - void subscribe(const monitor::observable::observer_ptr& o) override + core::monitor::subject& monitor_output() { - producer_->subscribe(o); - } - - void unsubscribe(const monitor::observable::observer_ptr& o) override - { - producer_->unsubscribe(o); + return producer_->monitor_output(); } // frame_producer @@ -390,9 +407,27 @@ public: } }; -spl::shared_ptr create_producer(const spl::shared_ptr& frame_factory, const core::video_format_desc& out_format_desc, const std::vector& params) +void describe_producer(core::help_sink& sink, const core::help_repository& repo) { - if(params.empty() || !boost::iequals(params[0], "decklink")) + 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); @@ -404,9 +439,29 @@ spl::shared_ptr create_producer(const spl::shared_ptrget_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, frame_factory, out_format_desc, device_index, filter_str, length)); + return create_destroy_proxy(spl::make_shared( + in_format_desc, + dependencies.frame_factory, + dependencies.format_desc, + channel_layout, + device_index, + filter_str, + length)); } -}} \ No newline at end of file +}}