]> git.sesse.net Git - casparcg/blobdiff - modules/decklink/consumer/decklink_consumer.cpp
Clarified logic around memcpy or not in Linux decklink consumer.
[casparcg] / modules / decklink / consumer / decklink_consumer.cpp
index 32e86f8a30e31bbd746e346b4044a7f3ccbf1be5..e453e33382d1444ef8c39aa59d64062460e5b9b0 100644 (file)
@@ -28,6 +28,7 @@
 #include "../decklink_api.h"
 
 #include <core/frame/frame.h>
+#include <core/frame/audio_channel_layout.h>
 #include <core/mixer/audio/audio_mixer.h>
 #include <core/consumer/frame_consumer.h>
 #include <core/diagnostics/call_context.h>
@@ -39,6 +40,8 @@
 #include <common/diagnostics/graph.h>
 #include <common/except.h>
 #include <common/memshfl.h>
+#include <common/memcpy.h>
+#include <common/no_init_proxy.h>
 #include <common/array.h>
 #include <common/future.h>
 #include <common/cache_aligned_vector.h>
@@ -61,23 +64,24 @@ struct configuration
                internal_keyer,
                external_keyer,
                external_separate_device_keyer,
-               default_keyer
+               default_keyer                                   = external_keyer
        };
 
        enum class latency_t
        {
                low_latency,
                normal_latency,
-               default_latency
+               default_latency = normal_latency
        };
 
