]> git.sesse.net Git - casparcg/commitdiff
Merged support for external_separate_key in decklink_consumer
authorHelge Norberg <helge.norberg@svt.se>
Tue, 25 Aug 2015 14:59:17 +0000 (16:59 +0200)
committerHelge Norberg <helge.norberg@svt.se>
Tue, 25 Aug 2015 14:59:17 +0000 (16:59 +0200)
modules/decklink/consumer/decklink_consumer.cpp
shell/casparcg.config

index d68fa281907ec363e2feb52b088469842b9bf444..32e86f8a30e31bbd746e346b4044a7f3ccbf1be5 100644 (file)
@@ -60,6 +60,7 @@ struct configuration
        {
                internal_keyer,
                external_keyer,
+               external_separate_device_keyer,
                default_keyer
        };
 
@@ -71,6 +72,7 @@ struct configuration
        };
 
        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;
@@ -81,8 +83,63 @@ struct configuration
        {
                return base_buffer_depth + (latency == latency_t::low_latency ? 0 : 1) + (embedded_audio ? 1 : 0);
        }
+
+       int key_device_index() const
+       {
+               return key_device_idx == 0 ? device_index + 1 : key_device_idx;
+       }
 };
 
+void set_latency(
+               const com_iface_ptr<IDeckLinkConfiguration>& config,
+               configuration::latency_t latency,
+               const std::wstring& print)
+{
+       if (latency == configuration::latency_t::low_latency)
+       {
+               config->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
+               CASPAR_LOG(info) << print << L" Enabled low-latency mode.";
+       }
+       else if (latency == configuration::latency_t::normal_latency)
+       {
+               config->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
+               CASPAR_LOG(info) << print << L" Disabled low-latency mode.";
+       }
+}
+
+void set_keyer(
+               const com_iface_ptr<IDeckLinkAttributes>& attributes,
+               const com_iface_ptr<IDeckLinkKeyer>& decklink_keyer,
+               configuration::keyer_t keyer,
+               const std::wstring& print)
+{
+       if (keyer == configuration::keyer_t::internal_keyer
+                       || keyer == configuration::keyer_t::external_separate_device_keyer)
+       {
+               BOOL value = true;
+               if (SUCCEEDED(attributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)
+                       CASPAR_LOG(error) << print << L" Failed to enable internal keyer.";     
+               else if (FAILED(decklink_keyer->Enable(FALSE)))
+                       CASPAR_LOG(error) << print << L" Failed to enable internal keyer.";                     
+               else if (FAILED(decklink_keyer->SetLevel(255)))
+                       CASPAR_LOG(error) << print << L" Failed to set key-level to max.";
+               else
+                       CASPAR_LOG(info) << print << L" Enabled internal keyer.";               
+       }
+       else if (keyer == configuration::keyer_t::external_keyer)
+       {
+               BOOL value = true;
+               if (SUCCEEDED(attributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &value)) && !value)
+                       CASPAR_LOG(error) << print << L" Failed to enable external keyer.";     
+               else if (FAILED(decklink_keyer->Enable(TRUE)))                  
+                       CASPAR_LOG(error) << print << L" Failed to enable external keyer.";     
+               else if (FAILED(decklink_keyer->SetLevel(255)))
+                       CASPAR_LOG(error) << print << L" Failed to set key-level to max.";
+               else
+                       CASPAR_LOG(info) << print << L" Enabled external keyer.";                       
+       }
+}
+
 class decklink_frame : public IDeckLinkVideoFrame
 {
        tbb::atomic<int>                                ref_count_;
@@ -173,6 +230,80 @@ public:
        }
 };
 