-       int                     device_index            = 1;
-       int                     key_device_idx          = 0;
-       bool            embedded_audio          = true;
-       keyer_t         keyer                           = keyer_t::default_keyer;
-       latency_t       latency                         = latency_t::default_latency;
-       bool            key_only                        = false;
-       int                     base_buffer_depth       = 3;
+       int                                                     device_index            = 1;
+       int                                                     key_device_idx          = 0;
+       bool                                            embedded_audio          = true;
+       keyer_t                                         keyer                           = keyer_t::default_keyer;
+       latency_t                                       latency                         = latency_t::default_latency;
+       bool                                            key_only                        = false;
+       int                                                     base_buffer_depth       = 3;
+       core::audio_channel_layout      out_channel_layout      = core::audio_channel_layout::invalid();
        
        int buffer_depth() const
        {
@@ -88,6 +92,31 @@ struct configuration
        {
                return key_device_idx == 0 ? device_index + 1 : key_device_idx;
        }
+
+       core::audio_channel_layout get_adjusted_layout(const core::audio_channel_layout& in_layout) const
+       {
+               auto adjusted = out_channel_layout == core::audio_channel_layout::invalid() ? in_layout : out_channel_layout;
+
+               if (adjusted.num_channels == 1) // Duplicate mono-signal into both left and right.
+               {
+                       adjusted.num_channels = 2;
+                       adjusted.channel_order.push_back(adjusted.channel_order.at(0)); // Usually FC -> FC FC
+               }
+               else if (adjusted.num_channels == 2)
+               {
+                       adjusted.num_channels = 2;
+               }
+               else if (adjusted.num_channels <= 8)
+               {
+                       adjusted.num_channels = 8;
+               }
+               else // Over 8 always pad to 16 or drop >16
+               {
+                       adjusted.num_channels = 16;
+               }
+
+               return adjusted;
+       }
 };
 
 void set_latency(
@@ -113,8 +142,7 @@ void set_keyer(
                configuration::keyer_t keyer,
                const std::wstring& print)
 {
-       if (keyer == configuration::keyer_t::internal_keyer
-                       || keyer == configuration::keyer_t::external_separate_device_keyer)
+       if (keyer == configuration::keyer_t::internal_keyer)
        {
                BOOL value = true;
                if (SUCCEEDED(attributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)
@@ -142,19 +170,32 @@ void set_keyer(
 
 class decklink_frame : public IDeckLinkVideoFrame
 {
-       tbb::atomic<int>                                ref_count_;
-       core::const_frame                               frame_;
-       const core::video_format_desc   format_desc_;
+       tbb::atomic<int>                                                                ref_count_;
+       core::const_frame                                                               frame_;
+       const core::video_format_desc                                   format_desc_;
 
-       const bool                                              key_only_;
-       cache_aligned_vector<uint8_t>   data_;
+       const bool                                                                              key_only_;
+       bool                                                                                    needs_to_copy_;
+       cache_aligned_vector<no_init_proxy<uint8_t>>    data_;
 public:
-       decklink_frame(core::const_frame frame, const core::video_format_desc& format_desc, bool key_only)
+       decklink_frame(core::const_frame frame, const core::video_format_desc& format_desc, bool key_only, bool will_attempt_dma)
                : frame_(frame)
                , format_desc_(format_desc)
                , key_only_(key_only)
        {
                ref_count_ = 0;
+
+               bool dma_transfer_from_gl_buffer_impossible;
+
+#if !defined(_MSC_VER)
+               // On Linux Decklink cannot DMA transfer from memory returned by glMapBuffer (at least on nvidia)
+               dma_transfer_from_gl_buffer_impossible = true;
+#else
+               // On Windows it is possible.
+               dma_transfer_from_gl_buffer_impossible = false;
+#endif
+
+               needs_to_copy_ = will_attempt_dma && dma_transfer_from_gl_buffer_impossible;
        }
        
        // IUnknown
@@ -190,7 +231,7 @@ public:
                {
                        if(static_cast<int>(frame_.image_data().size()) != format_desc_.size)
                        {
-                               data_.resize(format_desc_.size, 0);
+                               data_.resize(format_desc_.size);
                                *buffer = data_.data();
                        }
                        else if(key_only_)
@@ -203,7 +244,16 @@ public:
                                *buffer = data_.data();
                        }
                        else
+                       {
                                *buffer = const_cast<uint8_t*>(frame_.image_data().begin());
+
+                               if (needs_to_copy_)
+                               {
+                                       data_.resize(frame_.image_data().size());
+                                       fast_memcpy(data_.data(), *buffer, frame_.image_data().size());
+                                       *buffer = data_.data();
+                               }
+                       }
                }
                catch(...)
                {
@@ -243,10 +293,6 @@ struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyab
 
        key_video_context(const configuration& config, const std::wstring& print)
                : config_(config)
-               , output_(decklink_)
-               , keyer_(decklink_)
-               , attributes_(decklink_)
-               , configuration_(decklink_)
        {
                current_presentation_delay_ = 0;
                scheduled_frames_completed_ = 0;
@@ -256,7 +302,7 @@ struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyab
 
                if (FAILED(output_->SetScheduledFrameCompletionCallback(this)))
                        CASPAR_THROW_EXCEPTION(caspar_exception()
-                                       << msg_info(u8(print) + " Failed to set key playback completion callback.")
+                                       << msg_info(print + L" Failed to set key playback completion callback.")
                                        << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
        }
 
@@ -264,11 +310,11 @@ struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyab
        void enable_video(BMDDisplayMode display_mode, const Print& print)
        {
                if (FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault)))
-                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable key video output."));
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Could not enable key video output."));
 
                if (FAILED(output_->SetScheduledFrameCompletionCallback(this)))
                        CASPAR_THROW_EXCEPTION(caspar_exception()
-                                       << msg_info(u8(print()) + " Failed to set key playback completion callback.")
+                                       << msg_info(print() + L" Failed to set key playback completion callback.")
                                        << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
        }
 
@@ -281,18 +327,18 @@ struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyab
                }
        }
 
-       STDMETHOD(QueryInterface(REFIID, LPVOID*))      { return E_NOINTERFACE; }
-       STDMETHOD_(ULONG, AddRef())                                     { return 1; }
-       STDMETHOD_(ULONG, Release())                            { return 1; }
+       virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)       {return E_NOINTERFACE;}
+       virtual ULONG STDMETHODCALLTYPE AddRef()                                                        {return 1;}
+       virtual ULONG STDMETHODCALLTYPE Release()                                                       {return 1;}
 
-       STDMETHOD(ScheduledPlaybackHasStopped())
+       virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped()
        {
                return S_OK;
        }
 
-       STDMETHOD(ScheduledFrameCompleted(
+       virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted(
                        IDeckLinkVideoFrame* completed_frame,
-                       BMDOutputFrameCompletionResult result))
+                       BMDOutputFrameCompletionResult result)
        {
                auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);
                current_presentation_delay_ = dframe->get_age_millis();
@@ -321,7 +367,11 @@ struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLink
        tbb::atomic<bool>                                                                       is_running_;
                
        const std::wstring                                                                      model_name_                             = get_model_name(decklink_);
+       bool                                                                                            will_attempt_dma_;
        const core::video_format_desc                                           format_desc_;
+       const core::audio_channel_layout                                        in_channel_layout_;
+       const core::audio_channel_layout                                        out_channel_layout_             = config_.get_adjusted_layout(in_channel_layout_);
+       core::audio_channel_remapper                                            channel_remapper_               { in_channel_layout_, out_channel_layout_ };
        const int                                                                                       buffer_size_                    = config_.buffer_depth(); // Minimum buffer-size 3.
 
        long long                                                                                       video_scheduled_                = 0;
@@ -335,17 +385,23 @@ struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLink
        tbb::concurrent_bounded_queue<core::const_frame>        audio_frame_buffer_;
        
        spl::shared_ptr<diagnostics::graph>                                     graph_;
-       tbb::atomic<int64_t>                                                            current_presentation_delay_;
        caspar::timer                                                                           tick_timer_;
        retry_task<bool>                                                                        send_completion_;
+       reference_signal_detector                                                       reference_signal_detector_      { output_ };
+       tbb::atomic<int64_t>                                                            current_presentation_delay_;
        tbb::atomic<int64_t>                                                            scheduled_frames_completed_;
        std::unique_ptr<key_video_context>                                      key_context_;
 
 public:
-       decklink_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index) 
+       decklink_consumer(
+                       const configuration& config,
+                       const core::video_format_desc& format_desc,
+                       const core::audio_channel_layout& in_channel_layout,
+                       int channel_index) 
                : channel_index_(channel_index)
                , config_(config)
                , format_desc_(format_desc)
+               , in_channel_layout_(in_channel_layout)
        {
                is_running_ = true;
                current_presentation_delay_ = 0;
@@ -376,7 +432,7 @@ public:
                graph_->set_text(print());
                diagnostics::register_graph(graph_);
                
-               enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault));
+               enable_video(get_display_mode(output_, format_desc_.format, bmdFormat8BitBGRA, bmdVideoOutputFlagDefault, will_attempt_dma_));
                                
                if(config.embedded_audio)
                        enable_audio();
@@ -411,11 +467,11 @@ public:
        
        void enable_audio()
        {
-               if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped)))
-                               CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable audio output."));
+               if(FAILED(output_->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, out_channel_layout_.num_channels, bmdAudioOutputStreamTimestamped)))
+                               CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Could not enable audio output."));
                                
                if(FAILED(output_->SetAudioCallback(this)))