+struct key_video_context : public IDeckLinkVideoOutputCallback, boost::noncopyable
+{
+       const configuration                                                                             config_;
+       com_ptr<IDeckLink>                                                                              decklink_                                       = get_device(config_.key_device_index());
+       com_iface_ptr<IDeckLinkOutput>                                                  output_                                         = iface_cast<IDeckLinkOutput>(decklink_);
+       com_iface_ptr<IDeckLinkKeyer>                                                   keyer_                                          = iface_cast<IDeckLinkKeyer>(decklink_);
+       com_iface_ptr<IDeckLinkAttributes>                                              attributes_                                     = iface_cast<IDeckLinkAttributes>(decklink_);
+       com_iface_ptr<IDeckLinkConfiguration>                                   configuration_                          = iface_cast<IDeckLinkConfiguration>(decklink_);
+       tbb::atomic<int64_t>                                                                    current_presentation_delay_;
+       tbb::atomic<int64_t>                                                                    scheduled_frames_completed_;
+
+       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;
+
+               set_latency(configuration_, config.latency, print);
+               set_keyer(attributes_, keyer_, config.keyer, print);
+
+               if (FAILED(output_->SetScheduledFrameCompletionCallback(this)))
+                       CASPAR_THROW_EXCEPTION(caspar_exception()
+                                       << msg_info(u8(print) + " Failed to set key playback completion callback.")
+                                       << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
+       }
+
+       template<typename Print>
+       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."));
+
+               if (FAILED(output_->SetScheduledFrameCompletionCallback(this)))
+                       CASPAR_THROW_EXCEPTION(caspar_exception()
+                                       << msg_info(u8(print()) + " Failed to set key playback completion callback.")
+                                       << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
+       }
+
+       virtual ~key_video_context()
+       {
+               if (output_)
+               {
+                       output_->StopScheduledPlayback(0, nullptr, 0);
+                       output_->DisableVideoOutput();
+               }
+       }
+
+       STDMETHOD(QueryInterface(REFIID, LPVOID*))      { return E_NOINTERFACE; }
+       STDMETHOD_(ULONG, AddRef())                                     { return 1; }
+       STDMETHOD_(ULONG, Release())                            { return 1; }
+
+       STDMETHOD(ScheduledPlaybackHasStopped())
+       {
+               return S_OK;
+       }
+
+       STDMETHOD(ScheduledFrameCompleted(
+                       IDeckLinkVideoFrame* completed_frame,
+                       BMDOutputFrameCompletionResult result))
+       {
+               auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);
+               current_presentation_delay_ = dframe->get_age_millis();
+               ++scheduled_frames_completed_;
+
+               // Let the fill callback keep the pace, so no scheduling here.
+
+               return S_OK;
+       }
+};
+
 struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable
 {              
        const int                                                                                       channel_index_;
@@ -184,29 +315,31 @@ struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLink
        com_iface_ptr<IDeckLinkKeyer>                                           keyer_                                  = iface_cast<IDeckLinkKeyer>(decklink_);
        com_iface_ptr<IDeckLinkAttributes>                                      attributes_                             = iface_cast<IDeckLinkAttributes>(decklink_);
 
-       tbb::spin_mutex                                     exception_mutex_;
-       std::exception_ptr                                  exception_;
+       tbb::spin_mutex                                                                         exception_mutex_;
+       std::exception_ptr                                                                      exception_;
 
-       tbb::atomic<bool>                                   is_running_;
+       tbb::atomic<bool>                                                                       is_running_;
                
-       const std::wstring                                  model_name_                         = get_model_name(decklink_);
-       const core::video_format_desc                       format_desc_;
-       const int                                           buffer_size_                        = config_.buffer_depth(); // Minimum buffer-size 3.
+       const std::wstring                                                                      model_name_                             = get_model_name(decklink_);
+       const core::video_format_desc                                           format_desc_;
+       const int                                                                                       buffer_size_                    = config_.buffer_depth(); // Minimum buffer-size 3.
 
-       long long                                           video_scheduled_            = 0;
-       long long                                           audio_scheduled_            = 0;
+       long long                                                                                       video_scheduled_                = 0;
+       long long                                                                                       audio_scheduled_                = 0;
 
-       int                                                 preroll_count_                      = 0;
+       int                                                                                                     preroll_count_                  = 0;
                
-       boost::circular_buffer<std::vector<int32_t>>        audio_container_            { buffer_size_ + 1 };
+       boost::circular_buffer<std::vector<int32_t>>            audio_container_                { buffer_size_ + 1 };
 
-       tbb::concurrent_bounded_queue<core::const_frame>    video_frame_buffer_;
-       tbb::concurrent_bounded_queue<core::const_frame>    audio_frame_buffer_;
+       tbb::concurrent_bounded_queue<core::const_frame>        video_frame_buffer_;
+       tbb::concurrent_bounded_queue<core::const_frame>        audio_frame_buffer_;
        
-       spl::shared_ptr<diagnostics::graph>                 graph_;
+       spl::shared_ptr<diagnostics::graph>                                     graph_;
        tbb::atomic<int64_t>                                                            current_presentation_delay_;
        caspar::timer                                                                           tick_timer_;
-       retry_task<bool>                                    send_completion_;
+       retry_task<bool>                                                                        send_completion_;
+       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) 
@@ -216,6 +349,7 @@ public:
        {
                is_running_ = true;
                current_presentation_delay_ = 0;
+               scheduled_frames_completed_ = 0;
                                
                video_frame_buffer_.set_capacity(1);
 
@@ -224,12 +358,21 @@ public:
                // samples from 2 frames in order to keep up
                audio_frame_buffer_.set_capacity((format_desc.fps > 50.0) ? 2 : 1);
 
+               if (config.keyer == configuration::keyer_t::external_separate_device_keyer)
+                       key_context_.reset(new key_video_context(config, print()));
+
                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("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
                graph_->set_color("flushed-frame", diagnostics::color(0.4f, 0.3f, 0.8f));
                graph_->set_color("buffered-audio", diagnostics::color(0.9f, 0.9f, 0.5f));
                graph_->set_color("buffered-video", diagnostics::color(0.2f, 0.9f, 0.9f));
+
+               if (key_context_)
+               {
+                       graph_->set_color("key-offset", diagnostics::color(1.0f, 0.0f, 0.0f));
+               }
+
                graph_->set_text(print());
                diagnostics::register_graph(graph_);
                
@@ -238,9 +381,9 @@ public:
                if(config.embedded_audio)
                        enable_audio();
                
-               set_latency(config.latency);                            
-               set_keyer(config.keyer);
-                               
+               set_latency(configuration_, config.latency, print());                           
+               set_keyer(attributes_, keyer_, config.keyer, print());
+
                if(config.embedded_audio)               
                        output_->BeginAudioPreroll();           
                
@@ -265,48 +408,6 @@ public:
                        output_->DisableVideoOutput();
                }
        }