-                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not set audio callback."));
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Could not set audio callback."));
 
                CASPAR_LOG(info) << print() << L" Enabled embedded-audio.";
        }
@@ -423,11 +479,11 @@ public:
        void enable_video(BMDDisplayMode display_mode)
        {
                if(FAILED(output_->EnableVideoOutput(display_mode, bmdVideoOutputFlagDefault))) 
-                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable fill video output."));
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Could not enable fill video output."));
                
                if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
                        CASPAR_THROW_EXCEPTION(caspar_exception() 
-                                                                       << msg_info(u8(print()) + " Failed to set fill playback completion callback.")
+                                                                       << msg_info(print() + L" Failed to set fill playback completion callback.")
                                                                        << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
 
                if (key_context_)
@@ -437,10 +493,10 @@ public:
        void start_playback()
        {
                if(FAILED(output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0))) 
-                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Failed to schedule fill playback."));
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to schedule fill playback."));
 
                if (key_context_ && FAILED(key_context_->output_->StartScheduledPlayback(0, format_desc_.time_scale, 1.0)))
-                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Failed to schedule key playback."));
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Failed to schedule key playback."));
        }
        
        virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)       {return E_NOINTERFACE;}
@@ -475,26 +531,26 @@ public:
 
                        if(result == bmdOutputFrameDisplayedLate)
                        {
-                               graph_->set_tag("late-frame");
+                               graph_->set_tag(diagnostics::tag_severity::WARNING, "late-frame");
                                video_scheduled_ += format_desc_.duration;
-                               audio_scheduled_ += dframe->audio_data().size() / format_desc_.audio_channels;
+                               audio_scheduled_ += dframe->audio_data().size() / out_channel_layout_.num_channels;
                                //++video_scheduled_;
                                //audio_scheduled_ += format_desc_.audio_cadence[0];
                                //++audio_scheduled_;
                        }
                        else if(result == bmdOutputFrameDropped)