-                       
-       void set_latency(configuration::latency_t latency)
-       {               
-               if(latency == configuration::latency_t::low_latency)
-               {
-                       configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true);
-                       CASPAR_LOG(info) << print() << L" Enabled low-latency mode.";
-               }
-               else if(latency == configuration::latency_t::normal_latency)
-               {                       
-                       configuration_->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, false);
-                       CASPAR_LOG(info) << print() << L" Disabled low-latency mode.";
-               }
-       }
-
-       void set_keyer(configuration::keyer_t keyer)
-       {
-               if(keyer == configuration::keyer_t::internal_keyer) 
-               {
-                       BOOL value = true;
-                       if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsInternalKeying, &value)) && !value)
-                               CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";   
-                       else if(FAILED(keyer_->Enable(FALSE)))                  
-                               CASPAR_LOG(error) << print() << L" Failed to enable internal keyer.";                   
-                       else if(FAILED(keyer_->SetLevel(255)))                  
-                               CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
-                       else
-                               CASPAR_LOG(info) << print() << L" Enabled internal keyer.";             
-               }
-               else if(keyer == configuration::keyer_t::external_keyer)
-               {
-                       BOOL value = true;
-                       if(SUCCEEDED(attributes_->GetFlag(BMDDeckLinkSupportsExternalKeying, &value)) && !value)
-                               CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   
-                       else if(FAILED(keyer_->Enable(TRUE)))                   
-                               CASPAR_LOG(error) << print() << L" Failed to enable external keyer.";   
-                       else if(FAILED(keyer_->SetLevel(255)))                  
-                               CASPAR_LOG(error) << print() << L" Failed to set key-level to max.";
-                       else
-                               CASPAR_LOG(info) << print() << L" Enabled external keyer.";                     
-               }
-       }
        
        void enable_audio()
        {
@@ -322,18 +423,24 @@ 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 video output."));
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " Could not enable fill video output."));
                
                if(FAILED(output_->SetScheduledFrameCompletionCallback(this)))
                        CASPAR_THROW_EXCEPTION(caspar_exception() 
-                                                                       << msg_info(u8(print()) + " Failed to set playback completion callback.")
+                                                                       << msg_info(u8(print()) + " Failed to set fill playback completion callback.")
                                                                        << boost::errinfo_api_function("SetScheduledFrameCompletionCallback"));
+
+               if (key_context_)
+                       key_context_->enable_video(display_mode, [this]() { return print(); });
        }
 
        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 playback."));
+                       CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " 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."));
        }
        
        virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*)       {return E_NOINTERFACE;}
@@ -356,6 +463,16 @@ public:
                {
                        auto dframe = reinterpret_cast<decklink_frame*>(completed_frame);
                        current_presentation_delay_ = dframe->get_age_millis();
+                       ++scheduled_frames_completed_;
+
+                       if (key_context_)
+                               graph_->set_value(
+                                               "key-offset",
+                                               static_cast<double>(
+                                                               scheduled_frames_completed_
+                                                               - key_context_->scheduled_frames_completed_)
+                                               * 0.1 + 0.5);
+
                        if(result == bmdOutputFrameDisplayedLate)
                        {
                                graph_->set_tag("late-frame");
@@ -450,9 +567,16 @@ public:
                        
        void schedule_next_video(core::const_frame frame)
        {
-               auto frame2 = wrap_raw<com_ptr, IDeckLinkVideoFrame>(new decklink_frame(frame, format_desc_, config_.key_only));
-               if(FAILED(output_->ScheduleVideoFrame(get_raw(frame2), video_scheduled_, format_desc_.duration, format_desc_.time_scale)))
-                       CASPAR_LOG(error) << print() << L" Failed to schedule video.";
+               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)))
+                               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));
+               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.";
 
                video_scheduled_ += format_desc_.duration;
 
@@ -500,8 +624,16 @@ public:
        
        std::wstring print() const
        {
-               return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_) + L"-" +
-                       boost::lexical_cast<std::wstring>(config_.device_index) + L"|" +  format_desc_.name + L"]";
+               if (config_.keyer == configuration::keyer_t::external_separate_device_keyer)
+                       return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_)+L"-" +
+                               boost::lexical_cast<std::wstring>(config_.device_index) +
+                               L"&&" +
+                               boost::lexical_cast<std::wstring>(config_.key_device_index()) +
+                               L"|" +
+                               format_desc_.name + L"]";
+               else
+                       return model_name_ + L" [" + boost::lexical_cast<std::wstring>(channel_index_)+L"-" +
+                               boost::lexical_cast<std::wstring>(config_.device_index) + L"|" + format_desc_.name + L"]";
        }
 };
 
@@ -568,6 +700,12 @@ public:
                info.add(L"type", L"decklink");
                info.add(L"key-only", config_.key_only);
                info.add(L"device", config_.device_index);
+
+               if (config_.keyer == configuration::keyer_t::external_separate_device_keyer)
+               {
+                       info.add(L"key-device", config_.key_device_index());
+               }
+
                info.add(L"low-latency", config_.latency == configuration::latency_t::low_latency);
                info.add(L"embedded-audio", config_.embedded_audio);
                info.add(L"presentation-frame-age", presentation_frame_age_millis());