-                               graph_->set_tag("dropped-frame");
+                               graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame");
                        else if(result == bmdOutputFrameFlushed)
-                               graph_->set_tag("flushed-frame");
+                               graph_->set_tag(diagnostics::tag_severity::WARNING, "flushed-frame");
+
+                       UINT32 buffered;
+                       output_->GetBufferedVideoFrameCount(&buffered);
+                       graph_->set_value("buffered-video", static_cast<double>(buffered) / (config_.buffer_depth()));
 
-                       auto frame = core::const_frame::empty();        
+                       auto frame = core::const_frame::empty();
                        video_frame_buffer_.pop(frame);
                        send_completion_.try_completion();
                        schedule_next_video(frame);     
-                       
-                       UINT32 buffered;
-                       output_->GetBufferedVideoFrameCount(&buffered);
-                       graph_->set_value("buffered-video", static_cast<double>(buffered)/format_desc_.fps);
                }
                catch(...)
                {
@@ -524,7 +580,7 @@ public:
                                }
                                else
                                {
-                                       schedule_next_audio(core::audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()] * format_desc_.audio_channels, 0));
+                                       schedule_next_audio(core::mutable_audio_buffer(format_desc_.audio_cadence[preroll % format_desc_.audio_cadence.size()] * out_channel_layout_.num_channels, 0));
                                }
                        }
                        else
@@ -533,14 +589,14 @@ public:
 
                                while(audio_frame_buffer_.try_pop(frame))
                                {
+                                       UINT32 buffered;
+                                       output_->GetBufferedAudioSampleFrameCount(&buffered);
+                                       graph_->set_value("buffered-audio", static_cast<double>(buffered) / (format_desc_.audio_cadence[0] * config_.buffer_depth()));
+
                                        send_completion_.try_completion();
-                                       schedule_next_audio(frame.audio_data());
+                                       schedule_next_audio(channel_remapper_.mix_and_rearrange(frame.audio_data()));
                                }
                        }
-
-                       UINT32 buffered;
-                       output_->GetBufferedAudioSampleFrameCount(&buffered);
-                       graph_->set_value("buffered-audio", static_cast<double>(buffered) / (format_desc_.audio_cadence[0] * format_desc_.audio_channels * 2));
                }
                catch(...)
                {
@@ -555,7 +611,7 @@ public:
        template<typename T>
        void schedule_next_audio(const T& audio_data)
        {
-               auto sample_frame_count = static_cast<int>(audio_data.size()/format_desc_.audio_channels);
+               auto sample_frame_count = static_cast<int>(audio_data.size()/out_channel_layout_.num_channels);
 
                audio_container_.push_back(std::vector<int32_t>(audio_data.begin(), audio_data.end()));
 
@@ -569,12 +625,12 @@ public:
        {
                if (key_context_)
                {
-                       auto key_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, true));
-                       if (FAILED(key_context_->output_->ScheduleVideoFrame(key_frame, video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
+                       auto key_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, true, will_attempt_dma_));
+                       if (FAILED(key_context_->output_->ScheduleVideoFrame(get_raw(key_frame), video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
                                CASPAR_LOG(error) << print() << L" Failed to schedule key video.";
                }
 
-               auto fill_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, config_.key_only));
+               auto fill_frame = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, config_.key_only, will_attempt_dma_));
                if (FAILED(output_->ScheduleVideoFrame(get_raw(fill_frame), video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
                        CASPAR_LOG(error) << print() << L" Failed to schedule fill video.";
 
@@ -582,6 +638,8 @@ public:
 
                graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
                tick_timer_.restart();
+
+               reference_signal_detector_.detect_change([this]() { return print(); });
        }
 
        std::future<bool> send(core::const_frame frame)
@@ -595,7 +653,7 @@ public:
                        std::rethrow_exception(exception);              
 
                if(!is_running_)
-                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Is not running."));
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(print() + L" Is not running."));
                
                bool audio_ready = !config_.embedded_audio;
                bool video_ready = false;
@@ -669,13 +727,13 @@ public:
 
        // frame_consumer
        
-       void initialize(const core::video_format_desc& format_desc, int channel_index) override
+       void initialize(const core::video_format_desc& format_desc, const core::audio_channel_layout& channel_layout, int channel_index) override
        {
                format_desc_ = format_desc;
                executor_.invoke([=]
                {
                        consumer_.reset();
-                       consumer_.reset(new decklink_consumer(config_, format_desc, channel_index));                    
+                       consumer_.reset(new decklink_consumer(config_, format_desc, channel_layout, channel_index));                    
                });
        }
        
@@ -742,7 +800,8 @@ void describe_consumer(core::help_sink& sink, const core::help_repository& repo)
                                L"{[keyer:INTERNAL_KEY,EXTERNAL_KEY,EXTERNAL_SEPARATE_DEVICE_KEY]} "
                                L"{[low_latency:LOW_LATENCY]} "
                                L"{[embedded_audio:EMBEDDED_AUDIO]} "
-                               L"{[key_only:KEY_ONLY]}");
+                               L"{[key_only:KEY_ONLY]} "
+                               L"{CHANNEL_LAYOUT [channel_layout:string]}");
        sink.para()->text(L"Sends video on an SDI output using Blackmagic Decklink video cards.");
        sink.definitions()
                ->item(L"device_index", L"The Blackmagic video card to use (See Blackmagic control panel for card order). Default is 1.")
@@ -754,7 +813,8 @@ void describe_consumer(core::help_sink& sink, const core::help_repository& repo)
                ->item(L"key_only",
                                L" will extract only the alpha channel from the "
                                L"channel. This is useful when you have two SDI video cards, and neither has native support "
-                               L"for separate fill/key output");
+                               L"for separate fill/key output")
+               ->item(L"channel_layout", L"If specified, overrides the audio channel layout used by the channel.");
        sink.para()->text(L"Examples:");
        sink.example(L">> ADD 1 DECKLINK", L"for using the default device_index of 1.");
        sink.example(L">> ADD 1 DECKLINK 2", L"uses device_index 2.");
@@ -793,6 +853,18 @@ spl::shared_ptr<core::frame_consumer> create_consumer(
        config.embedded_audio   = contains_param(L"EMBEDDED_AUDIO", params);
        config.key_only                 = contains_param(L"KEY_ONLY", params);
 
+       auto channel_layout = get_param(L"CHANNEL_LAYOUT", params);
+
+       if (!channel_layout.empty())
+       {
+               auto found_layout = core::audio_channel_layout_repository::get_default()->get_layout(channel_layout);
+
+               if (!found_layout)
+                       CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Channel layout " + channel_layout + L" not found."));
+
+               config.out_channel_layout = *found_layout;
+       }
+
        return spl::make_shared<decklink_consumer_proxy>(config);
 }
 
@@ -809,12 +881,26 @@ spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(
        else if (keyer == L"external_separate_device")
                config.keyer = configuration::keyer_t::external_separate_device_keyer;
 
-       auto latency = ptree.get(L"latency", L"normal");
+       auto latency = ptree.get(L"latency", L"default");
        if(latency == L"low")
                config.latency = configuration::latency_t::low_latency;
        else if(latency == L"normal")
                config.latency = configuration::latency_t::normal_latency;
 
+       auto channel_layout = ptree.get_optional<std::wstring>(L"channel-layout");
+
+       if (channel_layout)
+       {
+               CASPAR_SCOPED_CONTEXT_MSG("/channel-layout")
+
+               auto found_layout = core::audio_channel_layout_repository::get_default()->get_layout(*channel_layout);
+
+               if (!found_layout)
+                       CASPAR_THROW_EXCEPTION(user_error() << msg_info(L"Channel layout " + *channel_layout + L" not found."));
+
+               config.out_channel_layout = *found_layout;
+       }
+
        config.key_only                         = ptree.get(L"key-only",                config.key_only);
        config.device_index                     = ptree.get(L"device",                  config.device_index);
        config.key_device_idx           = ptree.get(L"key-device",              config.key_device_idx);