@@ -601,14 +739,16 @@ void describe_consumer(core::help_sink& sink, const core::help_repository& repo)
        sink.short_description(L"Sends video on an SDI output using Blackmagic Decklink video cards.");
        sink.syntax(L"DECKLINK "
                                L"{[device_index:int]|1} "
-                               L"{[keyer:INTERNAL_KEY,EXTERNAL_KEY]} "
+                               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]}");
        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.")
-               ->item(L"keyer", L"If given tries to enable either internal or external keying. Not all Blackmagic cards supports this.")
+               ->item(L"keyer",
+                               L"If given tries to enable either internal or external keying. Not all Blackmagic cards supports this. "
+                               L"There is also a third experimental option (EXTERNAL_SEPARATE_DEVICE_KEY) which allocates device_index + 1 for synhronized key output.")
                ->item(L"low_latency", L"Tries to enable low latency if given.")
                ->item(L"embedded_audio", L"Embeds the audio into the SDI signal if given.")
                ->item(L"key_only",
@@ -620,8 +760,11 @@ void describe_consumer(core::help_sink& sink, const core::help_repository& repo)
        sink.example(L">> ADD 1 DECKLINK 2", L"uses device_index 2.");
        sink.example(L">> ADD 1 DECKLINK 1 EXTERNAL_KEY EMBEDDED_AUDIO");
        sink.example(
-               L">> ADD 1 DECKLINK 1 EMBEDDED_AUDIO\n"
-               L">> ADD 1 DECKLINK 2 KEY_ONLY", L"uses device with index 1 as fill output with audio and device with index 2 as key output.");
+                       L">> ADD 1 DECKLINK 1 EMBEDDED_AUDIO\n"
+                       L">> ADD 1 DECKLINK 2 KEY_ONLY", L"uses device with index 1 as fill output with audio and device with index 2 as key output.");
+       sink.example(
+                       L">> ADD 1 DECKLINK 1 EXTERNAL_SEPARATE_DEVICE_KEY EMBEDDED_AUDIO",
+                       L"Uses device 2 for key output. May give better sync between key and fill than the previous method.");
 }
 
 spl::shared_ptr<core::frame_consumer> create_consumer(
@@ -639,6 +782,8 @@ spl::shared_ptr<core::frame_consumer> create_consumer(
                config.keyer = configuration::keyer_t::internal_keyer;
        else if (contains_param(L"EXTERNAL_KEY", params))
                config.keyer = configuration::keyer_t::external_keyer;
+       else if (contains_param(L"EXTERNAL_SEPARATE_DEVICE_KEY", params))
+               config.keyer = configuration::keyer_t::external_separate_device_keyer;
        else
                config.keyer = configuration::keyer_t::default_keyer;
 
@@ -661,6 +806,8 @@ spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(
                config.keyer = configuration::keyer_t::external_keyer;
        else if(keyer == L"internal")
                config.keyer = configuration::keyer_t::internal_keyer;
+       else if (keyer == L"external_separate_device")
+               config.keyer = configuration::keyer_t::external_separate_device_keyer;
 
        auto latency = ptree.get(L"latency", L"normal");
        if(latency == L"low")
@@ -670,6 +817,7 @@ spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(
 
        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);
        config.embedded_audio           = ptree.get(L"embedded-audio",  config.embedded_audio);
        config.base_buffer_depth        = ptree.get(L"buffer-depth",    config.base_buffer_depth);
 
index 79689da7ce22842e959edb913573616627c0db69..ffceab1df2b0687ba083057a8ec0b97c083bde6d 100644 (file)
         <consumers>\r
             <decklink>\r
                 <device>[1..]</device>\r
+                <key-device>device + 1 [1..]</key-device>\r
                 <embedded-audio>false [true|false]</embedded-audio>\r
                 <latency>normal [normal|low|default]</latency>\r
-                <keyer>external [external|internal|default]</keyer>\r
+                <keyer>external [external|external_separate_device|internal|default]</keyer>\r
                 <key-only>false [true|false]</key-only>\r
                 <buffer-depth>3 [1..]</buffer-depth>\r
             </decklink>\r