From ae7ef096bceecea3462cbb73f0d0f350d15e6822 Mon Sep 17 00:00:00 2001 From: ianshade Date: Tue, 30 Oct 2018 21:48:32 +0100 Subject: [PATCH 01/13] NDI consumer and producer (currently only BGRA/BGRX support) --- src/modules/newtek/CMakeLists.txt | 30 ++ .../newtek/consumer/newtek_ndi_consumer.cpp | 157 ++++++++++ .../newtek/consumer/newtek_ndi_consumer.h | 41 +++ .../interop/Processing.NDI.DynamicLoad.h | 130 +++++++++ .../newtek/interop/Processing.NDI.Find.h | 76 +++++ .../newtek/interop/Processing.NDI.FrameSync.h | 123 ++++++++ .../interop/Processing.NDI.Lib.cplusplus.h | 95 +++++++ .../newtek/interop/Processing.NDI.Lib.h | 139 +++++++++ .../newtek/interop/Processing.NDI.Recv.ex.h | 171 +++++++++++ .../newtek/interop/Processing.NDI.Recv.h | 222 +++++++++++++++ .../newtek/interop/Processing.NDI.Routing.h | 59 ++++ .../newtek/interop/Processing.NDI.Send.h | 127 +++++++++ .../newtek/interop/Processing.NDI.compat.h | 39 +++ .../interop/Processing.NDI.deprecated.h | 216 ++++++++++++++ .../newtek/interop/Processing.NDI.structs.h | 258 +++++++++++++++++ .../newtek/interop/Processing.NDI.utilities.h | 108 +++++++ src/modules/newtek/newtek.cpp | 14 +- .../newtek/producer/newtek_ndi_producer.cpp | 269 ++++++++++++++++++ .../newtek/producer/newtek_ndi_producer.h | 38 +++ src/modules/newtek/util/ndi.cpp | 109 +++++++ src/modules/newtek/util/ndi.h | 48 ++++ 21 files changed, 2468 insertions(+), 1 deletion(-) create mode 100644 src/modules/newtek/consumer/newtek_ndi_consumer.cpp create mode 100644 src/modules/newtek/consumer/newtek_ndi_consumer.h create mode 100644 src/modules/newtek/interop/Processing.NDI.DynamicLoad.h create mode 100644 src/modules/newtek/interop/Processing.NDI.Find.h create mode 100644 src/modules/newtek/interop/Processing.NDI.FrameSync.h create mode 100644 src/modules/newtek/interop/Processing.NDI.Lib.cplusplus.h create mode 100644 src/modules/newtek/interop/Processing.NDI.Lib.h create mode 100644 src/modules/newtek/interop/Processing.NDI.Recv.ex.h create mode 100644 src/modules/newtek/interop/Processing.NDI.Recv.h create mode 100644 src/modules/newtek/interop/Processing.NDI.Routing.h create mode 100644 src/modules/newtek/interop/Processing.NDI.Send.h create mode 100644 src/modules/newtek/interop/Processing.NDI.compat.h create mode 100644 src/modules/newtek/interop/Processing.NDI.deprecated.h create mode 100644 src/modules/newtek/interop/Processing.NDI.structs.h create mode 100644 src/modules/newtek/interop/Processing.NDI.utilities.h create mode 100644 src/modules/newtek/producer/newtek_ndi_producer.cpp create mode 100644 src/modules/newtek/producer/newtek_ndi_producer.h create mode 100644 src/modules/newtek/util/ndi.cpp create mode 100644 src/modules/newtek/util/ndi.h diff --git a/src/modules/newtek/CMakeLists.txt b/src/modules/newtek/CMakeLists.txt index a1678ad50b..2797628f51 100644 --- a/src/modules/newtek/CMakeLists.txt +++ b/src/modules/newtek/CMakeLists.txt @@ -4,8 +4,14 @@ project (newtek) set(SOURCES consumer/newtek_ivga_consumer.cpp + consumer/newtek_ndi_consumer.cpp + + producer/newtek_ndi_producer.cpp + util/air_send.cpp + util/ndi.cpp + newtek.cpp StdAfx.cpp @@ -13,10 +19,30 @@ set(SOURCES set(HEADERS consumer/newtek_ivga_consumer.h + consumer/newtek_ndi_consumer.h + + producer/newtek_ndi_producer.h + util/air_send.h + util/ndi.h + newtek.h + interop/Processing.NDI.compat.h + interop/Processing.NDI.deprecated.h + interop/Processing.NDI.DynamicLoad.h + interop/Processing.NDI.Find.h + interop/Processing.NDI.FrameSync.h + interop/Processing.NDI.Lib.cplusplus.h + interop/Processing.NDI.Lib.h + interop/Processing.NDI.Recv.ex.h + interop/Processing.NDI.Recv.h + interop/Processing.NDI.Routing.h + interop/Processing.NDI.Send.h + interop/Processing.NDI.structs.h + interop/Processing.NDI.utilities.h + StdAfx.h ) @@ -28,15 +54,19 @@ include_directories(..) include_directories(../..) include_directories(${BOOST_INCLUDE_PATH}) include_directories(${TBB_INCLUDE_PATH}) +include_directories(${FFMPEG_INCLUDE_PATH}) set_target_properties(newtek PROPERTIES FOLDER modules) source_group(sources\\consumer consumer/*) +source_group(sources\\producer producer/*) +source_group(sources\\interop interop/*) source_group(sources\\util util/*) source_group(sources ./*) target_link_libraries(newtek common core + ffmpeg ) casparcg_add_include_statement("modules/newtek/newtek.h") diff --git a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp new file mode 100644 index 0000000000..13f1862293 --- /dev/null +++ b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 2018 + * + * 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: Krzysztof Zegzula, zegzulakrzysztof@gmail.com + * based on work of Robert Nagy, ronag89@gmail.com + */ + +#include "../StdAfx.h" + +#include "newtek_ndi_consumer.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "../util/ndi.h" + +namespace caspar { namespace newtek { + +struct newtek_ndi_consumer : public core::frame_consumer +{ + core::video_format_desc format_desc_; + NDIlib_v3* ndi_lib_; + NDIlib_send_instance_t ndi_send_instance_; + NDIlib_video_frame_v2_t ndi_video_frame_; + NDIlib_audio_frame_interleaved_16s_t ndi_audio_frame_; + std::atomic connected_ = false; + spl::shared_ptr graph_; + caspar::timer tick_timer_; + caspar::timer frame_timer_; + std::wstring name_; + + public: + newtek_ndi_consumer(std::wstring name) + : name_(name) + { + if (!ndi::load_library()) { + CASPAR_THROW_EXCEPTION(not_supported() << msg_info( + ndi::dll_name() + L" not available. Install NDI Redist version 3.7 or higher.")); + } + + graph_->set_text(print()); + graph_->set_color("frame-time", diagnostics::color(0.5f, 1.0f, 0.2f)); + graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f)); + graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f)); + diagnostics::register_graph(graph_); + } + + ~newtek_ndi_consumer() {} + + // frame_consumer + + void initialize(const core::video_format_desc& format_desc, int channel_index) override + { + format_desc_ = format_desc; + + ndi_lib_ = ndi::load_library(); + NDIlib_send_create_t NDI_send_create_desc; + auto tmp_name = u8(name_); + NDI_send_create_desc.p_ndi_name = tmp_name.c_str(); + NDI_send_create_desc.clock_video = false; // TODO: maybe send on separate thread and keep clocking + NDI_send_create_desc.clock_audio = false; + ndi_send_instance_ = ndi_lib_->NDIlib_send_create(&NDI_send_create_desc); + + ndi_video_frame_.xres = format_desc.width; + ndi_video_frame_.yres = format_desc.height; + ndi_video_frame_.frame_rate_N = format_desc.framerate.numerator(); + ndi_video_frame_.frame_rate_D = format_desc.framerate.denominator(); + ndi_video_frame_.FourCC = NDIlib_FourCC_type_BGRA; + ndi_video_frame_.line_stride_in_bytes = format_desc.width * 4; + + CASPAR_VERIFY(ndi_send_instance_); + } + + std::future send(core::const_frame frame) override + { + CASPAR_VERIFY(format_desc_.height * format_desc_.width * 4 == frame.image_data(0).size()); + + graph_->set_value("tick-time", tick_timer_.elapsed() * format_desc_.fps * 0.5); + tick_timer_.restart(); + + frame_timer_.restart(); + auto audio_buffer = core::audio_32_to_16(frame.audio_data()); + ndi_audio_frame_.sample_rate = format_desc_.audio_sample_rate; + ndi_audio_frame_.no_channels = format_desc_.audio_channels; + ndi_audio_frame_.no_samples = static_cast(audio_buffer.size()) / format_desc_.audio_channels; + ndi_audio_frame_.timecode = NDIlib_send_timecode_synthesize; + ndi_audio_frame_.p_data = const_cast(audio_buffer.data()); + + ndi_lib_->NDIlib_util_send_send_audio_interleaved_16s(ndi_send_instance_, &ndi_audio_frame_); + + ndi_video_frame_.p_data = const_cast(frame.image_data(0).begin()); + ndi_lib_->NDIlib_send_send_video_v2(ndi_send_instance_, &ndi_video_frame_); + + graph_->set_value("frame-time", frame_timer_.elapsed() * format_desc_.fps * 0.5); + + return make_ready_future(true); + } + + std::wstring print() const override { return L"newtek-ndi[" + name_ + L"]"; } + + std::wstring name() const override { return L"newtek-ndi"; } + + int index() const override { return 900; } + + bool has_synchronization_clock() const override { return false; } +}; + +spl::shared_ptr create_ndi_consumer(const std::vector& params, + std::vector> channels) +{ + if (params.size() < 1 || !boost::iequals(params.at(0), L"NEWTEK_NDI")) + return core::frame_consumer::empty(); + std::wstring name = L"CasparCG"; + if (contains_param(L"NAME", params)) { + name = get_param(L"NAME", params); + } + return spl::make_shared(name); +} + +spl::shared_ptr +create_preconfigured_ndi_consumer(const boost::property_tree::wptree& ptree, + std::vector> channels) +{ + std::wstring name = L"CasparCG"; + name = ptree.get(L"name", name); + return spl::make_shared(name); +} + +}} // namespace caspar::newtek diff --git a/src/modules/newtek/consumer/newtek_ndi_consumer.h b/src/modules/newtek/consumer/newtek_ndi_consumer.h new file mode 100644 index 0000000000..46a6f684d9 --- /dev/null +++ b/src/modules/newtek/consumer/newtek_ndi_consumer.h @@ -0,0 +1,41 @@ +/* + * Copyright 2013 Sveriges Television AB http://casparcg.com/ + * + * 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: Krzysztof Zegzula, zegzulakrzysztof@gmail.com + * based on work of Robert Nagy, ronag89@gmail.com work + */ + +#pragma once + +#include + +#include + +#include + +#include + +namespace caspar { namespace newtek { + +spl::shared_ptr create_ndi_consumer(const std::vector& params, + std::vector> channels); +spl::shared_ptr +create_preconfigured_ndi_consumer(const boost::property_tree::wptree& ptree, + std::vector> channels); + +}} // namespace caspar::newtek diff --git a/src/modules/newtek/interop/Processing.NDI.DynamicLoad.h b/src/modules/newtek/interop/Processing.NDI.DynamicLoad.h new file mode 100644 index 0000000000..038beafa0d --- /dev/null +++ b/src/modules/newtek/interop/Processing.NDI.DynamicLoad.h @@ -0,0 +1,130 @@ +#pragma once + +// NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review the SDK documentation +// for the description of the full license terms, which are also provided in the file "NDI License Agreement.pdf" within the SDK or +// online at http://new.tk/ndisdk_license/. Your use of any part of this SDK is acknowledgment that you agree to the SDK license +// terms. The full NDI SDK may be downloaded at https://www.newtek.com/ndi/sdk/ +// +//*********************************************************************************************************************************************** +// +// Copyright(c) 2014-2018 NewTek, inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//*********************************************************************************************************************************************** + +//**************************************************************************************************************** +typedef struct NDIlib_v3 +{ // V1.5 + bool(*NDIlib_initialize)(void); + void(*NDIlib_destroy)(void); + const char* (*NDIlib_version)(void); + bool(*NDIlib_is_supported_CPU)(void); + PROCESSINGNDILIB_DEPRECATED NDIlib_find_instance_t(*NDIlib_find_create)(const NDIlib_find_create_t* p_create_settings); + NDIlib_find_instance_t(*NDIlib_find_create_v2)(const NDIlib_find_create_t* p_create_settings); + void(*NDIlib_find_destroy)(NDIlib_find_instance_t p_instance); + const NDIlib_source_t* (*NDIlib_find_get_sources)(NDIlib_find_instance_t p_instance, uint32_t* p_no_sources, uint32_t timeout_in_ms); + NDIlib_send_instance_t(*NDIlib_send_create)(const NDIlib_send_create_t* p_create_settings); + void(*NDIlib_send_destroy)(NDIlib_send_instance_t p_instance); + PROCESSINGNDILIB_DEPRECATED void(*NDIlib_send_send_video)(NDIlib_send_instance_t p_instance, const NDIlib_video_frame_t* p_video_data); + PROCESSINGNDILIB_DEPRECATED void(*NDIlib_send_send_video_async)(NDIlib_send_instance_t p_instance, const NDIlib_video_frame_t* p_video_data); + PROCESSINGNDILIB_DEPRECATED void(*NDIlib_send_send_audio)(NDIlib_send_instance_t p_instance, const NDIlib_audio_frame_t* p_audio_data); + void(*NDIlib_send_send_metadata)(NDIlib_send_instance_t p_instance, const NDIlib_metadata_frame_t* p_metadata); + NDIlib_frame_type_e(*NDIlib_send_capture)(NDIlib_send_instance_t p_instance, NDIlib_metadata_frame_t* p_metadata, uint32_t timeout_in_ms); + void(*NDIlib_send_free_metadata)(NDIlib_send_instance_t p_instance, const NDIlib_metadata_frame_t* p_metadata); + bool(*NDIlib_send_get_tally)(NDIlib_send_instance_t p_instance, NDIlib_tally_t* p_tally, uint32_t timeout_in_ms); + int(*NDIlib_send_get_no_connections)(NDIlib_send_instance_t p_instance, uint32_t timeout_in_ms); + void(*NDIlib_send_clear_connection_metadata)(NDIlib_send_instance_t p_instance); + void(*NDIlib_send_add_connection_metadata)(NDIlib_send_instance_t p_instance, const NDIlib_metadata_frame_t* p_metadata); + void(*NDIlib_send_set_failover)(NDIlib_send_instance_t p_instance, const NDIlib_source_t* p_failover_source); + PROCESSINGNDILIB_DEPRECATED NDIlib_recv_instance_t(*NDIlib_recv_create_v2)(const NDIlib_recv_create_t* p_create_settings); + PROCESSINGNDILIB_DEPRECATED NDIlib_recv_instance_t(*NDIlib_recv_create)(const NDIlib_recv_create_t* p_create_settings); + void(*NDIlib_recv_destroy)(NDIlib_recv_instance_t p_instance); + PROCESSINGNDILIB_DEPRECATED NDIlib_frame_type_e(*NDIlib_recv_capture)(NDIlib_recv_instance_t p_instance, NDIlib_video_frame_t* p_video_data, NDIlib_audio_frame_t* p_audio_data, NDIlib_metadata_frame_t* p_metadata, uint32_t timeout_in_ms); + PROCESSINGNDILIB_DEPRECATED void(*NDIlib_recv_free_video)(NDIlib_recv_instance_t p_instance, const NDIlib_video_frame_t* p_video_data); + PROCESSINGNDILIB_DEPRECATED void(*NDIlib_recv_free_audio)(NDIlib_recv_instance_t p_instance, const NDIlib_audio_frame_t* p_audio_data); + void(*NDIlib_recv_free_metadata)(NDIlib_recv_instance_t p_instance, const NDIlib_metadata_frame_t* p_metadata); + bool(*NDIlib_recv_send_metadata)(NDIlib_recv_instance_t p_instance, const NDIlib_metadata_frame_t* p_metadata); + bool(*NDIlib_recv_set_tally)(NDIlib_recv_instance_t p_instance, const NDIlib_tally_t* p_tally); + void(*NDIlib_recv_get_performance)(NDIlib_recv_instance_t p_instance, NDIlib_recv_performance_t* p_total, NDIlib_recv_performance_t* p_dropped); + void(*NDIlib_recv_get_queue)(NDIlib_recv_instance_t p_instance, NDIlib_recv_queue_t* p_total); + void(*NDIlib_recv_clear_connection_metadata)(NDIlib_recv_instance_t p_instance); + void(*NDIlib_recv_add_connection_metadata)(NDIlib_recv_instance_t p_instance, const NDIlib_metadata_frame_t* p_metadata); + int(*NDIlib_recv_get_no_connections)(NDIlib_recv_instance_t p_instance); + NDIlib_routing_instance_t(*NDIlib_routing_create)(const NDIlib_routing_create_t* p_create_settings); + void(*NDIlib_routing_destroy)(NDIlib_routing_instance_t p_instance); + bool(*NDIlib_routing_change)(NDIlib_routing_instance_t p_instance, const NDIlib_source_t* p_source); + bool(*NDIlib_routing_clear)(NDIlib_routing_instance_t p_instance); + void(*NDIlib_util_send_send_audio_interleaved_16s)(NDIlib_send_instance_t p_instance, const NDIlib_audio_frame_interleaved_16s_t* p_audio_data); + PROCESSINGNDILIB_DEPRECATED void(*NDIlib_util_audio_to_interleaved_16s)(const NDIlib_audio_frame_t* p_src, NDIlib_audio_frame_interleaved_16s_t* p_dst); + PROCESSINGNDILIB_DEPRECATED void(*NDIlib_util_audio_from_interleaved_16s)(const NDIlib_audio_frame_interleaved_16s_t* p_src, NDIlib_audio_frame_t* p_dst); + // V2 + bool(*NDIlib_find_wait_for_sources)(NDIlib_find_instance_t p_instance, uint32_t timeout_in_ms); + const NDIlib_source_t* (*NDIlib_find_get_current_sources)(NDIlib_find_instance_t p_instance, uint32_t* p_no_sources); + PROCESSINGNDILIB_DEPRECATED void(*NDIlib_util_audio_to_interleaved_32f)(const NDIlib_audio_frame_t* p_src, NDIlib_audio_frame_interleaved_32f_t* p_dst); + PROCESSINGNDILIB_DEPRECATED void(*NDIlib_util_audio_from_interleaved_32f)(const NDIlib_audio_frame_interleaved_32f_t* p_src, NDIlib_audio_frame_t* p_dst); + void(*NDIlib_util_send_send_audio_interleaved_32f)(NDIlib_send_instance_t p_instance, const NDIlib_audio_frame_interleaved_32f_t* p_audio_data); + // V3 + void(*NDIlib_recv_free_video_v2)(NDIlib_recv_instance_t p_instance, const NDIlib_video_frame_v2_t* p_video_data); + void(*NDIlib_recv_free_audio_v2)(NDIlib_recv_instance_t p_instance, const NDIlib_audio_frame_v2_t* p_audio_data); + NDIlib_frame_type_e(*NDIlib_recv_capture_v2)(NDIlib_recv_instance_t p_instance, NDIlib_video_frame_v2_t* p_video_data, NDIlib_audio_frame_v2_t* p_audio_data, NDIlib_metadata_frame_t* p_metadata, uint32_t timeout_in_ms); // The amount of time in milliseconds to wait for data. + void(*NDIlib_send_send_video_v2)(NDIlib_send_instance_t p_instance, const NDIlib_video_frame_v2_t* p_video_data); + void(*NDIlib_send_send_video_async_v2)(NDIlib_send_instance_t p_instance, const NDIlib_video_frame_v2_t* p_video_data); + void(*NDIlib_send_send_audio_v2)(NDIlib_send_instance_t p_instance, const NDIlib_audio_frame_v2_t* p_audio_data); + void(*NDIlib_util_audio_to_interleaved_16s_v2)(const NDIlib_audio_frame_v2_t* p_src, NDIlib_audio_frame_interleaved_16s_t* p_dst); + void(*NDIlib_util_audio_from_interleaved_16s_v2)(const NDIlib_audio_frame_interleaved_16s_t* p_src, NDIlib_audio_frame_v2_t* p_dst); + void(*NDIlib_util_audio_to_interleaved_32f_v2)(const NDIlib_audio_frame_v2_t* p_src, NDIlib_audio_frame_interleaved_32f_t* p_dst); + void(*NDIlib_util_audio_from_interleaved_32f_v2)(const NDIlib_audio_frame_interleaved_32f_t* p_src, NDIlib_audio_frame_v2_t* p_dst); + // V3.01 + void(*NDIlib_recv_free_string)(NDIlib_recv_instance_t p_instance, const char* p_string); + bool(*NDIlib_recv_ptz_is_supported)(NDIlib_recv_instance_t p_instance); + bool(*NDIlib_recv_recording_is_supported)(NDIlib_recv_instance_t p_instance); + const char*(*NDIlib_recv_get_web_control)(NDIlib_recv_instance_t p_instance); + bool(*NDIlib_recv_ptz_zoom)(NDIlib_recv_instance_t p_instance, const float zoom_value); + bool(*NDIlib_recv_ptz_zoom_speed)(NDIlib_recv_instance_t p_instance, const float zoom_speed); + bool(*NDIlib_recv_ptz_pan_tilt)(NDIlib_recv_instance_t p_instance, const float pan_value, const float tilt_value); + bool(*NDIlib_recv_ptz_pan_tilt_speed)(NDIlib_recv_instance_t p_instance, const float pan_speed, const float tilt_speed); + bool(*NDIlib_recv_ptz_store_preset)(NDIlib_recv_instance_t p_instance, const int preset_no); + bool(*NDIlib_recv_ptz_recall_preset)(NDIlib_recv_instance_t p_instance, const int preset_no, const float speed); + bool(*NDIlib_recv_ptz_auto_focus)(NDIlib_recv_instance_t p_instance); + bool(*NDIlib_recv_ptz_focus)(NDIlib_recv_instance_t p_instance, const float focus_value); + bool(*NDIlib_recv_ptz_focus_speed)(NDIlib_recv_instance_t p_instance, const float focus_speed); + bool(*NDIlib_recv_ptz_white_balance_auto)(NDIlib_recv_instance_t p_instance); + bool(*NDIlib_recv_ptz_white_balance_indoor)(NDIlib_recv_instance_t p_instance); + bool(*NDIlib_recv_ptz_white_balance_outdoor)(NDIlib_recv_instance_t p_instance); + bool(*NDIlib_recv_ptz_white_balance_oneshot)(NDIlib_recv_instance_t p_instance); + bool(*NDIlib_recv_ptz_white_balance_manual)(NDIlib_recv_instance_t p_instance, const float red, const float blue); + bool(*NDIlib_recv_ptz_exposure_auto)(NDIlib_recv_instance_t p_instance); + bool(*NDIlib_recv_ptz_exposure_manual)(NDIlib_recv_instance_t p_instance, const float exposure_level); + bool(*NDIlib_recv_recording_start)(NDIlib_recv_instance_t p_instance, const char* p_filename_hint); + bool(*NDIlib_recv_recording_stop)(NDIlib_recv_instance_t p_instance); + bool(*NDIlib_recv_recording_set_audio_level)(NDIlib_recv_instance_t p_instance, const float level_dB); + bool(*NDIlib_recv_recording_is_recording)(NDIlib_recv_instance_t p_instance); + const char*(*NDIlib_recv_recording_get_filename)(NDIlib_recv_instance_t p_instance); + const char*(*NDIlib_recv_recording_get_error)(NDIlib_recv_instance_t p_instance); + bool(*NDIlib_recv_recording_get_times)(NDIlib_recv_instance_t p_instance, NDIlib_recv_recording_time_t* p_times); + // V3.10 + NDIlib_recv_instance_t(*NDIlib_recv_create_v3)(const NDIlib_recv_create_v3_t* p_create_settings); + // V3.5 + void(*NDIlib_recv_connect)(NDIlib_recv_instance_t p_instance, const NDIlib_source_t* p_src); + +} NDIlib_v3; + +typedef struct NDIlib_v3 NDIlib_v2; + +// Load the library +PROCESSINGNDILIB_API +const NDIlib_v3* NDIlib_v3_load(void); + +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +const NDIlib_v2* NDIlib_v2_load(void); diff --git a/src/modules/newtek/interop/Processing.NDI.Find.h b/src/modules/newtek/interop/Processing.NDI.Find.h new file mode 100644 index 0000000000..8b7d9bf0e2 --- /dev/null +++ b/src/modules/newtek/interop/Processing.NDI.Find.h @@ -0,0 +1,76 @@ +#pragma once + +// NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review the SDK documentation +// for the description of the full license terms, which are also provided in the file "NDI License Agreement.pdf" within the SDK or +// online at http://new.tk/ndisdk_license/. Your use of any part of this SDK is acknowledgment that you agree to the SDK license +// terms. The full NDI SDK may be downloaded at https://www.newtek.com/ndi/sdk/ +// +//*********************************************************************************************************************************************** +// +// Copyright(c) 2014-2018 NewTek, inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//*********************************************************************************************************************************************** + +//************************************************************************************************************************** +// Structures and type definitions required by NDI finding +// The reference to an instance of the finder +typedef void* NDIlib_find_instance_t; + +// The creation structure that is used when you are creating a finder +typedef struct NDIlib_find_create_t +{ // Do we want to incluide the list of NDI sources that are running + // on the local machine ? + // If TRUE then local sources will be visible, if FALSE then they + // will not. + bool show_local_sources; + + // Which groups do you want to search in for sources + const char* p_groups; + + // The list of additional IP addresses that exist that we should query for + // sources on. For instance, if you want to find the sources on a remote machine + // that is not on your local sub-net then you can put a comma seperated list of + // those IP addresses here and those sources will be available locally even though + // they are not mDNS discoverable. An example might be "12.0.0.8,13.0.12.8". + // When none is specified the registry is used. + // Default = nullptr; + const char* p_extra_ips; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_find_create_t(bool show_local_sources_ = true, const char* p_groups_ = nullptr, const char* p_extra_ips_ = nullptr); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_find_create_t; + +//************************************************************************************************************************** +// Create a new finder instance. This will return nullptr if it fails. +// This function is deprecated, please use NDIlib_find_create_v2 if you can. This function +// ignores the p_extra_ips member and sets it to the default. +PROCESSINGNDILIB_API +NDIlib_find_instance_t NDIlib_find_create_v2(const NDIlib_find_create_t* p_create_settings NDILIB_CPP_DEFAULT_VALUE(nullptr)); + +// This will destroy an existing finder instance. +PROCESSINGNDILIB_API +void NDIlib_find_destroy(NDIlib_find_instance_t p_instance); + +// This function will recover the current set of sources (i.e. the ones that exist right this second). +// The char* memory buffers returned in NDIlib_source_t are valid until the next call to NDIlib_find_get_current_sources or a call to NDIlib_find_destroy. +// For a given NDIlib_find_instance_t, do not call NDIlib_find_get_current_sources asynchronously. +PROCESSINGNDILIB_API +const NDIlib_source_t* NDIlib_find_get_current_sources(NDIlib_find_instance_t p_instance, uint32_t* p_no_sources); + +// This will allow you to wait until the number of online sources have changed. +PROCESSINGNDILIB_API +bool NDIlib_find_wait_for_sources(NDIlib_find_instance_t p_instance, uint32_t timeout_in_ms); diff --git a/src/modules/newtek/interop/Processing.NDI.FrameSync.h b/src/modules/newtek/interop/Processing.NDI.FrameSync.h new file mode 100644 index 0000000000..cf6e4c615a --- /dev/null +++ b/src/modules/newtek/interop/Processing.NDI.FrameSync.h @@ -0,0 +1,123 @@ +#pragma once + +//*********************************************************************************************************************************************** +// +// Copyright(c) 2014-2018 NewTek, inc +// +//*********************************************************************************************************************************************** + +// It is important when using video to realize that often you are using differenc clocks +// for different parts of the signal chain. Within NDI, the sender can send at the clock rate +// that it wants and the receiver will receive it at that rate. The receiver however is very +// unlikely to share the exact same clock rate in many cases. For instance, bear in mind that +// computer clocks rely on crystals which while all rated for the same frequency are still not +// exact. If you sending computer has an audio clock that it "thinks" is 48000Hz, to the receiver +// computer that has a different audio clock this might be 48001Hz or 47998Hz. While these +// differences might appear small they accumulate over time and can cause audio to either +// slightly drift out of sync (it is receiving more audio sample sthan it needs to play back) +// or might cause audio glitches because it is not receiving enough audio samples. While we +// have described the situation for audio, the same exact problem occurs for video sources; +// it is commonly thought that this can be solved by simply having a "frame buffer" and that +// displaying the "most recently received video frame" will solve these timing discrepencies. +// Unfortunately this is not the case and when it is done because of the variance in clock +// timings, it is veyr common the the video will appear the "jitter" when the sending and +// receiving closks are almost in alignment. The solution to these problems is to implement +// a "time base corrector" for the video clock which is a device that uses hysterisis to know +// when the best time is to either drop or insert a video frame such that the video is most +// likely to play back smoothly, and audio should be dynamically audio sampled (with a high +// order resampling filter) to adaptively track any clocking differences. Implementing these +// components is very difficult to get entirely correct under all scenarios and this +// implementation is provided to facilitate this and help people who are building real time +// video applications to receive audio and video without needing to undertake the full +// complexity of implementing such clock devices. +// +// Another way to look at what this class does is that it transforms "push" sources (i.e. +// NDI sources in which the data is pushed from the sender to the receiver) into "pull" +// sources in which a host application is pulling the data down-stream. The frame-sync +// automatically tracks all clocks to acheive the best video performance doing this +// operation. +// +// In addition to time-base correction operations, these implementations also will automatically +// detect and correct timing jitter that might occur. This will internally correct for timing +// anomolies that might be caused by network, sender or receiver side timing errors caused +// by CPU limitatoins, network bandwidth fluctuations, etc... +// +// A very common use of a frame-synchronizer might be if you are displaying video on screen +// timed to the GPU v-sync, you should use sych a device to convert from the incoming time-base +// into the time-base of the GPU. +// +// The following are common times that you want to use a frame-synchronizer +// Video playback on screen : Yes, you want the clock to be synced with vertical refresh. +// Audio playback through sound card : Yes you want the clock to be synced with your sound card clock. +// Video mixing : Yes you want the input video clocks to all be synced to your output video clock. +// Audio mixing : Yes, you want all input audio clocks to be brough into sync with your output audio clock. +// Recording a single channel : No, you want to record the signal in it's raw form without any reclocking. +// Recording multiple channels : Maybe. If you want to sync some input channels to match a master clock so that +// they can be ISO edited, then you might want a frame-sync. + +// The type instance for a frame-synchronizer +typedef void* NDIlib_framesync_instance_t; + +// Create a frame synchronizer instance that can be used to get frames +// from a receiver. Once this receiver has been bound to a frame-sync +// then you should use it in order to receover video frames. You can +// continue to use the underlying receiver for other operations (tally, +// PTZ, etc...). Note that it remains your responsability to destroy the +// receiver even when a frame-sync is using it. You should always destroy +// the receiver after the framesync has been destroyed. +PROCESSINGNDILIB_API +NDIlib_framesync_instance_t NDIlib_framesync_create(NDIlib_recv_instance_t p_receiver); + +// Destroy a frame-sync implemenration +PROCESSINGNDILIB_API +void NDIlib_framesync_destroy(NDIlib_framesync_instance_t p_instance); + +// This function will pull audio samples from the frame-sync queue. This function +// will always return data immediately, inserting silence if no current audio +// data is present. You should call this at the rate that you want audio and it +// will automatically adapt the incoming audio signal to match the rate at which +// you are calling by using dynamic audio sampling. Note that you have no obligation +// that your requested sample rate, no channels and no samples match the incoming signal +// and all combinations of conversions are supported. If you specify a sample-rate=0 +// or no_channels=0 then it will use the original sample rate of the buffer. +PROCESSINGNDILIB_API +void NDIlib_framesync_capture_audio(// The frame sync instance data + NDIlib_framesync_instance_t p_instance, + // The destination audio buffer that you wish to have filled in. + NDIlib_audio_frame_v2_t* p_audio_data, + // Your desired sample rate, number of channels and the number of desired samples. + int sample_rate, int no_channels, int no_samples); + +// Free audio returned by NDIlib_framesync_capture_audio +PROCESSINGNDILIB_API +void NDIlib_framesync_free_audio(// The frame sync instance data + NDIlib_framesync_instance_t p_instance, + // The destination audio buffer that you wish to have filled in. + NDIlib_audio_frame_v2_t* p_audio_data); + +// This function will pull video samples from the frame-sync queue. This function +// will always immediately return a video sample by using time-base correction. You can +// specify the desired field type which is then used to return the best possible frame. +// Note that field based frame-synronization means that the frame-synchronizer attempts +// to match the fielded input phase with the frame requests so that you have the most +// correct possible field ordering on output. Note that the same frame can be returned +// multiple times. +// +// If no video frame has evern been received, this will return NDIlib_video_frame_v2_t as +// an empty (all zero) structure. The reason for this is that it allows you to determine +// that there has not yet been any video and act accordingly. For instamce you might want +// to display a constant frame output at a particular video format, or black. +PROCESSINGNDILIB_API +void NDIlib_framesync_capture_video(// The frame sync instance data + NDIlib_framesync_instance_t p_instance, + // The destination audio buffer that you wish to have filled in. + NDIlib_video_frame_v2_t* p_video_data, + // The frame type that you would prefer, all effort is made to match these. + NDIlib_frame_format_type_e field_type NDILIB_CPP_DEFAULT_VALUE(NDIlib_frame_format_type_progressive)); + +// Free audio returned by NDIlib_framesync_capture_video +PROCESSINGNDILIB_API +void NDIlib_framesync_free_video(// The frame sync instance data + NDIlib_framesync_instance_t p_instance, + // The destination audio buffer that you wish to have filled in. + NDIlib_video_frame_v2_t* p_video_data); diff --git a/src/modules/newtek/interop/Processing.NDI.Lib.cplusplus.h b/src/modules/newtek/interop/Processing.NDI.Lib.cplusplus.h new file mode 100644 index 0000000000..dddad74797 --- /dev/null +++ b/src/modules/newtek/interop/Processing.NDI.Lib.cplusplus.h @@ -0,0 +1,95 @@ +#pragma once + +// NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review the SDK documentation +// for the description of the full license terms, which are also provided in the file "NDI License Agreement.pdf" within the SDK or +// online at http://new.tk/ndisdk_license/. Your use of any part of this SDK is acknowledgment that you agree to the SDK license +// terms. The full NDI SDK may be downloaded at https://www.newtek.com/ndi/sdk/ +// +//*********************************************************************************************************************************************** +// +// Copyright(c) 2014-2018 NewTek, inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//*********************************************************************************************************************************************** + + +// C++ implementations of default constructors are here to avoid them needing to be inline with +// all of the rest of the code. + +// All the structs used and reasonable defaults are here +inline NDIlib_source_t::NDIlib_source_t(const char* p_ndi_name_, const char* p_url_address_) + : p_ndi_name(p_ndi_name_), p_url_address(p_url_address_) {} + +inline NDIlib_video_frame_v2_t::NDIlib_video_frame_v2_t(int xres_, int yres_, NDIlib_FourCC_type_e FourCC_, int frame_rate_N_, int frame_rate_D_, + float picture_aspect_ratio_, NDIlib_frame_format_type_e frame_format_type_, + int64_t timecode_, uint8_t* p_data_, int line_stride_in_bytes_, const char* p_metadata_, int64_t timestamp_) + : xres(xres_), yres(yres_), FourCC(FourCC_), frame_rate_N(frame_rate_N_), frame_rate_D(frame_rate_D_), + picture_aspect_ratio(picture_aspect_ratio_), frame_format_type(frame_format_type_), + timecode(timecode_), p_data(p_data_), line_stride_in_bytes(line_stride_in_bytes_), p_metadata(p_metadata_), timestamp(timestamp_) {} + +inline NDIlib_audio_frame_v2_t::NDIlib_audio_frame_v2_t(int sample_rate_, int no_channels_, int no_samples_, int64_t timecode_, float* p_data_, + int channel_stride_in_bytes_, const char* p_metadata_, int64_t timestamp_) + : sample_rate(sample_rate_), no_channels(no_channels_), no_samples(no_samples_), timecode(timecode_), + p_data(p_data_), channel_stride_in_bytes(channel_stride_in_bytes_), p_metadata(p_metadata_), timestamp(timestamp_) {} + +inline NDIlib_video_frame_t::NDIlib_video_frame_t(int xres_, int yres_, NDIlib_FourCC_type_e FourCC_, int frame_rate_N_, int frame_rate_D_, + float picture_aspect_ratio_, NDIlib_frame_format_type_e frame_format_type_, + int64_t timecode_, uint8_t* p_data_, int line_stride_in_bytes_) + : xres(xres_), yres(yres_), FourCC(FourCC_), frame_rate_N(frame_rate_N_), frame_rate_D(frame_rate_D_), + picture_aspect_ratio(picture_aspect_ratio_), frame_format_type(frame_format_type_), + timecode(timecode_), p_data(p_data_), line_stride_in_bytes(line_stride_in_bytes_) {} + +inline NDIlib_audio_frame_t::NDIlib_audio_frame_t(int sample_rate_, int no_channels_, int no_samples_, int64_t timecode_, float* p_data_, + int channel_stride_in_bytes_) + : sample_rate(sample_rate_), no_channels(no_channels_), no_samples(no_samples_), timecode(timecode_), + p_data(p_data_), channel_stride_in_bytes(channel_stride_in_bytes_) {} + +inline NDIlib_metadata_frame_t::NDIlib_metadata_frame_t(int length_, int64_t timecode_, char* p_data_) + : length(length_), timecode(timecode_), p_data(p_data_) {} + +inline NDIlib_tally_t::NDIlib_tally_t(bool on_program_, bool on_preview_) + : on_program(on_program_), on_preview(on_preview_) {} + +inline NDIlib_routing_create_t::NDIlib_routing_create_t(const char* p_ndi_name_, const char* p_groups_) + : p_ndi_name(p_ndi_name_), p_groups(p_groups_) {} + +inline NDIlib_recv_create_v3_t::NDIlib_recv_create_v3_t(const NDIlib_source_t source_to_connect_to_, NDIlib_recv_color_format_e color_format_, + NDIlib_recv_bandwidth_e bandwidth_, bool allow_video_fields_, const char* p_ndi_name_) + : source_to_connect_to(source_to_connect_to_), color_format(color_format_), bandwidth(bandwidth_), allow_video_fields(allow_video_fields_), p_ndi_recv_name(p_ndi_name_) {} + +inline NDIlib_recv_create_t::NDIlib_recv_create_t(const NDIlib_source_t source_to_connect_to_, NDIlib_recv_color_format_e color_format_, + NDIlib_recv_bandwidth_e bandwidth_, bool allow_video_fields_) + : source_to_connect_to(source_to_connect_to_), color_format(color_format_), bandwidth(bandwidth_), allow_video_fields(allow_video_fields_) {} + +inline NDIlib_recv_performance_t::NDIlib_recv_performance_t(void) + : video_frames(0), audio_frames(0), metadata_frames(0) {} + +inline NDIlib_recv_queue_t::NDIlib_recv_queue_t(void) + : video_frames(0), audio_frames(0), metadata_frames(0) {} + +inline NDIlib_recv_recording_time_t::NDIlib_recv_recording_time_t(void) + : no_frames(0), start_time(0), last_time(0) {} + +inline NDIlib_send_create_t::NDIlib_send_create_t(const char* p_ndi_name_, const char* p_groups_, bool clock_video_, bool clock_audio_) + : p_ndi_name(p_ndi_name_), p_groups(p_groups_), clock_video(clock_video_), clock_audio(clock_audio_) {} + +inline NDIlib_find_create_t::NDIlib_find_create_t(bool show_local_sources_, const char* p_groups_, const char* p_extra_ips_) + : show_local_sources(show_local_sources_), p_groups(p_groups_), p_extra_ips(p_extra_ips_) {} + +inline NDIlib_audio_frame_interleaved_16s_t::NDIlib_audio_frame_interleaved_16s_t(int sample_rate_, int no_channels_, int no_samples_, int64_t timecode_, int reference_level_, short* p_data_) + : sample_rate(sample_rate_), no_channels(no_channels_), no_samples(no_samples_), timecode(timecode_), + reference_level(reference_level_), p_data(p_data_) {} + +inline NDIlib_audio_frame_interleaved_32f_t::NDIlib_audio_frame_interleaved_32f_t(int sample_rate_, int no_channels_, int no_samples_, int64_t timecode_, float* p_data_) + : sample_rate(sample_rate_), no_channels(no_channels_), no_samples(no_samples_), timecode(timecode_), p_data(p_data_) {} diff --git a/src/modules/newtek/interop/Processing.NDI.Lib.h b/src/modules/newtek/interop/Processing.NDI.Lib.h new file mode 100644 index 0000000000..54c152b8db --- /dev/null +++ b/src/modules/newtek/interop/Processing.NDI.Lib.h @@ -0,0 +1,139 @@ +#pragma once + +// NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review the SDK documentation +// for the description of the full license terms, which are also provided in the file "NDI License Agreement.pdf" within the SDK or +// online at http://new.tk/ndisdk_license/. Your use of any part of this SDK is acknowledgment that you agree to the SDK license +// terms. The full NDI SDK may be downloaded at https://www.newtek.com/ndi/sdk/ +// +//*********************************************************************************************************************************************** +// +// Copyright(c) 2014-2018 NewTek, inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//*********************************************************************************************************************************************** + +// Is this library being compiled, or imported by another application. +#ifdef _WIN32 +#define PROCESSINGNDILIB_DEPRECATED __declspec(deprecated) +#ifdef PROCESSINGNDILIB_EXPORTS +#ifdef __cplusplus +#define PROCESSINGNDILIB_API extern "C" __declspec(dllexport) +#else // __cplusplus +#define PROCESSINGNDILIB_API __declspec(dllexport) +#endif // __cplusplus +#else // PROCESSINGNDILIB_EXPORTS +#ifdef __cplusplus +#define PROCESSINGNDILIB_API extern "C" __declspec(dllimport) +#else // __cplusplus +#define PROCESSINGNDILIB_API __declspec(dllimport) +#endif // __cplusplus +#ifdef _WIN64 +#define NDILIB_LIBRARY_NAME "Processing.NDI.Lib.x64.dll" +#define NDILIB_REDIST_FOLDER "NDI_RUNTIME_DIR_V3" +#define NDILIB_REDIST_URL "http://new.tk/NDIRedistV3" +#else // _WIN64 +#define NDILIB_LIBRARY_NAME "Processing.NDI.Lib.x86.dll" +#define NDILIB_REDIST_FOLDER "NDI_RUNTIME_DIR_V3" +#define NDILIB_REDIST_URL "http://new.tk/NDIRedistV3" +#endif // _WIN64 +#endif // PROCESSINGNDILIB_EXPORTS +#else // _WIN32 +#ifdef __APPLE__ +#define NDILIB_LIBRARY_NAME "libndi.3.dylib" +#define NDILIB_REDIST_FOLDER "NDI_RUNTIME_DIR_V3" +#define NDILIB_REDIST_URL "http://new.tk/NDIRedistV3Apple" +#else // __APPLE__ +#define NDILIB_LIBRARY_NAME "libndi.so.3" +#define NDILIB_REDIST_FOLDER "NDI_RUNTIME_DIR_V3" +#define NDILIB_REDIST_URL "" +#endif // __APPLE__ +#define PROCESSINGNDILIB_DEPRECATED +#ifdef __cplusplus +#define PROCESSINGNDILIB_API extern "C" __attribute((visibility("default"))) +#else // __cplusplus +#define PROCESSINGNDILIB_API __attribute((visibility("default"))) +#endif // __cplusplus +#endif // _WIN32 + +#ifndef NDILIB_CPP_DEFAULT_CONSTRUCTORS +#ifdef __cplusplus +#define NDILIB_CPP_DEFAULT_CONSTRUCTORS 1 +#else // __cplusplus +#define NDILIB_CPP_DEFAULT_CONSTRUCTORS 0 +#endif // __cplusplus +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +#ifndef NDILIB_CPP_DEFAULT_VALUE +#ifdef __cplusplus +#define NDILIB_CPP_DEFAULT_VALUE(a) =(a) +#else // __cplusplus +#define NDILIB_CPP_DEFAULT_VALUE(a) +#endif // __cplusplus +#endif // NDILIB_CPP_DEFAULT_VALUE + +// Data structures shared by multiple SDKs +#include "Processing.NDI.compat.h" +#include "Processing.NDI.structs.h" + +// This is not actually required, but will start and end the libraries which might get +// you slightly better performance in some cases. In general it is more "correct" to +// call these although it is not required. There is no way to call these that would have +// an adverse impact on anything (even calling destroy before you've deleted all your +// objects). This will return false if the CPU is not sufficiently capable to run NDILib +// currently NDILib requires SSE4.2 instructions (see documentation). You can verify +// a specific CPU against the library with a call to NDIlib_is_supported_CPU() +PROCESSINGNDILIB_API +bool NDIlib_initialize(void); + +PROCESSINGNDILIB_API +void NDIlib_destroy(void); + +PROCESSINGNDILIB_API +const char* NDIlib_version(void); + +// Recover whether the current CPU in the system is capable of running NDILib. +PROCESSINGNDILIB_API +bool NDIlib_is_supported_CPU(void); + +// The finding (discovery API) +#include "Processing.NDI.Find.h" + +// The receiving video and audio API +#include "Processing.NDI.Recv.h" + +// Extensions to support PTZ control, etc... +#include "Processing.NDI.Recv.ex.h" + +// The sending video API +#include "Processing.NDI.Send.h" + +// The routing of inputs API +#include "Processing.NDI.Routing.h" + +// Utility functions +#include "Processing.NDI.utilities.h" + +// Deprecated structures and functions +#include "Processing.NDI.deprecated.h" + +// Dynamic loading used for OSS libraries +#include "Processing.NDI.DynamicLoad.h" + +// The frame synchronizer +#include "Processing.NDI.FrameSync.h" + +// The C++ implementations +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS +#include "Processing.NDI.Lib.cplusplus.h" +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS \ No newline at end of file diff --git a/src/modules/newtek/interop/Processing.NDI.Recv.ex.h b/src/modules/newtek/interop/Processing.NDI.Recv.ex.h new file mode 100644 index 0000000000..f92b12055e --- /dev/null +++ b/src/modules/newtek/interop/Processing.NDI.Recv.ex.h @@ -0,0 +1,171 @@ +#pragma once + +// NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review the SDK documentation +// for the description of the full license terms, which are also provided in the file "NDI License Agreement.pdf" within the SDK or +// online at http://new.tk/ndisdk_license/. Your use of any part of this SDK is acknowledgment that you agree to the SDK license +// terms. The full NDI SDK may be downloaded at https://www.newtek.com/ndi/sdk/ +// +//*********************************************************************************************************************************************** +// +// Copyright(c) 2014-2018 NewTek, inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//*********************************************************************************************************************************************** + +//************************************************************************************************************************************************************** +// Has this receiver got PTZ control. Note that it might take a second or two after the connection for this value to be set. +// To avoid the need to poll this function, you can know when the value of this function might have changed when the +// NDILib_recv_capture* call would return NDIlib_frame_type_status_change +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_is_supported(NDIlib_recv_instance_t p_instance); + +// Has this receiver got recording control. Note that it might take a second or two after the connection for this value to be set. +// To avoid the need to poll this function, you can know when the value of this function might have changed when the +// NDILib_recv_capture* call would return NDIlib_frame_type_status_change +PROCESSINGNDILIB_API +bool NDIlib_recv_recording_is_supported(NDIlib_recv_instance_t p_instance); + +//************************************************************************************************************************************************************** +// PTZ Controls +// Zoom to an absolute value. +// zoom_value = 0.0 (zoomed in) ... 1.0 (zoomed out) +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_zoom(NDIlib_recv_instance_t p_instance, const float zoom_value); + +// Zoom at a particular speed +// zoom_speed = -1.0 (zoom outwards) ... +1.0 (zoom inwards) +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_zoom_speed(NDIlib_recv_instance_t p_instance, const float zoom_speed); + +// Set the pan and tilt to an absolute value +// pan_value = -1.0 (left) ... 0.0 (centred) ... +1.0 (right) +// tilt_value = -1.0 (bottom) ... 0.0 (centred) ... +1.0 (top) +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_pan_tilt(NDIlib_recv_instance_t p_instance, const float pan_value, const float tilt_value); + +// Set the pan and tilt direction and speed +// pan_speed = -1.0 (moving right) ... 0.0 (stopped) ... +1.0 (moving left) +// tilt_speed = -1.0 (down) ... 0.0 (stopped) ... +1.0 (moving up) +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_pan_tilt_speed(NDIlib_recv_instance_t p_instance, const float pan_speed, const float tilt_speed); + +// Store the current position, focus, etc... as a preset. +// preset_no = 0 ... 99 +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_store_preset(NDIlib_recv_instance_t p_instance, const int preset_no); + +// Recall a preset, including position, focus, etc... +// preset_no = 0 ... 99 +// speed = 0.0(as slow as possible) ... 1.0(as fast as possible) The speed at which to move to the new preset +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_recall_preset(NDIlib_recv_instance_t p_instance, const int preset_no, const float speed); + +// Put the camera in auto-focus +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_auto_focus(NDIlib_recv_instance_t p_instance); + +// Focus to an absolute value. +// focus_value = 0.0 (focussed to infinity) ... 1.0 (focussed as close as possible) +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_focus(NDIlib_recv_instance_t p_instance, const float focus_value); + +// Focus at a particular speed +// focus_speed = -1.0 (focus outwards) ... +1.0 (focus inwards) +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_focus_speed(NDIlib_recv_instance_t p_instance, const float focus_speed); + +// Put the camera in auto white balance moce +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_white_balance_auto(NDIlib_recv_instance_t p_instance); + +// Put the camera in indoor white balance +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_white_balance_indoor(NDIlib_recv_instance_t p_instance); + +// Put the camera in indoor white balance +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_white_balance_outdoor(NDIlib_recv_instance_t p_instance); + +// Use the current brightness to automatically set the current white balance +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_white_balance_oneshot(NDIlib_recv_instance_t p_instance); + +// Set the manual camera white balance using the R, B values +// red = 0.0(not red) ... 1.0(very red) +// blue = 0.0(not blue) ... 1.0(very blue) +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_white_balance_manual(NDIlib_recv_instance_t p_instance, const float red, const float blue); + +// Put the camera in auto-exposure mode +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_exposure_auto(NDIlib_recv_instance_t p_instance); + +// Manually set the camera exposure +// exposure_level = 0.0(dark) ... 1.0(light) +PROCESSINGNDILIB_API +bool NDIlib_recv_ptz_exposure_manual(NDIlib_recv_instance_t p_instance, const float exposure_level); + +//************************************************************************************************************************************************************** +// Recording control +// This will start recording.If the recorder was already recording then the message is ignored.A filename is passed in as a "hint".Since the recorder might +// already be recording(or might not allow complete flexibility over its filename), the filename might or might not be used.If the filename is empty, or +// not present, a name will be chosen automatically. If you do not with to provide a filename hint you can simply pass nullptr. +PROCESSINGNDILIB_API +bool NDIlib_recv_recording_start(NDIlib_recv_instance_t p_instance, const char* p_filename_hint); + +// Stop recording. +PROCESSINGNDILIB_API +bool NDIlib_recv_recording_stop(NDIlib_recv_instance_t p_instance); + +// This will control the audio level for the recording.dB is specified in decibels relative to the reference level of the source. Not all recording sources support +// controlling audio levels.For instance, a digital audio device would not be able to avoid clipping on sources already at the wrong level, thus +// might not support this message. +PROCESSINGNDILIB_API +bool NDIlib_recv_recording_set_audio_level(NDIlib_recv_instance_t p_instance, const float level_dB); + +// This will determine if the source is currently recording. It will return true while recording is in progress and false when it is not. Because there is +// one recorded and multiple people might be connected to it, there is a chance that it is recording which was initiated by someone else. +PROCESSINGNDILIB_API +bool NDIlib_recv_recording_is_recording(NDIlib_recv_instance_t p_instance); + +// Get the current filename for recording. When this is set it will return a non-nullptr value which is owned by you and freed using NDIlib_recv_free_string. +// If a file was already being recorded by another client, the massage will contain the name of that file. The filename contains a UNC path (when one is available) +// to the recorded file, and can be used to access the file on your local machine for playback. If a UNC path is not available, then this will represent the local +// filename. This will remain valid even after the file has stopped being recorded until the next file is started. +PROCESSINGNDILIB_API +const char* NDIlib_recv_recording_get_filename(NDIlib_recv_instance_t p_instance); + +// This will tell you whether there was a recording error and what that string is. When this is set it will return a non-nullptr value which is owned by you and +// freed using NDIlib_recv_free_string. When there is no error it will return nullptr. +PROCESSINGNDILIB_API +const char* NDIlib_recv_recording_get_error(NDIlib_recv_instance_t p_instance); + +// In order to get the duration +typedef struct NDIlib_recv_recording_time_t +{ // The number of actual video frames recorded. + int64_t no_frames; + + // The starting time and current largest time of the record, in UTC time, at 100ns unit intervals. This allows you to know the record + // time irrespective of frame-rate. For instance, last_time - start_time woudl give you the recording length in 100ns intervals. + int64_t start_time, last_time; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_recv_recording_time_t(void); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_recv_recording_time_t; + +// Get the current recording times. These remain +PROCESSINGNDILIB_API +bool NDIlib_recv_recording_get_times(NDIlib_recv_instance_t p_instance, NDIlib_recv_recording_time_t* p_times); \ No newline at end of file diff --git a/src/modules/newtek/interop/Processing.NDI.Recv.h b/src/modules/newtek/interop/Processing.NDI.Recv.h new file mode 100644 index 0000000000..b7c8ac7f71 --- /dev/null +++ b/src/modules/newtek/interop/Processing.NDI.Recv.h @@ -0,0 +1,222 @@ +#pragma once + +// NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review the SDK documentation +// for the description of the full license terms, which are also provided in the file "NDI License Agreement.pdf" within the SDK or +// online at http://new.tk/ndisdk_license/. Your use of any part of this SDK is acknowledgment that you agree to the SDK license +// terms. The full NDI SDK may be downloaded at https://www.newtek.com/ndi/sdk/ +// +//*********************************************************************************************************************************************** +// +// Copyright(c) 2014-2018 NewTek, inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//*********************************************************************************************************************************************** + +//************************************************************************************************************************** +// Structures and type definitions required by NDI finding +// The reference to an instance of the receiver +typedef void* NDIlib_recv_instance_t; + +typedef enum NDIlib_recv_bandwidth_e +{ + NDIlib_recv_bandwidth_metadata_only = -10, // Receive metadata. + NDIlib_recv_bandwidth_audio_only = 10, // Receive metadata, audio. + NDIlib_recv_bandwidth_lowest = 0, // Receive metadata, audio, video at a lower bandwidth and resolution. + NDIlib_recv_bandwidth_highest = 100 // Receive metadata, audio, video at full resolution. + +} NDIlib_recv_bandwidth_e; + +typedef enum NDIlib_recv_color_format_e +{ + NDIlib_recv_color_format_BGRX_BGRA = 0, // No alpha channel: BGRX, Alpha channel: BGRA + NDIlib_recv_color_format_UYVY_BGRA = 1, // No alpha channel: UYVY, Alpha channel: BGRA + NDIlib_recv_color_format_RGBX_RGBA = 2, // No alpha channel: RGBX, Alpha channel: RGBA + NDIlib_recv_color_format_UYVY_RGBA = 3, // No alpha channel: UYVY, Alpha channel: RGBA + +#ifdef _WIN32 + // On Windows there are some APIs that require bottom to top images in RGBA format. Specifying + // this format will return images in this format. The image data pointer will still point to the + // "top" of the image, althought he stride will be negative. You can get the "bottom" line of the image + // using : video_data.p_data + (video_data.yres - 1)*video_data.line_stride_in_bytes + NDIlib_recv_color_format_BGRX_BGRA_flipped = 200, +#endif + + // Read the SDK documentation to understand the pros and cons of this format. + NDIlib_recv_color_format_fastest = 100, + + // Legacy definitions for backwards compatibility + NDIlib_recv_color_format_e_BGRX_BGRA = NDIlib_recv_color_format_BGRX_BGRA, + NDIlib_recv_color_format_e_UYVY_BGRA = NDIlib_recv_color_format_UYVY_BGRA, + NDIlib_recv_color_format_e_RGBX_RGBA = NDIlib_recv_color_format_RGBX_RGBA, + NDIlib_recv_color_format_e_UYVY_RGBA = NDIlib_recv_color_format_UYVY_RGBA + +} NDIlib_recv_color_format_e; + +// The creation structure that is used when you are creating a receiver +typedef struct NDIlib_recv_create_v3_t +{ // The source that you wish to connect to. + NDIlib_source_t source_to_connect_to; + + // Your preference of color space. See above. + NDIlib_recv_color_format_e color_format; + + // The bandwidth setting that you wish to use for this video source. Bandwidth + // controlled by changing both the compression level and the resolution of the source. + // A good use for low bandwidth is working on WIFI connections. + NDIlib_recv_bandwidth_e bandwidth; + + // When this flag is FALSE, all video that you receive will be progressive. For sources + // that provide fields, this is de-interlaced on the receiving side (because we cannot change + // what the up-stream source was actually rendering. This is provided as a convenience to + // down-stream sources that do not wish to understand fielded video. There is almost no + // performance impact of using this function. + bool allow_video_fields; + + // The name of the NDI receiver to create. This is a nullptr terminated UTF8 string and should be + // the name of receive channel that you have. This is in many ways symettric with the name of + // senders, so this might be "Channel 1" on your system. If this is nullptr then it will use the + // filename of your application indexed with the number of the instance number of this receiver. + // If your applicaiton is My + const char* p_ndi_recv_name; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_recv_create_v3_t(const NDIlib_source_t source_to_connect_to_ = NDIlib_source_t(), NDIlib_recv_color_format_e color_format_ = NDIlib_recv_color_format_UYVY_BGRA, + NDIlib_recv_bandwidth_e bandwidth_ = NDIlib_recv_bandwidth_highest, bool allow_video_fields_ = true, const char* p_ndi_name_ = nullptr); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_recv_create_v3_t; + + +// This allows you determine the current performance levels of the receiving to be able to detect whether frames have been dropped +typedef struct NDIlib_recv_performance_t +{ // The number of video frames + int64_t video_frames; + + // The number of audio frames + int64_t audio_frames; + + // The number of metadata frames + int64_t metadata_frames; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_recv_performance_t(void); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_recv_performance_t; + +// Get the current queue depths +typedef struct NDIlib_recv_queue_t +{ // The number of video frames + int video_frames; + + // The number of audio frames + int audio_frames; + + // The number of metadata frames + int metadata_frames; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_recv_queue_t(void); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_recv_queue_t; + +//************************************************************************************************************************** +// Create a new receiver instance. This will return nullptr if it fails. If you create this with the default settings (nullptr) +// then it will automatically determine a receiver name. +PROCESSINGNDILIB_API +NDIlib_recv_instance_t NDIlib_recv_create_v3(const NDIlib_recv_create_v3_t* p_create_settings NDILIB_CPP_DEFAULT_VALUE(nullptr)); + +// This will destroy an existing receiver instance. +PROCESSINGNDILIB_API +void NDIlib_recv_destroy(NDIlib_recv_instance_t p_instance); + +// This function allows you to change the connection to another video source, you can also disconnect it by specifying a nullptr here. +// This allows you to preserve a receiver without needing to +PROCESSINGNDILIB_API +void NDIlib_recv_connect(NDIlib_recv_instance_t p_instance, const NDIlib_source_t* p_src NDILIB_CPP_DEFAULT_VALUE(nullptr)); + +// This will allow you to receive video, audio and metadata frames. +// Any of the buffers can be nullptr, in which case data of that type +// will not be captured in this call. This call can be called simultaneously +// on separate threads, so it is entirely possible to receive audio, video, metadata +// all on separate threads. This function will return NDIlib_frame_type_none if no +// data is received within the specified timeout and NDIlib_frame_type_error if the connection is lost. +// Buffers captured with this must be freed with the appropriate free function below. +PROCESSINGNDILIB_API +NDIlib_frame_type_e NDIlib_recv_capture_v2( + NDIlib_recv_instance_t p_instance, // The library instance + NDIlib_video_frame_v2_t* p_video_data, // The video data received (can be nullptr) + NDIlib_audio_frame_v2_t* p_audio_data, // The audio data received (can be nullptr) + NDIlib_metadata_frame_t* p_metadata, // The metadata received (can be nullptr) + uint32_t timeout_in_ms); // The amount of time in milliseconds to wait for data. + +// Free the buffers returned by capture for video +PROCESSINGNDILIB_API +void NDIlib_recv_free_video_v2(NDIlib_recv_instance_t p_instance, const NDIlib_video_frame_v2_t* p_video_data); + +// Free the buffers returned by capture for audio +PROCESSINGNDILIB_API +void NDIlib_recv_free_audio_v2(NDIlib_recv_instance_t p_instance, const NDIlib_audio_frame_v2_t* p_audio_data); + +// Free the buffers returned by capture for metadata +PROCESSINGNDILIB_API +void NDIlib_recv_free_metadata(NDIlib_recv_instance_t p_instance, const NDIlib_metadata_frame_t* p_metadata); + +// This will free a string that was allocated and returned by NDIlib_recv (for instance the NDIlib_recv_get_web_control) function. +PROCESSINGNDILIB_API +void NDIlib_recv_free_string(NDIlib_recv_instance_t p_instance, const char* p_string); + +// This function will send a meta message to the source that we are connected too. This returns FALSE if we are +// not currently connected to anything. +PROCESSINGNDILIB_API +bool NDIlib_recv_send_metadata(NDIlib_recv_instance_t p_instance, const NDIlib_metadata_frame_t* p_metadata); + +// Set the up-stream tally notifications. This returns FALSE if we are not currently connected to anything. That +// said, the moment that we do connect to something it will automatically be sent the tally state. +PROCESSINGNDILIB_API +bool NDIlib_recv_set_tally(NDIlib_recv_instance_t p_instance, const NDIlib_tally_t* p_tally); + +// Get the current performance structures. This can be used to determine if you have been calling NDIlib_recv_capture fast +// enough, or if your processing of data is not keeping up with real-time. The total structure will give you the total frame +// counts received, the dropped structure will tell you how many frames have been dropped. Either of these could be nullptr. +PROCESSINGNDILIB_API +void NDIlib_recv_get_performance(NDIlib_recv_instance_t p_instance, NDIlib_recv_performance_t* p_total, NDIlib_recv_performance_t* p_dropped); + +// This will allow you to determine the current queue depth for all of the frame sources at any time. +PROCESSINGNDILIB_API +void NDIlib_recv_get_queue(NDIlib_recv_instance_t p_instance, NDIlib_recv_queue_t* p_total); + +// Connection based metadata is data that is sent automatically each time a new connection is received. You queue all of these +// up and they are sent on each connection. To reset them you need to clear them all and set them up again. +PROCESSINGNDILIB_API +void NDIlib_recv_clear_connection_metadata(NDIlib_recv_instance_t p_instance); + +// Add a connection metadata string to the list of what is sent on each new connection. If someone is already connected then +// this string will be sent to them immediately. +PROCESSINGNDILIB_API +void NDIlib_recv_add_connection_metadata(NDIlib_recv_instance_t p_instance, const NDIlib_metadata_frame_t* p_metadata); + +// Is this receiver currently connected to a source on the other end, or has the source not yet been found or is no longe ronline. +// This will normally return 0 or 1 +PROCESSINGNDILIB_API +int NDIlib_recv_get_no_connections(NDIlib_recv_instance_t p_instance); + +// Get the URL that might be used for configuration of this input. Note that it might take a second or two after the connection for +// this value to be set. This function will return nullptr if there is no web control user interface. You should call NDIlib_recv_free_string +// to free the string that is returned by this function. The returned value will be a fully formed URL, for instamce "http://10.28.1.192/configuration/" +// To avoid the need to poll this function, you can know when the value of this function might have changed when the +// NDILib_recv_capture* call would return NDIlib_frame_type_status_change +PROCESSINGNDILIB_API +const char* NDIlib_recv_get_web_control(NDIlib_recv_instance_t p_instance); diff --git a/src/modules/newtek/interop/Processing.NDI.Routing.h b/src/modules/newtek/interop/Processing.NDI.Routing.h new file mode 100644 index 0000000000..9e26c002f8 --- /dev/null +++ b/src/modules/newtek/interop/Processing.NDI.Routing.h @@ -0,0 +1,59 @@ +#pragma once + +// NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review the SDK documentation +// for the description of the full license terms, which are also provided in the file "NDI License Agreement.pdf" within the SDK or +// online at http://new.tk/ndisdk_license/. Your use of any part of this SDK is acknowledgment that you agree to the SDK license +// terms. The full NDI SDK may be downloaded at https://www.newtek.com/ndi/sdk/ +// +//*********************************************************************************************************************************************** +// +// Copyright(c) 2014-2018 NewTek, inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//*********************************************************************************************************************************************** + +//************************************************************************************************************************** +// Structures and type definitions required by NDI sending +// The reference to an instance of the sender +typedef void* NDIlib_routing_instance_t; + +// The creation structure that is used when you are creating a sender +typedef struct NDIlib_routing_create_t +{ // The name of the NDI source to create. This is a nullptr terminated UTF8 string. + const char* p_ndi_name; + + // What groups should this source be part of + const char* p_groups; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_routing_create_t(const char* p_ndi_name_ = nullptr, const char* p_groups_ = nullptr); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_routing_create_t; + +// Create an NDI routing source +PROCESSINGNDILIB_API +NDIlib_routing_instance_t NDIlib_routing_create(const NDIlib_routing_create_t* p_create_settings); + +// Destroy and NDI routing source +PROCESSINGNDILIB_API +void NDIlib_routing_destroy(NDIlib_routing_instance_t p_instance); + +// Change the routing of this source to another destination +PROCESSINGNDILIB_API +bool NDIlib_routing_change(NDIlib_routing_instance_t p_instance, const NDIlib_source_t* p_source); + +// Change the routing of this source to another destination +PROCESSINGNDILIB_API +bool NDIlib_routing_clear(NDIlib_routing_instance_t p_instance); \ No newline at end of file diff --git a/src/modules/newtek/interop/Processing.NDI.Send.h b/src/modules/newtek/interop/Processing.NDI.Send.h new file mode 100644 index 0000000000..ca2598ca7b --- /dev/null +++ b/src/modules/newtek/interop/Processing.NDI.Send.h @@ -0,0 +1,127 @@ +#pragma once + +// NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review the SDK documentation +// for the description of the full license terms, which are also provided in the file "NDI License Agreement.pdf" within the SDK or +// online at http://new.tk/ndisdk_license/. Your use of any part of this SDK is acknowledgment that you agree to the SDK license +// terms. The full NDI SDK may be downloaded at https://www.newtek.com/ndi/sdk/ +// +//*********************************************************************************************************************************************** +// +// Copyright(c) 2014-2018 NewTek, inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//*********************************************************************************************************************************************** + +//************************************************************************************************************************** +// Structures and type definitions required by NDI sending +// The reference to an instance of the sender +typedef void* NDIlib_send_instance_t; + +// The creation structure that is used when you are creating a sender +typedef struct NDIlib_send_create_t +{ // The name of the NDI source to create. This is a nullptr terminated UTF8 string. + const char* p_ndi_name; + + // What groups should this source be part of. nullptr means default. + const char* p_groups; + + // Do you want audio and video to "clock" themselves. When they are clocked then + // by adding video frames, they will be rate limited to match the current frame-rate + // that you are submitting at. The same is true for audio. In general if you are submitting + // video and audio off a single thread then you should only clock one of them (video is + // probably the better of the two to clock off). If you are submtiting audio and video + // of separate threads then having both clocked can be useful. + bool clock_video, clock_audio; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_send_create_t(const char* p_ndi_name_ = nullptr, const char* p_groups_ = nullptr, bool clock_video_ = true, bool clock_audio_ = true); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_send_create_t; + +// Create a new sender instance. This will return nullptr if it fails. If you specify leave p_create_settings null then +// the sender will be created with default settings. +PROCESSINGNDILIB_API +NDIlib_send_instance_t NDIlib_send_create(const NDIlib_send_create_t* p_create_settings NDILIB_CPP_DEFAULT_VALUE(nullptr) ); + +// This will destroy an existing finder instance. +PROCESSINGNDILIB_API +void NDIlib_send_destroy(NDIlib_send_instance_t p_instance); + +// This will add a video frame +PROCESSINGNDILIB_API +void NDIlib_send_send_video_v2(NDIlib_send_instance_t p_instance, const NDIlib_video_frame_v2_t* p_video_data); + +// This will add a video frame and will return immediately, having scheduled the frame to be displayed. +// All processing and sending of the video will occur asynchronously. The memory accessed by NDIlib_video_frame_t +// cannot be freed or re-used by the caller until a synchronizing event has occurred. In general the API is better +// able to take advantage of asynchronous processing than you might be able to by simple having a separate thread +// to submit frames. +// +// This call is particularly beneficial when processing BGRA video since it allows any color conversion, compression +// and network sending to all be done on separate threads from your main rendering thread. +// +// Synchronozing events are : +// - a call to NDIlib_send_send_video +// - a call to NDIlib_send_send_video_async with another frame to be sent +// - a call to NDIlib_send_send_video with p_video_data=nullptr +// - a call to NDIlib_send_destroy +PROCESSINGNDILIB_API +void NDIlib_send_send_video_async_v2(NDIlib_send_instance_t p_instance, const NDIlib_video_frame_v2_t* p_video_data); + +// This will add an audio frame +PROCESSINGNDILIB_API +void NDIlib_send_send_audio_v2(NDIlib_send_instance_t p_instance, const NDIlib_audio_frame_v2_t* p_audio_data); + +// This will add a metadata frame +PROCESSINGNDILIB_API +void NDIlib_send_send_metadata(NDIlib_send_instance_t p_instance, const NDIlib_metadata_frame_t* p_metadata); + +// This allows you to receive metadata from the other end of the connection +PROCESSINGNDILIB_API +NDIlib_frame_type_e NDIlib_send_capture( + NDIlib_send_instance_t p_instance, // The instance data + NDIlib_metadata_frame_t* p_metadata, // The metadata received (can be nullptr) + uint32_t timeout_in_ms); // The amount of time in milliseconds to wait for data. + +// Free the buffers returned by capture for metadata +PROCESSINGNDILIB_API +void NDIlib_send_free_metadata(NDIlib_send_instance_t p_instance, const NDIlib_metadata_frame_t* p_metadata); + +// Determine the current tally sate. If you specify a timeout then it will wait until it has changed, otherwise it will simply poll it +// and return the current tally immediately. The return value is whether anything has actually change (true) or whether it timed out (false) +PROCESSINGNDILIB_API +bool NDIlib_send_get_tally(NDIlib_send_instance_t p_instance, NDIlib_tally_t* p_tally, uint32_t timeout_in_ms); + +// Get the current number of receivers connected to this source. This can be used to avoid even rendering when nothing is connected to the video source. +// which can significantly improve the efficiency if you want to make a lot of sources available on the network. If you specify a timeout that is not +// 0 then it will wait until there are connections for this amount of time. +PROCESSINGNDILIB_API +int NDIlib_send_get_no_connections(NDIlib_send_instance_t p_instance, uint32_t timeout_in_ms); + +// Connection based metadata is data that is sent automatically each time a new connection is received. You queue all of these +// up and they are sent on each connection. To reset them you need to clear them all and set them up again. +PROCESSINGNDILIB_API +void NDIlib_send_clear_connection_metadata(NDIlib_send_instance_t p_instance); + +// Add a connection metadata string to the list of what is sent on each new connection. If someone is already connected then +// this string will be sent to them immediately. +PROCESSINGNDILIB_API +void NDIlib_send_add_connection_metadata(NDIlib_send_instance_t p_instance, const NDIlib_metadata_frame_t* p_metadata); + +// This will assign a new fail-over source for this video source. What this means is that if this video source was to fail +// any receivers would automatically switch over to use this source, unless this source then came back online. You can specify +// nullptr to clear the source. +PROCESSINGNDILIB_API +void NDIlib_send_set_failover(NDIlib_send_instance_t p_instance, const NDIlib_source_t* p_failover_source); diff --git a/src/modules/newtek/interop/Processing.NDI.compat.h b/src/modules/newtek/interop/Processing.NDI.compat.h new file mode 100644 index 0000000000..31d9963569 --- /dev/null +++ b/src/modules/newtek/interop/Processing.NDI.compat.h @@ -0,0 +1,39 @@ +#pragma once + +// NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review the SDK documentation +// for the description of the full license terms, which are also provided in the file "NDI License Agreement.pdf" within the SDK or +// online at http://new.tk/ndisdk_license/. Your use of any part of this SDK is acknowledgment that you agree to the SDK license +// terms. The full NDI SDK may be downloaded at https://www.newtek.com/ndi/sdk/ +// +//*********************************************************************************************************************************************** +// +// Copyright(c) 2014-2018 NewTek, inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//*********************************************************************************************************************************************** + +#ifdef __cplusplus + +#include + +#else + +#include +#include + +#endif + +#ifndef INFINITE +#define INFINITE 0xFFFFFFFF +#endif diff --git a/src/modules/newtek/interop/Processing.NDI.deprecated.h b/src/modules/newtek/interop/Processing.NDI.deprecated.h new file mode 100644 index 0000000000..bc5e62bfa4 --- /dev/null +++ b/src/modules/newtek/interop/Processing.NDI.deprecated.h @@ -0,0 +1,216 @@ +#pragma once + +// NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review the SDK documentation +// for the description of the full license terms, which are also provided in the file "NDI License Agreement.pdf" within the SDK or +// online at http://new.tk/ndisdk_license/. Your use of any part of this SDK is acknowledgment that you agree to the SDK license +// terms. The full NDI SDK may be downloaded at https://www.newtek.com/ndi/sdk/ +// +//*********************************************************************************************************************************************** +// +// Copyright(c) 2014-2018 NewTek, inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//*********************************************************************************************************************************************** + +// This describes a video frame +PROCESSINGNDILIB_DEPRECATED +typedef struct NDIlib_video_frame_t +{ // The resolution of this frame + int xres, yres; + + // What FourCC this is with. This can be two values + NDIlib_FourCC_type_e FourCC; + + // What is the frame-rate of this frame. + // For instance NTSC is 30000,1001 = 30000/1001 = 29.97fps + int frame_rate_N, frame_rate_D; + + // What is the picture aspect ratio of this frame. + // For instance 16.0/9.0 = 1.778 is 16:9 video. If this is zero, then square pixels are assumed (xres/yres) + float picture_aspect_ratio; + + // Is this a fielded frame, or is it progressive + NDIlib_frame_format_type_e frame_format_type; + + // The timecode of this frame in 100ns intervals + int64_t timecode; + + // The video data itself + uint8_t* p_data; + + // The inter line stride of the video data, in bytes. + int line_stride_in_bytes; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_video_frame_t(int xres_ = 0, int yres_ = 0, NDIlib_FourCC_type_e FourCC_ = NDIlib_FourCC_type_UYVY, int frame_rate_N_ = 30000, int frame_rate_D_ = 1001, + float picture_aspect_ratio_ = 0.0f, NDIlib_frame_format_type_e frame_format_type_ = NDIlib_frame_format_type_progressive, + int64_t timecode_ = NDIlib_send_timecode_synthesize, uint8_t* p_data_ = nullptr, int line_stride_in_bytes_ = 0); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_video_frame_t; + +// This describes an audio frame +PROCESSINGNDILIB_DEPRECATED +typedef struct NDIlib_audio_frame_t +{ // The sample-rate of this buffer + int sample_rate; + + // The number of audio channels + int no_channels; + + // The number of audio samples per channel + int no_samples; + + // The timecode of this frame in 100ns intervals + int64_t timecode; + + // The audio data + float* p_data; + + // The inter channel stride of the audio channels, in bytes + int channel_stride_in_bytes; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_audio_frame_t(int sample_rate_ = 48000, int no_channels_ = 2, int no_samples_ = 0, int64_t timecode_ = NDIlib_send_timecode_synthesize, + float* p_data_ = nullptr, int channel_stride_in_bytes_ = 0); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_audio_frame_t; + +// For legacy reasons I called this the wrong thing. For backwards compatibility. +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +NDIlib_find_instance_t NDIlib_find_create2(const NDIlib_find_create_t* p_create_settings NDILIB_CPP_DEFAULT_VALUE(nullptr)); + +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +NDIlib_find_instance_t NDIlib_find_create(const NDIlib_find_create_t* p_create_settings NDILIB_CPP_DEFAULT_VALUE(nullptr)); + +// DEPRECATED. This function is basically exactly the following and was confusing to use. +// if ((!timeout_in_ms) || (NDIlib_find_wait_for_sources(timeout_in_ms))) +// return NDIlib_find_get_current_sources(p_instance, p_no_sources); +// return nullptr; +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +const NDIlib_source_t* NDIlib_find_get_sources(NDIlib_find_instance_t p_instance, uint32_t* p_no_sources, uint32_t timeout_in_ms); + +// The creation structure that is used when you are creating a receiver +PROCESSINGNDILIB_DEPRECATED +typedef struct NDIlib_recv_create_t +{ // The source that you wish to connect to. + NDIlib_source_t source_to_connect_to; + + // Your preference of color space. See above. + NDIlib_recv_color_format_e color_format; + + // The bandwidth setting that you wish to use for this video source. Bandwidth + // controlled by changing both the compression level and the resolution of the source. + // A good use for low bandwidth is working on WIFI connections. + NDIlib_recv_bandwidth_e bandwidth; + + // When this flag is FALSE, all video that you receive will be progressive. For sources + // that provide fields, this is de-interlaced on the receiving side (because we cannot change + // what the up-stream source was actually rendering. This is provided as a convenience to + // down-stream sources that do not wish to understand fielded video. There is almost no + // performance impact of using this function. + bool allow_video_fields; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_recv_create_t(const NDIlib_source_t source_to_connect_to_ = NDIlib_source_t(), NDIlib_recv_color_format_e color_format_ = NDIlib_recv_color_format_UYVY_BGRA, + NDIlib_recv_bandwidth_e bandwidth_ = NDIlib_recv_bandwidth_highest, bool allow_video_fields_ = true); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_recv_create_t; + +// This function is deprecated, please use NDIlib_recv_create_v3 if you can. Using this function will continue to work, and be +// supported for backwards compatibility. If the input parameter is nullptr it will be created with default settings and an automatically +// determined receiver name, +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +NDIlib_recv_instance_t NDIlib_recv_create_v2(const NDIlib_recv_create_t* p_create_settings NDILIB_CPP_DEFAULT_VALUE(nullptr) ); + +// For legacy reasons I called this the wrong thing. For backwards compatibility. If the input parameter is nullptr it will be created with +// default settings and an automatically determined receiver name. +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +NDIlib_recv_instance_t NDIlib_recv_create2(const NDIlib_recv_create_t* p_create_settings NDILIB_CPP_DEFAULT_VALUE(nullptr)); + +// This function is deprecated, please use NDIlib_recv_create_v3 if you can. Using this function will continue to work, and be +// supported for backwards compatibility. This version sets bandwidth to highest and allow fields to true. If the input parameter is nullptr it +// will be created with default settings and an automatically determined receiver name. +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +NDIlib_recv_instance_t NDIlib_recv_create(const NDIlib_recv_create_t* p_create_settings); + +// This will allow you to receive video, audio and metadata frames. +// Any of the buffers can be nullptr, in which case data of that type +// will not be captured in this call. This call can be called simultaneously +// on separate threads, so it is entirely possible to receive audio, video, metadata +// all on separate threads. This function will return NDIlib_frame_type_none if no +// data is received within the specified timeout and NDIlib_frame_type_error if the connection is lost. +// Buffers captured with this must be freed with the appropriate free function below. +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +NDIlib_frame_type_e NDIlib_recv_capture( +NDIlib_recv_instance_t p_instance, // The library instance +NDIlib_video_frame_t* p_video_data, // The video data received (can be nullptr) +NDIlib_audio_frame_t* p_audio_data, // The audio data received (can be nullptr) +NDIlib_metadata_frame_t* p_metadata, // The metadata received (can be nullptr) +uint32_t timeout_in_ms); // The amount of time in milliseconds to wait for data. + +// Free the buffers returned by capture for video +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +void NDIlib_recv_free_video(NDIlib_recv_instance_t p_instance, const NDIlib_video_frame_t* p_video_data); + +// Free the buffers returned by capture for audio +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +void NDIlib_recv_free_audio(NDIlib_recv_instance_t p_instance, const NDIlib_audio_frame_t* p_audio_data); + +// This will add a video frame +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +void NDIlib_send_send_video(NDIlib_send_instance_t p_instance, const NDIlib_video_frame_t* p_video_data); + +// This will add a video frame and will return immediately, having scheduled the frame to be displayed. +// All processing and sending of the video will occur asynchronously. The memory accessed by NDIlib_video_frame_t +// cannot be freed or re-used by the caller until a synchronizing event has occurred. In general the API is better +// able to take advantage of asynchronous processing than you might be able to by simple having a separate thread +// to submit frames. +// +// This call is particularly beneficial when processing BGRA video since it allows any color conversion, compression +// and network sending to all be done on separate threads from your main rendering thread. +// +// Synchronizing events are : +// - a call to NDIlib_send_send_video +// - a call to NDIlib_send_send_video_async with another frame to be sent +// - a call to NDIlib_send_send_video with p_video_data=nullptr +// - a call to NDIlib_send_destroy +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +void NDIlib_send_send_video_async(NDIlib_send_instance_t p_instance, const NDIlib_video_frame_t* p_video_data); + +// This will add an audio frame +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +void NDIlib_send_send_audio(NDIlib_send_instance_t p_instance, const NDIlib_audio_frame_t* p_audio_data); + +// Convert an planar floating point audio buffer into a interleaved short audio buffer. +// IMPORTANT : You must allocate the space for the samples in the destination to allow for your own memory management. +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +void NDIlib_util_audio_to_interleaved_16s(const NDIlib_audio_frame_t* p_src, NDIlib_audio_frame_interleaved_16s_t* p_dst); + +// Convert an interleaved short audio buffer audio buffer into a planar floating point one. +// IMPORTANT : You must allocate the space for the samples in the destination to allow for your own memory management. +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +void NDIlib_util_audio_from_interleaved_16s(const NDIlib_audio_frame_interleaved_16s_t* p_src, NDIlib_audio_frame_t* p_dst); + +// Convert an planar floating point audio buffer into a interleaved floating point audio buffer. +// IMPORTANT : You must allocate the space for the samples in the destination to allow for your own memory management. +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +void NDIlib_util_audio_to_interleaved_32f(const NDIlib_audio_frame_t* p_src, NDIlib_audio_frame_interleaved_32f_t* p_dst); + +// Convert an interleaved floating point audio buffer into a planar floating point one. +// IMPORTANT : You must allocate the space for the samples in the destination to allow for your own memory management. +PROCESSINGNDILIB_API PROCESSINGNDILIB_DEPRECATED +void NDIlib_util_audio_from_interleaved_32f(const NDIlib_audio_frame_interleaved_32f_t* p_src, NDIlib_audio_frame_t* p_dst); diff --git a/src/modules/newtek/interop/Processing.NDI.structs.h b/src/modules/newtek/interop/Processing.NDI.structs.h new file mode 100644 index 0000000000..eb7598d4e7 --- /dev/null +++ b/src/modules/newtek/interop/Processing.NDI.structs.h @@ -0,0 +1,258 @@ +#pragma once + +// NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review the SDK documentation +// for the description of the full license terms, which are also provided in the file "NDI License Agreement.pdf" within the SDK or +// online at http://new.tk/ndisdk_license/. Your use of any part of this SDK is acknowledgment that you agree to the SDK license +// terms. The full NDI SDK may be downloaded at https://www.newtek.com/ndi/sdk/ +// +//*********************************************************************************************************************************************** +// +// Copyright(c) 2014-2018 NewTek, inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//*********************************************************************************************************************************************** + +#ifndef NDI_LIB_FOURCC +#define NDI_LIB_FOURCC(ch0, ch1, ch2, ch3) \ + ((uint32_t)(uint8_t)(ch0) | ((uint32_t)(uint8_t)(ch1) << 8) | ((uint32_t)(uint8_t)(ch2) << 16) | ((uint32_t)(uint8_t)(ch3) << 24)) +#endif + +// An enumeration to specify the type of a packet returned by the functions +typedef enum NDIlib_frame_type_e +{ // What frame type is this ? + NDIlib_frame_type_none = 0, + NDIlib_frame_type_video = 1, + NDIlib_frame_type_audio = 2, + NDIlib_frame_type_metadata = 3, + NDIlib_frame_type_error = 4, + + // This indicates that the settings on this input have changed. + // For instamce, this value will be returned from NDIlib_recv_capture_v2 and NDIlib_recv_capture + // when the device is known to have new settings, for instance the web-url has changed ot the device + // is now known to be a PTZ camera. + NDIlib_frame_type_status_change = 100 + +} NDIlib_frame_type_e; + +typedef enum NDIlib_FourCC_type_e +{ // YCbCr color space + NDIlib_FourCC_type_UYVY = NDI_LIB_FOURCC('U', 'Y', 'V', 'Y'), + + // 4:2:0 formats + NDIlib_FourCC_type_YV12 = NDI_LIB_FOURCC('Y', 'V', '1', '2'), + NDIlib_FourCC_type_NV12 = NDI_LIB_FOURCC('N', 'V', '1', '2'), + NDIlib_FourCC_type_I420 = NDI_LIB_FOURCC('I', '4', '2', '0'), + + // BGRA + NDIlib_FourCC_type_BGRA = NDI_LIB_FOURCC('B', 'G', 'R', 'A'), + NDIlib_FourCC_type_BGRX = NDI_LIB_FOURCC('B', 'G', 'R', 'X'), + + // RGBA + NDIlib_FourCC_type_RGBA = NDI_LIB_FOURCC('R', 'G', 'B', 'A'), + NDIlib_FourCC_type_RGBX = NDI_LIB_FOURCC('R', 'G', 'B', 'X'), + + // This is a UYVY buffer followed immediately by an alpha channel buffer. + // If the stride of the YCbCr component is "stride", then the alpha channel + // starts at image_ptr + yres*stride. The alpha channel stride is stride/2. + NDIlib_FourCC_type_UYVA = NDI_LIB_FOURCC('U', 'Y', 'V', 'A') + +} NDIlib_FourCC_type_e; + +typedef enum NDIlib_frame_format_type_e +{ // A progressive frame + NDIlib_frame_format_type_progressive = 1, + + // A fielded frame with the field 0 being on the even lines and field 1 being + // on the odd lines/ + NDIlib_frame_format_type_interleaved = 0, + + // Individual fields + NDIlib_frame_format_type_field_0 = 2, + NDIlib_frame_format_type_field_1 = 3 + +} NDIlib_frame_format_type_e; + +// When you specify this as a timecode, the timecode will be synthesized for you. This may +// be used when sending video, audio or metadata. If you never specify a timecode at all, +// asking for each to be synthesized, then this will use the current system time as the +// starting timecode and then generate synthetic ones, keeping your streams exactly in +// sync as long as the frames you are sending do not deviate from the system time in any +// meaningful way. In practice this means that if you never specify timecodes that they +// will always be generated for you correctly. Timecodes coming from different senders on +// the same machine will always be in sync with eachother when working in this way. If you +// have NTP installed on your local network, then streams can be synchronized between +// multiple machines with very high precision. +// +// If you specify a timecode at a particular frame (audio or video), then ask for all subsequent +// ones to be synthesized. The subsequent ones will be generated to continue this sequency +// maintining the correct relationship both the between streams and samples generated, avoiding +// them deviating in time from teh timecode that you specified in any meanginfful way. +// +// If you specify timecodes on one stream (e.g. video) and ask for the other stream (audio) to +// be sythesized, the correct timecodes will be generated for the other stream and will be synthesize +// exactly to match (they are not quantized inter-streams) the correct sample positions. +// +// When you send metadata messagesa and ask for the timecode to be synthesized, then it is chosen +// to match the closest audio or video frame timecode so that it looks close to something you might +// want ... unless there is no sample that looks close in which a timecode is synthesized from the +// last ones known and the time since it was sent. +// +static const int64_t NDIlib_send_timecode_synthesize = INT64_MAX; + +// If the time-stamp is not available (i.e. a version of a sender before v2.5) +static const int64_t NDIlib_recv_timestamp_undefined = INT64_MAX; + +// This is a descriptor of a NDI source available on the network. +typedef struct NDIlib_source_t +{ // A UTF8 string that provides a user readable name for this source. + // This can be used for serialization, etc... and comprises the machine + // name and the source name on that machine. In the form + // MACHINE_NAME (NDI_SOURCE_NAME) + // If you specify this parameter either as nullptr, or an EMPTY string then the + // specific ip addres adn port number from below is used. + const char* p_ndi_name; + + // A UTF8 string that provides the actual network address and any parameters. + // This is not meant to be application readable and might well change in teh future. + // This can be nullptr if you do not know it and the API internally will instantiate + // a finder that is used to discover it even if it is not yet available on the network. + union + { // The current way of addressing the value + const char* p_url_address; + + // We used to use an IP address before we used the more general URL notification + // this is now depreciated byt maintained for compatability. + PROCESSINGNDILIB_DEPRECATED const char* p_ip_address; + }; + + // Default constructor in C++ +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_source_t(const char* p_ndi_name_ = nullptr, const char* p_url_address_ = nullptr); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_source_t; + +// This describes a video frame +typedef struct NDIlib_video_frame_v2_t +{ // The resolution of this frame + int xres, yres; + + // What FourCC this is with. This can be two values + NDIlib_FourCC_type_e FourCC; + + // What is the frame-rate of this frame. + // For instance NTSC is 30000,1001 = 30000/1001 = 29.97fps + int frame_rate_N, frame_rate_D; + + // What is the picture aspect ratio of this frame. + // For instance 16.0/9.0 = 1.778 is 16:9 video + // 0 means square pixels + float picture_aspect_ratio; + + // Is this a fielded frame, or is it progressive + NDIlib_frame_format_type_e frame_format_type; + + // The timecode of this frame in 100ns intervals + int64_t timecode; + + // The video data itself + uint8_t* p_data; + + // The inter line stride of the video data, in bytes. If the stride is 0 then it is sizeof(one pixel)*xres + int line_stride_in_bytes; + + // Per frame metadata for this frame. This is a nullptr terminated UTF8 string that should be + // in XML format. If you do not want any metadata then you may specify nullptr here. + const char* p_metadata; // Present in >= v2.5 + + // This is only valid when receiving a frame and is specified as a 100ns time that was the exact + // moment that the frame was submitted by the sending side and is generated by the SDK. If this + // value is NDIlib_recv_timestamp_undefined then this value is not available and is NDIlib_recv_timestamp_undefined. + int64_t timestamp; // Present in >= v2.5 + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_video_frame_v2_t(int xres_ = 0, int yres_ = 0, NDIlib_FourCC_type_e FourCC_ = NDIlib_FourCC_type_UYVY, int frame_rate_N_ = 30000, int frame_rate_D_ = 1001, + float picture_aspect_ratio_ = 0.0f, NDIlib_frame_format_type_e frame_format_type_ = NDIlib_frame_format_type_progressive, + int64_t timecode_ = NDIlib_send_timecode_synthesize, uint8_t* p_data_ = nullptr, int line_stride_in_bytes_ = 0, const char* p_metadata_ = nullptr, int64_t timestamp_ = 0); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_video_frame_v2_t; + +// This describes an audio frame +typedef struct NDIlib_audio_frame_v2_t +{ // The sample-rate of this buffer + int sample_rate; + + // The number of audio channels + int no_channels; + + // The number of audio samples per channel + int no_samples; + + // The timecode of this frame in 100ns intervals + int64_t timecode; + + // The audio data + float* p_data; + + // The inter channel stride of the audio channels, in bytes + int channel_stride_in_bytes; + + // Per frame metadata for this frame. This is a nullptr terminated UTF8 string that should be + // in XML format. If you do not want any metadata then you may specify nullptr here. + const char* p_metadata; // Present in >= v2.5 + + // This is only valid when receiving a frame and is specified as a 100ns time that was the exact + // moment that the frame was submitted by the sending side and is generated by the SDK. If this + // value is NDIlib_recv_timestamp_undefined then this value is not available and is NDIlib_recv_timestamp_undefined. + int64_t timestamp; // Present in >= v2.5 + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_audio_frame_v2_t(int sample_rate_ = 48000, int no_channels_ = 2, int no_samples_ = 0, int64_t timecode_ = NDIlib_send_timecode_synthesize, + float* p_data_ = nullptr, int channel_stride_in_bytes_ = 0, const char* p_metadata_ = nullptr, int64_t timestamp_ = 0); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_audio_frame_v2_t; + +// The data description for metadata +typedef struct NDIlib_metadata_frame_t +{ // The length of the string in UTF8 characters. This includes the nullptr terminating character. + // If this is 0, then the length is assume to be the length of a nullptr terminated string. + int length; + + // The timecode of this frame in 100ns intervals + int64_t timecode; + + // The metadata as a UTF8 XML string. This is a nullptr terminated string. + char* p_data; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_metadata_frame_t(int length_ = 0, int64_t timecode_ = NDIlib_send_timecode_synthesize, char* p_data_ = nullptr); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_metadata_frame_t; + +// Tally structures +typedef struct NDIlib_tally_t +{ // Is this currently on program output + bool on_program; + + // Is this currently on preview output + bool on_preview; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_tally_t(bool on_program_ = false, bool on_preview_ = false); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_tally_t; diff --git a/src/modules/newtek/interop/Processing.NDI.utilities.h b/src/modules/newtek/interop/Processing.NDI.utilities.h new file mode 100644 index 0000000000..a7312a4d28 --- /dev/null +++ b/src/modules/newtek/interop/Processing.NDI.utilities.h @@ -0,0 +1,108 @@ +#pragma once + +// NOTE : The following MIT license applies to this file ONLY and not to the SDK as a whole. Please review the SDK documentation +// for the description of the full license terms, which are also provided in the file "NDI License Agreement.pdf" within the SDK or +// online at http://new.tk/ndisdk_license/. Your use of any part of this SDK is acknowledgment that you agree to the SDK license +// terms. The full NDI SDK may be downloaded at https://www.newtek.com/ndi/sdk/ +// +//*********************************************************************************************************************************************** +// +// Copyright(c) 2014-2018 NewTek, inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//*********************************************************************************************************************************************** + +// Because many applications like submitting 16bit interleaved audio, these functions will convert in +// and out of that format. It is important to note that the NDI SDK does define fully audio levels, something +// that most applications that you use do not. Specifically, the floating point -1.0 to +1.0 range is defined +// as a professional audio reference level of +4dBU. If we take 16bit audio and scale it into this range +// it is almost always correct for sending and will cause no problems. For receiving however it is not at +// all uncommon that the user has audio that exceeds reference level and in this case it is likely that audio +// exceeds the reference level and so if you are not careful you will end up having audio clipping when +// you use the 16 bit range. + +// This describes an audio frame +typedef struct NDIlib_audio_frame_interleaved_16s_t +{ // The sample-rate of this buffer + int sample_rate; + + // The number of audio channels + int no_channels; + + // The number of audio samples per channel + int no_samples; + + // The timecode of this frame in 100ns intervals + int64_t timecode; + + // The audio reference level in dB. This specifies how many dB above the reference level (+4dBU) is the full range of 16 bit audio. + // If you do not understand this and want to just use numbers : + // - If you are sending audio, specify +0dB. Most common applications produce audio at reference level. + // - If receiving audio, specify +20dB. This means that the full 16 bit range corresponds to professional level audio with 20dB of headroom. Note that + // if you are writing it into a file it might sound soft because you have 20dB of headroom before clipping. + int reference_level; + + // The audio data, interleaved 16bpp + short* p_data; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_audio_frame_interleaved_16s_t(int sample_rate_ = 48000, int no_channels_ = 2, int no_samples_ = 0, int64_t timecode_ = NDIlib_send_timecode_synthesize, + int reference_level_ = 0, short* p_data_ = nullptr); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_audio_frame_interleaved_16s_t; + +// This describes an audio frame +typedef struct NDIlib_audio_frame_interleaved_32f_t +{ // The sample-rate of this buffer + int sample_rate; + + // The number of audio channels + int no_channels; + + // The number of audio samples per channel + int no_samples; + + // The timecode of this frame in 100ns intervals + int64_t timecode; + + // The audio data, interleaved 32bpp + float* p_data; + +#if NDILIB_CPP_DEFAULT_CONSTRUCTORS + NDIlib_audio_frame_interleaved_32f_t(int sample_rate_ = 48000, int no_channels_ = 2, int no_samples_ = 0, int64_t timecode_ = NDIlib_send_timecode_synthesize, + float* p_data_ = nullptr); +#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS + +} NDIlib_audio_frame_interleaved_32f_t; + +// This will add an audio frame in 16bpp +PROCESSINGNDILIB_API +void NDIlib_util_send_send_audio_interleaved_16s(NDIlib_send_instance_t p_instance, const NDIlib_audio_frame_interleaved_16s_t* p_audio_data); + +// This will add an audio frame interleaved floating point +PROCESSINGNDILIB_API +void NDIlib_util_send_send_audio_interleaved_32f(NDIlib_send_instance_t p_instance, const NDIlib_audio_frame_interleaved_32f_t* p_audio_data); + +PROCESSINGNDILIB_API +void NDIlib_util_audio_to_interleaved_16s_v2(const NDIlib_audio_frame_v2_t* p_src, NDIlib_audio_frame_interleaved_16s_t* p_dst); + +PROCESSINGNDILIB_API +void NDIlib_util_audio_from_interleaved_16s_v2(const NDIlib_audio_frame_interleaved_16s_t* p_src, NDIlib_audio_frame_v2_t* p_dst); + +PROCESSINGNDILIB_API +void NDIlib_util_audio_to_interleaved_32f_v2(const NDIlib_audio_frame_v2_t* p_src, NDIlib_audio_frame_interleaved_32f_t* p_dst); + +PROCESSINGNDILIB_API +void NDIlib_util_audio_from_interleaved_32f_v2(const NDIlib_audio_frame_interleaved_32f_t* p_src, NDIlib_audio_frame_v2_t* p_dst); diff --git a/src/modules/newtek/newtek.cpp b/src/modules/newtek/newtek.cpp index 3c4d9ca051..f0c4c255dd 100644 --- a/src/modules/newtek/newtek.cpp +++ b/src/modules/newtek/newtek.cpp @@ -24,6 +24,11 @@ #include "consumer/newtek_ivga_consumer.h" #include "util/air_send.h" +#include "consumer/newtek_ndi_consumer.h" +#include "producer/newtek_ndi_producer.h" + +#include "util/ndi.h" + #include namespace caspar { namespace newtek { @@ -34,8 +39,15 @@ void init(core::module_dependencies dependencies) dependencies.consumer_registry->register_consumer_factory(L"iVGA Consumer", create_ivga_consumer); dependencies.consumer_registry->register_preconfigured_consumer_factory(L"newtek-ivga", create_preconfigured_ivga_consumer); + + dependencies.consumer_registry->register_consumer_factory(L"NDI Consumer", create_ndi_consumer); + dependencies.consumer_registry->register_preconfigured_consumer_factory(L"newtek-ndi", + create_preconfigured_ndi_consumer); + + dependencies.producer_registry->register_producer_factory(L"NDI Producer", create_ndi_producer); + } catch (...) { } } -}} // namespace caspar::newtek \ No newline at end of file +}} // namespace caspar::newtek diff --git a/src/modules/newtek/producer/newtek_ndi_producer.cpp b/src/modules/newtek/producer/newtek_ndi_producer.cpp new file mode 100644 index 0000000000..1ebf67bd6c --- /dev/null +++ b/src/modules/newtek/producer/newtek_ndi_producer.cpp @@ -0,0 +1,269 @@ +/* + * Copyright 2018 + * + * 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: Krzysztof Zegzula, zegzulakrzysztof@gmail.com + * based on work of Robert Nagy, ronag89@gmail.com and Jerzy Jaśkiewicz, jurek@tvp.pl + */ + +#include "../StdAfx.h" + +#include "newtek_ndi_producer.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4244) +#endif +extern "C" { +#include +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#include + +#include "../util/ndi.h" + +namespace caspar { namespace newtek { + +struct newtek_ndi_producer : public core::frame_producer +{ + static int instances_; + int instance_no_; + const std::wstring name_; + + spl::shared_ptr frame_factory_; + core::video_format_desc format_desc_; + NDIlib_v3* ndi_lib_; + NDIlib_framesync_instance_t ndi_framesync_; + NDIlib_recv_instance_t ndi_recv_instance_; + NDIlib_video_frame_v2_t ndi_video_frame_; + NDIlib_audio_frame_v2_t ndi_audio_frame_; + spl::shared_ptr graph_; + timer tick_timer_; + timer frame_timer_; + + std::queue frames_; + mutable std::mutex frames_mutex_; + core::draw_frame last_frame_ = core::draw_frame::empty(); + executor executor_; + + int cadence_counter_; + int cadence_length_; + + public: + explicit newtek_ndi_producer(spl::shared_ptr frame_factory, + core::video_format_desc format_desc, + std::wstring name) + : format_desc_(format_desc) + , frame_factory_(frame_factory) + , name_(name) + , executor_(print()) + , instance_no_(instances_++) + , cadence_counter_(0) + { + if (!ndi::load_library()) { + CASPAR_THROW_EXCEPTION(not_supported() << msg_info( + ndi::dll_name() + L" not available. Install NDI Redist version 3.7 or higher.")); + } + + graph_->set_text(print()); + graph_->set_color("frame-time", diagnostics::color(0.5f, 1.0f, 0.2f)); + graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f)); + graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f)); + diagnostics::register_graph(graph_); + executor_.set_capacity(1); + cadence_length_ = static_cast(format_desc_.audio_cadence.size()); + initialize(); + } + + ~newtek_ndi_producer() + { + executor_.stop(); + ndi::fs_destroy(ndi_framesync_); + ndi_lib_->NDIlib_recv_destroy(ndi_recv_instance_); + } + + std::wstring print() const override + { + return L"newtek-ndi[" + boost::lexical_cast(instance_no_) + L"|" + name_ + L"]"; + } + + std::wstring name() const override { return L"newtek-ndi"; } + + core::draw_frame receive_impl(int hints) override + { + graph_->set_value("tick-time", tick_timer_.elapsed() * format_desc_.fps * 0.5); + tick_timer_.restart(); + core::draw_frame frame; + executor_.begin_invoke([this]() { prepare_next_frame(); }); + { + std::lock_guard lock(frames_mutex_); + if (frames_.size() > 0) { + frame = frames_.front(); + frames_.pop(); + last_frame_ = frame; + } else { + frame = last_frame_; + } + return frame; + } + } + bool prepare_next_frame() + { + try { + frame_timer_.restart(); + NDIlib_video_frame_v2_t video_frame; + NDIlib_audio_frame_v2_t audio_frame; + ndi::fs_capture_audio(ndi_framesync_, + &audio_frame, + format_desc_.audio_sample_rate, + format_desc_.audio_channels, + format_desc_.audio_cadence[++cadence_counter_ %= cadence_length_]); + ndi::fs_capture_video(ndi_framesync_, &video_frame, NDIlib_frame_format_type_progressive); + if (video_frame.p_data != nullptr) { + std::shared_ptr av_frame(av_frame_alloc(), [](AVFrame* frame) { av_frame_free(&frame); }); + std::shared_ptr a_frame(av_frame_alloc(), [](AVFrame* frame) { av_frame_free(&frame); }); + av_frame->data[0] = video_frame.p_data; + av_frame->linesize[0] = video_frame.line_stride_in_bytes; + switch (video_frame.FourCC) { + break; + case NDIlib_FourCC_type_BGRA: + av_frame->format = AV_PIX_FMT_BGRA; + break; + case NDIlib_FourCC_type_BGRX: + av_frame->format = AV_PIX_FMT_BGRA; + break; + case NDIlib_FourCC_type_RGBA: + av_frame->format = AV_PIX_FMT_RGBA; + break; + case NDIlib_FourCC_type_RGBX: + av_frame->format = AV_PIX_FMT_RGBA; + break; + default: + av_frame->format = AV_PIX_FMT_BGRA; + CASPAR_LOG(warning) + << print() << L" NDI frame format not supported (" << video_frame.FourCC << L")."; + } + av_frame->width = video_frame.xres; + av_frame->height = video_frame.yres; + av_frame->interlaced_frame = + video_frame.frame_format_type == NDIlib_frame_format_type_interleaved ? 1 : 0; + av_frame->top_field_first = av_frame->interlaced_frame; + NDIlib_audio_frame_interleaved_16s_t audio_frame_16s; + std::vector audio_data_32s; + audio_frame_16s.p_data = new short[audio_frame.no_samples * audio_frame.no_channels]; + if (audio_frame.p_data != nullptr) { + audio_frame_16s.reference_level = 0; // not sure + ndi_lib_->NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &audio_frame_16s); + a_frame->channels = audio_frame_16s.no_channels; + a_frame->sample_rate = audio_frame_16s.sample_rate; + + a_frame->format = AV_SAMPLE_FMT_S32; + a_frame->nb_samples = audio_frame_16s.no_samples; + audio_data_32s = + ndi::audio_16_to_32(audio_frame_16s.p_data, audio_frame.no_samples * audio_frame.no_channels); + a_frame->data[0] = reinterpret_cast(audio_data_32s.data()); + } + auto mframe = + ffmpeg::make_frame(this, *(frame_factory_.get()), std::move(av_frame), std::move(a_frame)); + auto dframe = core::draw_frame(std::move(mframe)); + { + std::lock_guard lock(frames_mutex_); + frames_.push(dframe); + while (frames_.size() > 2) { // should never happen because of frame sync + frames_.pop(); + graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame"); + CASPAR_LOG(info) << print() << "Frame dropped"; + } + } + ndi::fs_free_audio(ndi_framesync_, &audio_frame); + ndi::fs_free_video(ndi_framesync_, &video_frame); + delete[] audio_frame_16s.p_data; + } + + graph_->set_value("frame-time", frame_timer_.elapsed() * format_desc_.fps * 0.5); + } catch (...) { + CASPAR_LOG_CURRENT_EXCEPTION(); + return false; + } + return true; + } + + // frame_producer + + void initialize() + { + ndi_lib_ = ndi::load_library(); + NDIlib_recv_create_v3_t NDI_recv_create_desc; + NDI_recv_create_desc.allow_video_fields = false; + NDI_recv_create_desc.bandwidth = NDIlib_recv_bandwidth_highest; + NDI_recv_create_desc.color_format = NDIlib_recv_color_format_BGRX_BGRA; + std::string tmp_name = u8(name_); + NDI_recv_create_desc.p_ndi_recv_name = tmp_name.c_str(); + NDI_recv_create_desc.source_to_connect_to.p_ndi_name = tmp_name.c_str(); + ndi_recv_instance_ = ndi_lib_->NDIlib_recv_create_v3(&NDI_recv_create_desc); + ndi_framesync_ = ndi::fs_create(ndi_recv_instance_); + CASPAR_VERIFY(ndi_recv_instance_); + } + +}; // namespace newtek + +int newtek_ndi_producer::instances_ = 0; +spl::shared_ptr create_ndi_producer(const core::frame_producer_dependencies& dependencies, + const std::vector& params) +{ + if (params.size() < 2 || !boost::iequals(params.at(0), "ndi")) + return core::frame_producer::empty(); + + auto name = params.at(1); + + try { + auto producer = + spl::make_shared(dependencies.frame_factory, dependencies.format_desc, name); + return core::create_destroy_proxy(std::move(producer)); + } catch (...) { + CASPAR_LOG_CURRENT_EXCEPTION(); + } + return core::frame_producer::empty(); +} +}} // namespace caspar::newtek diff --git a/src/modules/newtek/producer/newtek_ndi_producer.h b/src/modules/newtek/producer/newtek_ndi_producer.h new file mode 100644 index 0000000000..977c77c1d9 --- /dev/null +++ b/src/modules/newtek/producer/newtek_ndi_producer.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018 + * + * 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: Krzysztof Zegzula, zegzulakrzysztof@gmail.com + * based on work of Robert Nagy, ronag89@gmail.com + */ + +#pragma once + +#include + +#include + +#include + +#include + +namespace caspar { namespace newtek { + +spl::shared_ptr create_ndi_producer(const core::frame_producer_dependencies& dependencies, + const std::vector& params); + +}} // namespace caspar::newtek diff --git a/src/modules/newtek/util/ndi.cpp b/src/modules/newtek/util/ndi.cpp new file mode 100644 index 0000000000..cb9e072640 --- /dev/null +++ b/src/modules/newtek/util/ndi.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2018 + * + * 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: Krzysztof Zegzula, zegzulakrzysztof@gmail.com + */ + +#include "../StdAfx.h" + +#include "ndi.h" + +#include + +#include + +#include + +#include + +namespace caspar { namespace newtek { namespace ndi { + +NDIlib_framesync_instance_t (*fs_create)(NDIlib_recv_instance_t p_receiver) = nullptr; +void (*fs_destroy)(NDIlib_framesync_instance_t p_instance) = nullptr; +void (*fs_capture_audio)(NDIlib_framesync_instance_t p_instance, + NDIlib_audio_frame_v2_t* p_audio_data, + int sample_rate, + int no_channels, + int no_samples) = nullptr; + +void (*fs_free_audio)(NDIlib_framesync_instance_t p_instance, NDIlib_audio_frame_v2_t* p_audio_data) = nullptr; + +void (*fs_capture_video)(NDIlib_framesync_instance_t p_instance, + NDIlib_video_frame_v2_t* p_video_data, + NDIlib_frame_format_type_e field_type) = nullptr; + +void (*fs_free_video)(NDIlib_framesync_instance_t p_instance, NDIlib_video_frame_v2_t* p_video_data) = nullptr; +const std::wstring& dll_name(); +NDIlib_v3* load_library(); + +const std::wstring& dll_name() +{ + static std::wstring name = L"Processing.NDI.Lib.x64.dll"; + + return name; +} + +NDIlib_v3* load_library() +{ + static NDIlib_v3* ndi_lib = nullptr; + + if (ndi_lib) + return ndi_lib; + auto runtime_dir = _wgetenv(L"NDI_RUNTIME_DIR_V3"); + + if (runtime_dir == NULL) + return nullptr; + auto dll_path = boost::filesystem::path(runtime_dir) / dll_name(); + auto module = LoadLibrary(dll_path.c_str()); + + if (!module) + return nullptr; + + static std::shared_ptr lib(module, FreeLibrary); + + wchar_t actualFilename[256]; + GetModuleFileNameW(module, actualFilename, sizeof(actualFilename)); + CASPAR_LOG(info) << L"Loaded " << actualFilename; + + auto NDIlib_v3_load = GetProcAddress(module, "NDIlib_v3_load"); + ndi_lib = (NDIlib_v3*)(NDIlib_v3_load()); + + // these functions have to be loaded this way because they aren't in NDIlib_v3 struct + fs_create = reinterpret_cast(GetProcAddress(module, "NDIlib_framesync_create")); + fs_destroy = reinterpret_cast(GetProcAddress(module, "NDIlib_framesync_destroy")); + fs_capture_audio = + reinterpret_cast(GetProcAddress(module, "NDIlib_framesync_capture_audio")); + fs_free_audio = reinterpret_cast(GetProcAddress(module, "NDIlib_framesync_free_audio")); + fs_capture_video = + reinterpret_cast(GetProcAddress(module, "NDIlib_framesync_capture_video")); + fs_free_video = reinterpret_cast(GetProcAddress(module, "NDIlib_framesync_free_video")); + return ndi_lib; +} + +std::vector audio_16_to_32(short* audio_data, int size) +{ + auto output32 = std::vector(); + + output32.reserve(size); + for (int n = 0; n < size; ++n) + output32.push_back((audio_data[n] & 0xFFFFFFFF) << 16); + + return output32; +} + +}}} // namespace caspar::newtek::ndi diff --git a/src/modules/newtek/util/ndi.h b/src/modules/newtek/util/ndi.h new file mode 100644 index 0000000000..573fc0226e --- /dev/null +++ b/src/modules/newtek/util/ndi.h @@ -0,0 +1,48 @@ +/* + * Copyright 2013 Sveriges Television AB http://casparcg.com/ + * + * 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: Krzysztof Zegzula, zegzulakrzysztof@gmail.com + */ +#pragma once + +#include "../interop/Processing.NDI.Lib.h" +#include + +namespace caspar { namespace newtek { namespace ndi { +extern NDIlib_framesync_instance_t (*fs_create)(NDIlib_recv_instance_t p_receiver); +extern void (*fs_destroy)(NDIlib_framesync_instance_t p_instance); +extern void (*fs_capture_audio)(NDIlib_framesync_instance_t p_instance, + NDIlib_audio_frame_v2_t* p_audio_data, + int sample_rate, + int no_channels, + int no_samples); + +extern void (*fs_free_audio)(NDIlib_framesync_instance_t p_instance, NDIlib_audio_frame_v2_t* p_audio_data); + +extern void (*fs_capture_video)(NDIlib_framesync_instance_t p_instance, + NDIlib_video_frame_v2_t* p_video_data, + NDIlib_frame_format_type_e field_type); + +extern void (*fs_free_video)(NDIlib_framesync_instance_t p_instance, NDIlib_video_frame_v2_t* p_video_data); + +const std::wstring& dll_name(); +NDIlib_v3* load_library(); + +std::vector audio_16_to_32(short* audio_data, int size); + +}}} // namespace caspar::newtek::ndi From 9fae7da3678a31552c3432d6536229c3514a2b3f Mon Sep 17 00:00:00 2001 From: ianshade Date: Thu, 8 Nov 2018 22:25:14 +0100 Subject: [PATCH 02/13] Changed audio sending to interleaved 32f --- .../newtek/consumer/newtek_ndi_consumer.cpp | 28 +++++++++++-------- .../newtek/producer/newtek_ndi_producer.cpp | 2 -- src/modules/newtek/util/ndi.cpp | 13 ++++++++- src/modules/newtek/util/ndi.h | 3 +- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp index 13f1862293..50c84064de 100644 --- a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp +++ b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp @@ -50,8 +50,7 @@ struct newtek_ndi_consumer : public core::frame_consumer NDIlib_v3* ndi_lib_; NDIlib_send_instance_t ndi_send_instance_; NDIlib_video_frame_v2_t ndi_video_frame_; - NDIlib_audio_frame_interleaved_16s_t ndi_audio_frame_; - std::atomic connected_ = false; + NDIlib_audio_frame_interleaved_32f_t ndi_audio_frame_; spl::shared_ptr graph_; caspar::timer tick_timer_; caspar::timer frame_timer_; @@ -85,7 +84,7 @@ struct newtek_ndi_consumer : public core::frame_consumer NDIlib_send_create_t NDI_send_create_desc; auto tmp_name = u8(name_); NDI_send_create_desc.p_ndi_name = tmp_name.c_str(); - NDI_send_create_desc.clock_video = false; // TODO: maybe send on separate thread and keep clocking + NDI_send_create_desc.clock_video = false; // TODO: maybe keep clocking (?) NDI_send_create_desc.clock_audio = false; ndi_send_instance_ = ndi_lib_->NDIlib_send_create(&NDI_send_create_desc); @@ -96,6 +95,10 @@ struct newtek_ndi_consumer : public core::frame_consumer ndi_video_frame_.FourCC = NDIlib_FourCC_type_BGRA; ndi_video_frame_.line_stride_in_bytes = format_desc.width * 4; + ndi_audio_frame_.sample_rate = format_desc_.audio_sample_rate; + ndi_audio_frame_.no_channels = format_desc_.audio_channels; + ndi_audio_frame_.timecode = NDIlib_send_timecode_synthesize; + CASPAR_VERIFY(ndi_send_instance_); } @@ -105,16 +108,14 @@ struct newtek_ndi_consumer : public core::frame_consumer graph_->set_value("tick-time", tick_timer_.elapsed() * format_desc_.fps * 0.5); tick_timer_.restart(); - frame_timer_.restart(); - auto audio_buffer = core::audio_32_to_16(frame.audio_data()); - ndi_audio_frame_.sample_rate = format_desc_.audio_sample_rate; - ndi_audio_frame_.no_channels = format_desc_.audio_channels; - ndi_audio_frame_.no_samples = static_cast(audio_buffer.size()) / format_desc_.audio_channels; - ndi_audio_frame_.timecode = NDIlib_send_timecode_synthesize; - ndi_audio_frame_.p_data = const_cast(audio_buffer.data()); - ndi_lib_->NDIlib_util_send_send_audio_interleaved_16s(ndi_send_instance_, &ndi_audio_frame_); + auto audio_data = frame.audio_data(); + int audio_data_size = static_cast(audio_data.size()); + ndi_audio_frame_.no_samples = audio_data_size / format_desc_.audio_channels; + std::vector audio_buffer = ndi::audio_32_to_32f(audio_data.data(), audio_data_size); + ndi_audio_frame_.p_data = const_cast(audio_buffer.data()); + ndi_lib_->NDIlib_util_send_send_audio_interleaved_32f(ndi_send_instance_, &ndi_audio_frame_); ndi_video_frame_.p_data = const_cast(frame.image_data(0).begin()); ndi_lib_->NDIlib_send_send_video_v2(ndi_send_instance_, &ndi_video_frame_); @@ -124,7 +125,10 @@ struct newtek_ndi_consumer : public core::frame_consumer return make_ready_future(true); } - std::wstring print() const override { return L"newtek-ndi[" + name_ + L"]"; } + std::wstring print() const override + { + return L"newtek-ndi[" + name_ + L"]"; + } // TODO: maybe put tally status in the name std::wstring name() const override { return L"newtek-ndi"; } diff --git a/src/modules/newtek/producer/newtek_ndi_producer.cpp b/src/modules/newtek/producer/newtek_ndi_producer.cpp index 1ebf67bd6c..4fe628c778 100644 --- a/src/modules/newtek/producer/newtek_ndi_producer.cpp +++ b/src/modules/newtek/producer/newtek_ndi_producer.cpp @@ -197,8 +197,6 @@ struct newtek_ndi_producer : public core::frame_producer ndi_lib_->NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &audio_frame_16s); a_frame->channels = audio_frame_16s.no_channels; a_frame->sample_rate = audio_frame_16s.sample_rate; - - a_frame->format = AV_SAMPLE_FMT_S32; a_frame->nb_samples = audio_frame_16s.no_samples; audio_data_32s = ndi::audio_16_to_32(audio_frame_16s.p_data, audio_frame.no_samples * audio_frame.no_channels); diff --git a/src/modules/newtek/util/ndi.cpp b/src/modules/newtek/util/ndi.cpp index cb9e072640..cc833ed3b1 100644 --- a/src/modules/newtek/util/ndi.cpp +++ b/src/modules/newtek/util/ndi.cpp @@ -95,7 +95,7 @@ NDIlib_v3* load_library() return ndi_lib; } -std::vector audio_16_to_32(short* audio_data, int size) +std::vector audio_16_to_32(const short* audio_data, int size) { auto output32 = std::vector(); @@ -106,4 +106,15 @@ std::vector audio_16_to_32(short* audio_data, int size) return output32; } +std::vector audio_32_to_32f(const int* audio_data, int size) +{ + auto output32 = std::vector(); + + output32.reserve(size); + for (int n = 0; n < size; ++n) + output32.push_back((1.0f * audio_data[n]) / INT32_MAX); + + return output32; +} + }}} // namespace caspar::newtek::ndi diff --git a/src/modules/newtek/util/ndi.h b/src/modules/newtek/util/ndi.h index 573fc0226e..462550aac5 100644 --- a/src/modules/newtek/util/ndi.h +++ b/src/modules/newtek/util/ndi.h @@ -43,6 +43,7 @@ extern void (*fs_free_video)(NDIlib_framesync_instance_t p_instance, NDIlib_vide const std::wstring& dll_name(); NDIlib_v3* load_library(); -std::vector audio_16_to_32(short* audio_data, int size); +std::vector audio_16_to_32(const short* audio_data, int size); +std::vector audio_32_to_32f(const int* audio_data, int size); }}} // namespace caspar::newtek::ndi From cc2e5ca652a4923be8a84bf8934444f776345618 Mon Sep 17 00:00:00 2001 From: ianshade Date: Thu, 8 Nov 2018 23:29:38 +0100 Subject: [PATCH 03/13] Reverted clockin --- src/modules/newtek/consumer/newtek_ndi_consumer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp index 50c84064de..1f43aad03d 100644 --- a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp +++ b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp @@ -84,8 +84,8 @@ struct newtek_ndi_consumer : public core::frame_consumer NDIlib_send_create_t NDI_send_create_desc; auto tmp_name = u8(name_); NDI_send_create_desc.p_ndi_name = tmp_name.c_str(); - NDI_send_create_desc.clock_video = false; // TODO: maybe keep clocking (?) - NDI_send_create_desc.clock_audio = false; + //NDI_send_create_desc.clock_video = false; + //NDI_send_create_desc.clock_audio = false; ndi_send_instance_ = ndi_lib_->NDIlib_send_create(&NDI_send_create_desc); ndi_video_frame_.xres = format_desc.width; From bdb56d7f801a027c2f54e4dbc4ab6d29e139c430 Mon Sep 17 00:00:00 2001 From: ianshade Date: Sat, 10 Nov 2018 18:27:02 +0100 Subject: [PATCH 04/13] added NDI Find for faster connection to sources --- .../newtek/consumer/newtek_ndi_consumer.cpp | 5 +- .../newtek/producer/newtek_ndi_producer.cpp | 55 +++++++---- src/modules/newtek/util/ndi.cpp | 93 ++++++++++++++++--- src/modules/newtek/util/ndi.h | 7 +- 4 files changed, 125 insertions(+), 35 deletions(-) diff --git a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp index 1f43aad03d..f63fd3e47a 100644 --- a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp +++ b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp @@ -38,8 +38,6 @@ #include #include -#include - #include "../util/ndi.h" namespace caspar { namespace newtek { @@ -61,8 +59,7 @@ struct newtek_ndi_consumer : public core::frame_consumer : name_(name) { if (!ndi::load_library()) { - CASPAR_THROW_EXCEPTION(not_supported() << msg_info( - ndi::dll_name() + L" not available. Install NDI Redist version 3.7 or higher.")); + ndi::not_installed(); } graph_->set_text(print()); diff --git a/src/modules/newtek/producer/newtek_ndi_producer.cpp b/src/modules/newtek/producer/newtek_ndi_producer.cpp index 4fe628c778..3ab79a1722 100644 --- a/src/modules/newtek/producer/newtek_ndi_producer.cpp +++ b/src/modules/newtek/producer/newtek_ndi_producer.cpp @@ -59,7 +59,6 @@ extern "C" { #ifdef _MSC_VER #pragma warning(pop) #endif -#include #include "../util/ndi.h" @@ -85,6 +84,7 @@ struct newtek_ndi_producer : public core::frame_producer std::queue frames_; mutable std::mutex frames_mutex_; core::draw_frame last_frame_ = core::draw_frame::empty(); + int frame_no_; executor executor_; int cadence_counter_; @@ -100,10 +100,10 @@ struct newtek_ndi_producer : public core::frame_producer , executor_(print()) , instance_no_(instances_++) , cadence_counter_(0) + , frame_no_(0) { if (!ndi::load_library()) { - CASPAR_THROW_EXCEPTION(not_supported() << msg_info( - ndi::dll_name() + L" not available. Install NDI Redist version 3.7 or higher.")); + ndi::not_installed(); } graph_->set_text(print()); @@ -135,6 +135,9 @@ struct newtek_ndi_producer : public core::frame_producer graph_->set_value("tick-time", tick_timer_.elapsed() * format_desc_.fps * 0.5); tick_timer_.restart(); core::draw_frame frame; + if (0 == frame_no_++) { + executor_.invoke([this]() { prepare_next_frame(); }); + } executor_.begin_invoke([this]() { prepare_next_frame(); }); { std::lock_guard lock(frames_mutex_); @@ -154,19 +157,18 @@ struct newtek_ndi_producer : public core::frame_producer frame_timer_.restart(); NDIlib_video_frame_v2_t video_frame; NDIlib_audio_frame_v2_t audio_frame; + ndi::fs_capture_video(ndi_framesync_, &video_frame, NDIlib_frame_format_type_progressive); ndi::fs_capture_audio(ndi_framesync_, &audio_frame, format_desc_.audio_sample_rate, format_desc_.audio_channels, format_desc_.audio_cadence[++cadence_counter_ %= cadence_length_]); - ndi::fs_capture_video(ndi_framesync_, &video_frame, NDIlib_frame_format_type_progressive); if (video_frame.p_data != nullptr) { std::shared_ptr av_frame(av_frame_alloc(), [](AVFrame* frame) { av_frame_free(&frame); }); std::shared_ptr a_frame(av_frame_alloc(), [](AVFrame* frame) { av_frame_free(&frame); }); av_frame->data[0] = video_frame.p_data; av_frame->linesize[0] = video_frame.line_stride_in_bytes; switch (video_frame.FourCC) { - break; case NDIlib_FourCC_type_BGRA: av_frame->format = AV_PIX_FMT_BGRA; break; @@ -179,10 +181,12 @@ struct newtek_ndi_producer : public core::frame_producer case NDIlib_FourCC_type_RGBX: av_frame->format = AV_PIX_FMT_RGBA; break; - default: + default: // should never happen because library handles the conversion for us av_frame->format = AV_PIX_FMT_BGRA; - CASPAR_LOG(warning) - << print() << L" NDI frame format not supported (" << video_frame.FourCC << L")."; + // cannot log here: + /*CASPAR_LOG(warning) + << print() << L" NDI frame format not supported (" << video_frame.FourCC << L").";*/ + break; } av_frame->width = video_frame.xres; av_frame->height = video_frame.yres; @@ -193,11 +197,11 @@ struct newtek_ndi_producer : public core::frame_producer std::vector audio_data_32s; audio_frame_16s.p_data = new short[audio_frame.no_samples * audio_frame.no_channels]; if (audio_frame.p_data != nullptr) { - audio_frame_16s.reference_level = 0; // not sure + audio_frame_16s.reference_level = 0; ndi_lib_->NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &audio_frame_16s); a_frame->channels = audio_frame_16s.no_channels; a_frame->sample_rate = audio_frame_16s.sample_rate; - a_frame->nb_samples = audio_frame_16s.no_samples; + a_frame->nb_samples = audio_frame_16s.no_samples; audio_data_32s = ndi::audio_16_to_32(audio_frame_16s.p_data, audio_frame.no_samples * audio_frame.no_channels); a_frame->data[0] = reinterpret_cast(audio_data_32s.data()); @@ -208,7 +212,7 @@ struct newtek_ndi_producer : public core::frame_producer { std::lock_guard lock(frames_mutex_); frames_.push(dframe); - while (frames_.size() > 2) { // should never happen because of frame sync + while (frames_.size() > 2) { // should never happen because frame sync takes care of it frames_.pop(); graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame"); CASPAR_LOG(info) << print() << "Frame dropped"; @@ -231,19 +235,34 @@ struct newtek_ndi_producer : public core::frame_producer void initialize() { - ndi_lib_ = ndi::load_library(); + ndi_lib_ = ndi::load_library(); + auto sources = ndi::get_current_sources(); NDIlib_recv_create_v3_t NDI_recv_create_desc; - NDI_recv_create_desc.allow_video_fields = false; - NDI_recv_create_desc.bandwidth = NDIlib_recv_bandwidth_highest; - NDI_recv_create_desc.color_format = NDIlib_recv_color_format_BGRX_BGRA; - std::string tmp_name = u8(name_); - NDI_recv_create_desc.p_ndi_recv_name = tmp_name.c_str(); - NDI_recv_create_desc.source_to_connect_to.p_ndi_name = tmp_name.c_str(); + NDI_recv_create_desc.allow_video_fields = false; + NDI_recv_create_desc.bandwidth = NDIlib_recv_bandwidth_highest; + NDI_recv_create_desc.color_format = NDIlib_recv_color_format_BGRX_BGRA; + std::string src_name = u8(name_); + + NDI_recv_create_desc.source_to_connect_to.p_ndi_name = src_name.c_str(); + NDI_recv_create_desc.p_ndi_recv_name = src_name.c_str(); ndi_recv_instance_ = ndi_lib_->NDIlib_recv_create_v3(&NDI_recv_create_desc); ndi_framesync_ = ndi::fs_create(ndi_recv_instance_); + auto found_source = sources->find(src_name); + if (found_source != sources->end()) { + + ndi_lib_->NDIlib_recv_connect(ndi_recv_instance_, &found_source->second); + } CASPAR_VERIFY(ndi_recv_instance_); } + core::draw_frame last_frame() override + { + if (!last_frame_) { + last_frame_ = receive_impl(0); + } + return core::draw_frame::still(last_frame_); + } + }; // namespace newtek int newtek_ndi_producer::instances_ = 0; diff --git a/src/modules/newtek/util/ndi.cpp b/src/modules/newtek/util/ndi.cpp index cc833ed3b1..6ccd7437c0 100644 --- a/src/modules/newtek/util/ndi.cpp +++ b/src/modules/newtek/util/ndi.cpp @@ -24,8 +24,14 @@ #include "ndi.h" #include +#include -#include +#ifdef _WIN32 +#include +#else +#include +#include +#endif #include @@ -48,41 +54,69 @@ void (*fs_capture_video)(NDIlib_framesync_instance_t p_instance, NDIlib_frame_format_type_e field_type) = nullptr; void (*fs_free_video)(NDIlib_framesync_instance_t p_instance, NDIlib_video_frame_v2_t* p_video_data) = nullptr; -const std::wstring& dll_name(); -NDIlib_v3* load_library(); const std::wstring& dll_name() { - static std::wstring name = L"Processing.NDI.Lib.x64.dll"; + static std::wstring name = u16(NDILIB_LIBRARY_NAME); return name; } +static std::mutex find_instance_mutex; +static std::shared_ptr find_instance; + NDIlib_v3* load_library() { static NDIlib_v3* ndi_lib = nullptr; if (ndi_lib) return ndi_lib; - auto runtime_dir = _wgetenv(L"NDI_RUNTIME_DIR_V3"); + const char* runtime_dir = getenv(NDILIB_REDIST_FOLDER); if (runtime_dir == NULL) return nullptr; - auto dll_path = boost::filesystem::path(runtime_dir) / dll_name(); - auto module = LoadLibrary(dll_path.c_str()); + + auto dll_path = boost::filesystem::path(runtime_dir) / NDILIB_LIBRARY_NAME; + +#ifdef _WIN32 + auto module = LoadLibrary(dll_path.c_str()); if (!module) return nullptr; static std::shared_ptr lib(module, FreeLibrary); - wchar_t actualFilename[256]; - GetModuleFileNameW(module, actualFilename, sizeof(actualFilename)); - CASPAR_LOG(info) << L"Loaded " << actualFilename; + // wchar_t actualFilename[256]; + // GetModuleFileNameW(module, actualFilename, sizeof(actualFilename)); + CASPAR_LOG(info) << L"Loaded " << dll_path; auto NDIlib_v3_load = GetProcAddress(module, "NDIlib_v3_load"); - ndi_lib = (NDIlib_v3*)(NDIlib_v3_load()); + if (!NDIlib_v3_load) + return nullptr; + +#else + // Try to load the library + void* hNDILib = dlopen(dll_path.c_str(), RTLD_LOCAL | RTLD_LAZY); + + // The main NDI entry point for dynamic loading if we got the library + const NDIlib_v3* (*NDIlib_v3_load)(void) = NULL; + if (hNDILib) { + CASPAR_LOG(info) << L"Loaded " << dll_path; + static std::shared_ptr lib(hNDILib, dlclose); + *((void**)&NDIlib_v3_load) = dlsym(hNDILib, "NDIlib_v3_load"); + } + if (!NDIlib_v3_load) + return nullptr; + +#endif + + ndi_lib = (NDIlib_v3*)(NDIlib_v3_load()); + if (!ndi_lib->NDIlib_initialize()) { + not_initialized(); + } + +#ifdef _WIN32 // these functions have to be loaded this way because they aren't in NDIlib_v3 struct fs_create = reinterpret_cast(GetProcAddress(module, "NDIlib_framesync_create")); fs_destroy = reinterpret_cast(GetProcAddress(module, "NDIlib_framesync_destroy")); @@ -92,9 +126,46 @@ NDIlib_v3* load_library() fs_capture_video = reinterpret_cast(GetProcAddress(module, "NDIlib_framesync_capture_video")); fs_free_video = reinterpret_cast(GetProcAddress(module, "NDIlib_framesync_free_video")); + +#else + *((void**)&fs_create) = dlsym(hNDILib, "NDIlib_framesync_create"); + *((void**)&fs_destroy) = dlsym(hNDILib, "NDIlib_framesync_destroy"); + *((void**)&fs_capture_audio) = dlsym(hNDILib, "NDIlib_framesync_capture_audio"); + *((void**)&fs_free_audio) = dlsym(hNDILib, "NDIlib_framesync_free_audio"); + *((void**)&fs_capture_video) = dlsym(hNDILib, "NDIlib_framesync_capture_video"); + *((void**)&fs_free_video) = dlsym(hNDILib, "NDIlib_framesync_free_video"); + +#endif + find_instance.reset(new NDIlib_find_instance_t(ndi_lib->NDIlib_find_create_v2(nullptr)), + [](NDIlib_find_instance_t* p) { ndi_lib->NDIlib_find_destroy(*p); }); return ndi_lib; } +std::shared_ptr> get_current_sources() +{ + auto sources_map = std::make_shared>(); + uint32_t no_sources; + std::lock_guard guard(find_instance_mutex); + const NDIlib_source_t* sources = + load_library()->NDIlib_find_get_current_sources(*(find_instance.get()), &no_sources); + for (uint32_t i = 0; i < no_sources; i++) { + sources_map->emplace(std::string(sources[i].p_ndi_name), sources[i]); + } + return sources_map; +} + +void not_installed() +{ + CASPAR_THROW_EXCEPTION(not_supported() + << msg_info(dll_name() + L" not available. Install NDI Redist version 3.7 or higher from " + + u16(NDILIB_REDIST_URL))); +} + +void not_initialized() +{ + CASPAR_THROW_EXCEPTION(not_supported() << msg_info("Unable to initialize NDI on this system.")); +} + std::vector audio_16_to_32(const short* audio_data, int size) { auto output32 = std::vector(); diff --git a/src/modules/newtek/util/ndi.h b/src/modules/newtek/util/ndi.h index 462550aac5..5d21f47384 100644 --- a/src/modules/newtek/util/ndi.h +++ b/src/modules/newtek/util/ndi.h @@ -40,8 +40,11 @@ extern void (*fs_capture_video)(NDIlib_framesync_instance_t p_instance, extern void (*fs_free_video)(NDIlib_framesync_instance_t p_instance, NDIlib_video_frame_v2_t* p_video_data); -const std::wstring& dll_name(); -NDIlib_v3* load_library(); +const std::wstring& dll_name(); +NDIlib_v3* load_library(); +std::shared_ptr> get_current_sources(); +void not_initialized(); +void not_installed(); std::vector audio_16_to_32(const short* audio_data, int size); std::vector audio_32_to_32f(const int* audio_data, int size); From 67a46b7265cc83fec28ea2b381dfebc2f70f01de Mon Sep 17 00:00:00 2001 From: ianshade Date: Sat, 10 Nov 2018 19:58:40 +0100 Subject: [PATCH 05/13] minor Find implementation tweaks --- src/modules/newtek/producer/newtek_ndi_producer.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modules/newtek/producer/newtek_ndi_producer.cpp b/src/modules/newtek/producer/newtek_ndi_producer.cpp index 3ab79a1722..95fbfd3192 100644 --- a/src/modules/newtek/producer/newtek_ndi_producer.cpp +++ b/src/modules/newtek/producer/newtek_ndi_producer.cpp @@ -243,15 +243,15 @@ struct newtek_ndi_producer : public core::frame_producer NDI_recv_create_desc.color_format = NDIlib_recv_color_format_BGRX_BGRA; std::string src_name = u8(name_); - NDI_recv_create_desc.source_to_connect_to.p_ndi_name = src_name.c_str(); + auto found_source = sources->find(src_name); + if (found_source != sources->end()) { + NDI_recv_create_desc.source_to_connect_to = found_source->second; + } else { + NDI_recv_create_desc.source_to_connect_to.p_ndi_name = src_name.c_str(); + } NDI_recv_create_desc.p_ndi_recv_name = src_name.c_str(); ndi_recv_instance_ = ndi_lib_->NDIlib_recv_create_v3(&NDI_recv_create_desc); ndi_framesync_ = ndi::fs_create(ndi_recv_instance_); - auto found_source = sources->find(src_name); - if (found_source != sources->end()) { - - ndi_lib_->NDIlib_recv_connect(ndi_recv_instance_, &found_source->second); - } CASPAR_VERIFY(ndi_recv_instance_); } From eeed1130e7270af7444509f64229da2318f14a50 Mon Sep 17 00:00:00 2001 From: ianshade Date: Thu, 15 Nov 2018 20:11:48 +0100 Subject: [PATCH 06/13] get_current_sources() return type changed --- .../newtek/producer/newtek_ndi_producer.cpp | 15 ++++++++------- src/modules/newtek/util/ndi.cpp | 10 +++++----- src/modules/newtek/util/ndi.h | 10 +++++----- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/modules/newtek/producer/newtek_ndi_producer.cpp b/src/modules/newtek/producer/newtek_ndi_producer.cpp index 95fbfd3192..fcd7074adc 100644 --- a/src/modules/newtek/producer/newtek_ndi_producer.cpp +++ b/src/modules/newtek/producer/newtek_ndi_producer.cpp @@ -235,23 +235,24 @@ struct newtek_ndi_producer : public core::frame_producer void initialize() { - ndi_lib_ = ndi::load_library(); - auto sources = ndi::get_current_sources(); + ndi_lib_ = ndi::load_library(); + auto sources = ndi::get_current_sources(); + NDIlib_recv_create_v3_t NDI_recv_create_desc; NDI_recv_create_desc.allow_video_fields = false; NDI_recv_create_desc.bandwidth = NDIlib_recv_bandwidth_highest; NDI_recv_create_desc.color_format = NDIlib_recv_color_format_BGRX_BGRA; std::string src_name = u8(name_); - auto found_source = sources->find(src_name); - if (found_source != sources->end()) { + auto found_source = sources.find(src_name); + if (found_source != sources.end()) { NDI_recv_create_desc.source_to_connect_to = found_source->second; } else { NDI_recv_create_desc.source_to_connect_to.p_ndi_name = src_name.c_str(); } - NDI_recv_create_desc.p_ndi_recv_name = src_name.c_str(); - ndi_recv_instance_ = ndi_lib_->NDIlib_recv_create_v3(&NDI_recv_create_desc); - ndi_framesync_ = ndi::fs_create(ndi_recv_instance_); + NDI_recv_create_desc.p_ndi_recv_name = src_name.c_str(); + ndi_recv_instance_ = ndi_lib_->NDIlib_recv_create_v3(&NDI_recv_create_desc); + ndi_framesync_ = ndi::fs_create(ndi_recv_instance_); CASPAR_VERIFY(ndi_recv_instance_); } diff --git a/src/modules/newtek/util/ndi.cpp b/src/modules/newtek/util/ndi.cpp index 6ccd7437c0..876c04cb3c 100644 --- a/src/modules/newtek/util/ndi.cpp +++ b/src/modules/newtek/util/ndi.cpp @@ -141,15 +141,15 @@ NDIlib_v3* load_library() return ndi_lib; } -std::shared_ptr> get_current_sources() +std::map get_current_sources() { - auto sources_map = std::make_shared>(); - uint32_t no_sources; + auto sources_map = std::map(); + uint32_t no_sources; std::lock_guard guard(find_instance_mutex); - const NDIlib_source_t* sources = + const NDIlib_source_t* sources = load_library()->NDIlib_find_get_current_sources(*(find_instance.get()), &no_sources); for (uint32_t i = 0; i < no_sources; i++) { - sources_map->emplace(std::string(sources[i].p_ndi_name), sources[i]); + sources_map.emplace(std::string(sources[i].p_ndi_name), sources[i]); } return sources_map; } diff --git a/src/modules/newtek/util/ndi.h b/src/modules/newtek/util/ndi.h index 5d21f47384..bc03cd8194 100644 --- a/src/modules/newtek/util/ndi.h +++ b/src/modules/newtek/util/ndi.h @@ -40,11 +40,11 @@ extern void (*fs_capture_video)(NDIlib_framesync_instance_t p_instance, extern void (*fs_free_video)(NDIlib_framesync_instance_t p_instance, NDIlib_video_frame_v2_t* p_video_data); -const std::wstring& dll_name(); -NDIlib_v3* load_library(); -std::shared_ptr> get_current_sources(); -void not_initialized(); -void not_installed(); +const std::wstring& dll_name(); +NDIlib_v3* load_library(); +std::map get_current_sources(); +void not_initialized(); +void not_installed(); std::vector audio_16_to_32(const short* audio_data, int size); std::vector audio_32_to_32f(const int* audio_data, int size); From 5b18eaae4a1957db2fdc6f39d6fd660f80a44428 Mon Sep 17 00:00:00 2001 From: ianshade Date: Thu, 15 Nov 2018 21:24:47 +0100 Subject: [PATCH 07/13] added configuration.ndi.auto-load parameter to load the dll and start NDI Finder immediately --- src/modules/newtek/newtek.cpp | 7 +++++++ src/shell/casparcg.config | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/modules/newtek/newtek.cpp b/src/modules/newtek/newtek.cpp index f0c4c255dd..7e21af1638 100644 --- a/src/modules/newtek/newtek.cpp +++ b/src/modules/newtek/newtek.cpp @@ -31,6 +31,9 @@ #include +#include +#include + namespace caspar { namespace newtek { void init(core::module_dependencies dependencies) @@ -45,6 +48,10 @@ void init(core::module_dependencies dependencies) create_preconfigured_ndi_consumer); dependencies.producer_registry->register_producer_factory(L"NDI Producer", create_ndi_producer); + + bool autoload = caspar::env::properties().get(L"configuration.ndi.auto-load", false); + if (autoload) + ndi::load_library(); } catch (...) { } diff --git a/src/shell/casparcg.config b/src/shell/casparcg.config index 3a87bea5fb..c8e3812fda 100644 --- a/src/shell/casparcg.config +++ b/src/shell/casparcg.config @@ -49,6 +49,9 @@ 0 [0|1024-65535] false [true|false] + + false [true|false] + PAL [PAL|NTSC|576p2500|720p2398|720p2400|720p2500|720p5000|720p2997|720p5994|720p3000|720p6000|1080p2398|1080p2400|1080i5000|1080i5994|1080i6000|1080p2500|1080p2997|1080p3000|1080p5000|1080p5994|1080p6000|1556p2398|1556p2400|1556p2500|dci1080p2398|dci1080p2400|dci1080p2500|2160p2398|2160p2400|2160p2500|2160p2997|2160p3000|2160p5000|2160p5994|2160p6000|dci2160p2398|dci2160p2400|dci2160p2500] @@ -90,6 +93,9 @@ 0 (0=not set) + + [custom name] + [file|url] [most ffmpeg arguments related to filtering and output codecs] From bbb78faaa13d746b13d4ccc9d64f8f23c771852d Mon Sep 17 00:00:00 2001 From: ianshade Date: Sat, 17 Nov 2018 08:53:20 +0100 Subject: [PATCH 08/13] Added default consumer numbering --- .../newtek/consumer/newtek_ndi_consumer.cpp | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp index f63fd3e47a..436569022f 100644 --- a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp +++ b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp @@ -44,6 +44,10 @@ namespace caspar { namespace newtek { struct newtek_ndi_consumer : public core::frame_consumer { + static int instances_; + int instance_no_; + std::wstring name_; + core::video_format_desc format_desc_; NDIlib_v3* ndi_lib_; NDIlib_send_instance_t ndi_send_instance_; @@ -52,11 +56,11 @@ struct newtek_ndi_consumer : public core::frame_consumer spl::shared_ptr graph_; caspar::timer tick_timer_; caspar::timer frame_timer_; - std::wstring name_; public: newtek_ndi_consumer(std::wstring name) : name_(name) + , instance_no_(instances_++) { if (!ndi::load_library()) { ndi::not_installed(); @@ -79,11 +83,13 @@ struct newtek_ndi_consumer : public core::frame_consumer ndi_lib_ = ndi::load_library(); NDIlib_send_create_t NDI_send_create_desc; - auto tmp_name = u8(name_); - NDI_send_create_desc.p_ndi_name = tmp_name.c_str(); - //NDI_send_create_desc.clock_video = false; - //NDI_send_create_desc.clock_audio = false; - ndi_send_instance_ = ndi_lib_->NDIlib_send_create(&NDI_send_create_desc); + if (name_.empty()) { + name_ = default_ndi_name(); + } + auto tmp_name = u8(name_); + NDI_send_create_desc.p_ndi_name = tmp_name.c_str(); + + ndi_send_instance_ = ndi_lib_->NDIlib_send_create(&NDI_send_create_desc); ndi_video_frame_.xres = format_desc.width; ndi_video_frame_.yres = format_desc.height; @@ -129,17 +135,24 @@ struct newtek_ndi_consumer : public core::frame_consumer std::wstring name() const override { return L"newtek-ndi"; } + std::wstring default_ndi_name() const + { + return L"CasparCG" + (instance_no_ ? L" " + boost::lexical_cast(instance_no_) : L""); + } + int index() const override { return 900; } bool has_synchronization_clock() const override { return false; } }; +int newtek_ndi_consumer::instances_ = 0; + spl::shared_ptr create_ndi_consumer(const std::vector& params, std::vector> channels) { if (params.size() < 1 || !boost::iequals(params.at(0), L"NEWTEK_NDI")) return core::frame_consumer::empty(); - std::wstring name = L"CasparCG"; + std::wstring name; if (contains_param(L"NAME", params)) { name = get_param(L"NAME", params); } @@ -150,8 +163,7 @@ spl::shared_ptr create_preconfigured_ndi_consumer(const boost::property_tree::wptree& ptree, std::vector> channels) { - std::wstring name = L"CasparCG"; - name = ptree.get(L"name", name); + auto name = ptree.get(L"name", L""); return spl::make_shared(name); } From 445c79dd1db288dc0911c104350fd6977646183a Mon Sep 17 00:00:00 2001 From: ianshade Date: Sat, 17 Nov 2018 12:29:17 +0100 Subject: [PATCH 09/13] Added optional interlaced support for NDI consumer --- .../newtek/consumer/newtek_ndi_consumer.cpp | 59 ++++++++++++++----- src/shell/casparcg.config | 3 +- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp index 436569022f..35f6430355 100644 --- a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp +++ b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp @@ -44,24 +44,33 @@ namespace caspar { namespace newtek { struct newtek_ndi_consumer : public core::frame_consumer { - static int instances_; - int instance_no_; - std::wstring name_; + static int instances_; + int instance_no_; + std::wstring name_; + bool allow_fields_; core::video_format_desc format_desc_; NDIlib_v3* ndi_lib_; NDIlib_send_instance_t ndi_send_instance_; NDIlib_video_frame_v2_t ndi_video_frame_; NDIlib_audio_frame_interleaved_32f_t ndi_audio_frame_; + std::shared_ptr field_data_; spl::shared_ptr graph_; caspar::timer tick_timer_; caspar::timer frame_timer_; + int frame_no_; public: - newtek_ndi_consumer(std::wstring name) + newtek_ndi_consumer(std::wstring name, bool allow_fields) : name_(name) , instance_no_(instances_++) + , frame_no_(0) + , allow_fields_(allow_fields) { + if (name_.empty()) { + name_ = default_ndi_name(); + } + if (!ndi::load_library()) { ndi::not_installed(); } @@ -73,7 +82,7 @@ struct newtek_ndi_consumer : public core::frame_consumer diagnostics::register_graph(graph_); } - ~newtek_ndi_consumer() {} + ~newtek_ndi_consumer() { ndi_lib_->NDIlib_send_destroy(ndi_send_instance_); } // frame_consumer @@ -83,10 +92,8 @@ struct newtek_ndi_consumer : public core::frame_consumer ndi_lib_ = ndi::load_library(); NDIlib_send_create_t NDI_send_create_desc; - if (name_.empty()) { - name_ = default_ndi_name(); - } - auto tmp_name = u8(name_); + + auto tmp_name = u8(name_); NDI_send_create_desc.p_ndi_name = tmp_name.c_str(); ndi_send_instance_ = ndi_lib_->NDIlib_send_create(&NDI_send_create_desc); @@ -97,6 +104,16 @@ struct newtek_ndi_consumer : public core::frame_consumer ndi_video_frame_.frame_rate_D = format_desc.framerate.denominator(); ndi_video_frame_.FourCC = NDIlib_FourCC_type_BGRA; ndi_video_frame_.line_stride_in_bytes = format_desc.width * 4; + ndi_video_frame_.frame_format_type = NDIlib_frame_format_type_progressive; + + if (format_desc.field_count == 2 && allow_fields_) { + ndi_video_frame_.yres /= 2; + ndi_video_frame_.frame_rate_N /= 2; + ndi_video_frame_.picture_aspect_ratio = format_desc.width*1.0f / format_desc.height; + field_data_.reset(new uint8_t[ndi_video_frame_.line_stride_in_bytes * ndi_video_frame_.yres], + std::default_delete()); + ndi_video_frame_.p_data = field_data_.get(); + } ndi_audio_frame_.sample_rate = format_desc_.audio_sample_rate; ndi_audio_frame_.no_channels = format_desc_.audio_channels; @@ -112,17 +129,25 @@ struct newtek_ndi_consumer : public core::frame_consumer graph_->set_value("tick-time", tick_timer_.elapsed() * format_desc_.fps * 0.5); tick_timer_.restart(); frame_timer_.restart(); - auto audio_data = frame.audio_data(); int audio_data_size = static_cast(audio_data.size()); ndi_audio_frame_.no_samples = audio_data_size / format_desc_.audio_channels; std::vector audio_buffer = ndi::audio_32_to_32f(audio_data.data(), audio_data_size); ndi_audio_frame_.p_data = const_cast(audio_buffer.data()); ndi_lib_->NDIlib_util_send_send_audio_interleaved_32f(ndi_send_instance_, &ndi_audio_frame_); - - ndi_video_frame_.p_data = const_cast(frame.image_data(0).begin()); + if (format_desc_.field_count == 2 && allow_fields_) { + ndi_video_frame_.frame_format_type = + (frame_no_ % 2 ? NDIlib_frame_format_type_field_1 : NDIlib_frame_format_type_field_0); + for (auto y = 0; y < ndi_video_frame_.yres; ++y) { + std::memcpy(reinterpret_cast(ndi_video_frame_.p_data) + y * format_desc_.width * 4, + frame.image_data(0).data() + (y * 2 + frame_no_ % 2) * format_desc_.width * 4, + format_desc_.width * 4); + } + } else { + ndi_video_frame_.p_data = const_cast(frame.image_data(0).begin()); + } ndi_lib_->NDIlib_send_send_video_v2(ndi_send_instance_, &ndi_video_frame_); - + frame_no_++; graph_->set_value("frame-time", frame_timer_.elapsed() * format_desc_.fps * 0.5); return make_ready_future(true); @@ -145,7 +170,7 @@ struct newtek_ndi_consumer : public core::frame_consumer bool has_synchronization_clock() const override { return false; } }; -int newtek_ndi_consumer::instances_ = 0; +int newtek_ndi_consumer::instances_ = 0; spl::shared_ptr create_ndi_consumer(const std::vector& params, std::vector> channels) @@ -156,7 +181,8 @@ spl::shared_ptr create_ndi_consumer(const std::vector(name); + bool allow_fields = contains_param(L"ALLOW_FIELDS", params); + return spl::make_shared(name, allow_fields); } spl::shared_ptr @@ -164,7 +190,8 @@ create_preconfigured_ndi_consumer(const boost::property_tree::wptree& std::vector> channels) { auto name = ptree.get(L"name", L""); - return spl::make_shared(name); + bool allow_fields = ptree.get(L"allow-fields", false); + return spl::make_shared(name, allow_fields); } }} // namespace caspar::newtek diff --git a/src/shell/casparcg.config b/src/shell/casparcg.config index c8e3812fda..e7cdb102ab 100644 --- a/src/shell/casparcg.config +++ b/src/shell/casparcg.config @@ -94,7 +94,8 @@ - [custom name] + [custom name] + false [true|false] [file|url] From 006d776a0bfeb72135104891717f71157146129e Mon Sep 17 00:00:00 2001 From: ianshade Date: Sat, 17 Nov 2018 12:33:30 +0100 Subject: [PATCH 10/13] Unified consumer and producer naming (newtek-ndi -> ndi) --- src/modules/newtek/consumer/newtek_ndi_consumer.cpp | 6 +++--- src/modules/newtek/producer/newtek_ndi_producer.cpp | 4 ++-- src/shell/casparcg.config | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp index 35f6430355..85f3aa3e54 100644 --- a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp +++ b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp @@ -155,10 +155,10 @@ struct newtek_ndi_consumer : public core::frame_consumer std::wstring print() const override { - return L"newtek-ndi[" + name_ + L"]"; + return L"ndi[" + name_ + L"]"; } // TODO: maybe put tally status in the name - std::wstring name() const override { return L"newtek-ndi"; } + std::wstring name() const override { return L"ndi"; } std::wstring default_ndi_name() const { @@ -175,7 +175,7 @@ int newtek_ndi_consumer::instances_ = 0; spl::shared_ptr create_ndi_consumer(const std::vector& params, std::vector> channels) { - if (params.size() < 1 || !boost::iequals(params.at(0), L"NEWTEK_NDI")) + if (params.size() < 1 || !boost::iequals(params.at(0), L"NDI")) return core::frame_consumer::empty(); std::wstring name; if (contains_param(L"NAME", params)) { diff --git a/src/modules/newtek/producer/newtek_ndi_producer.cpp b/src/modules/newtek/producer/newtek_ndi_producer.cpp index fcd7074adc..d003734dcd 100644 --- a/src/modules/newtek/producer/newtek_ndi_producer.cpp +++ b/src/modules/newtek/producer/newtek_ndi_producer.cpp @@ -125,10 +125,10 @@ struct newtek_ndi_producer : public core::frame_producer std::wstring print() const override { - return L"newtek-ndi[" + boost::lexical_cast(instance_no_) + L"|" + name_ + L"]"; + return L"ndi[" + boost::lexical_cast(instance_no_) + L"|" + name_ + L"]"; } - std::wstring name() const override { return L"newtek-ndi"; } + std::wstring name() const override { return L"ndi"; } core::draw_frame receive_impl(int hints) override { diff --git a/src/shell/casparcg.config b/src/shell/casparcg.config index e7cdb102ab..3d54d02220 100644 --- a/src/shell/casparcg.config +++ b/src/shell/casparcg.config @@ -93,10 +93,10 @@ 0 (0=not set) - + [custom name] false [true|false] - + [file|url] [most ffmpeg arguments related to filtering and output codecs] From 992914cdf329a7aed96ff17b3368fe68bca6c54c Mon Sep 17 00:00:00 2001 From: ianshade Date: Sat, 17 Nov 2018 13:08:24 +0100 Subject: [PATCH 11/13] Added low bandwidth mode for the producer; code formating --- .../newtek/consumer/newtek_ndi_consumer.cpp | 25 +++++++------------ .../newtek/producer/newtek_ndi_producer.cpp | 20 +++++++++------ 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp index 85f3aa3e54..84c392775f 100644 --- a/src/modules/newtek/consumer/newtek_ndi_consumer.cpp +++ b/src/modules/newtek/consumer/newtek_ndi_consumer.cpp @@ -44,10 +44,10 @@ namespace caspar { namespace newtek { struct newtek_ndi_consumer : public core::frame_consumer { - static int instances_; - int instance_no_; - std::wstring name_; - bool allow_fields_; + static int instances_; + const int instance_no_; + const std::wstring name_; + const bool allow_fields_; core::video_format_desc format_desc_; NDIlib_v3* ndi_lib_; @@ -62,15 +62,11 @@ struct newtek_ndi_consumer : public core::frame_consumer public: newtek_ndi_consumer(std::wstring name, bool allow_fields) - : name_(name) + : name_(!name.empty() ? name : default_ndi_name()) , instance_no_(instances_++) , frame_no_(0) , allow_fields_(allow_fields) { - if (name_.empty()) { - name_ = default_ndi_name(); - } - if (!ndi::load_library()) { ndi::not_installed(); } @@ -108,8 +104,8 @@ struct newtek_ndi_consumer : public core::frame_consumer if (format_desc.field_count == 2 && allow_fields_) { ndi_video_frame_.yres /= 2; - ndi_video_frame_.frame_rate_N /= 2; - ndi_video_frame_.picture_aspect_ratio = format_desc.width*1.0f / format_desc.height; + ndi_video_frame_.frame_rate_N /= 2; + ndi_video_frame_.picture_aspect_ratio = format_desc.width * 1.0f / format_desc.height; field_data_.reset(new uint8_t[ndi_video_frame_.line_stride_in_bytes * ndi_video_frame_.yres], std::default_delete()); ndi_video_frame_.p_data = field_data_.get(); @@ -153,10 +149,7 @@ struct newtek_ndi_consumer : public core::frame_consumer return make_ready_future(true); } - std::wstring print() const override - { - return L"ndi[" + name_ + L"]"; - } // TODO: maybe put tally status in the name + std::wstring print() const override { return L"ndi[" + name_ + L"]"; } // TODO: maybe put tally status in the name std::wstring name() const override { return L"ndi"; } @@ -189,7 +182,7 @@ spl::shared_ptr create_preconfigured_ndi_consumer(const boost::property_tree::wptree& ptree, std::vector> channels) { - auto name = ptree.get(L"name", L""); + auto name = ptree.get(L"name", L""); bool allow_fields = ptree.get(L"allow-fields", false); return spl::make_shared(name, allow_fields); } diff --git a/src/modules/newtek/producer/newtek_ndi_producer.cpp b/src/modules/newtek/producer/newtek_ndi_producer.cpp index d003734dcd..16bbc31482 100644 --- a/src/modules/newtek/producer/newtek_ndi_producer.cpp +++ b/src/modules/newtek/producer/newtek_ndi_producer.cpp @@ -67,8 +67,9 @@ namespace caspar { namespace newtek { struct newtek_ndi_producer : public core::frame_producer { static int instances_; - int instance_no_; + const int instance_no_; const std::wstring name_; + const bool low_bandwidth_; spl::shared_ptr frame_factory_; core::video_format_desc format_desc_; @@ -93,10 +94,12 @@ struct newtek_ndi_producer : public core::frame_producer public: explicit newtek_ndi_producer(spl::shared_ptr frame_factory, core::video_format_desc format_desc, - std::wstring name) + std::wstring name, + bool low_bandwidth) : format_desc_(format_desc) , frame_factory_(frame_factory) , name_(name) + , low_bandwidth_(low_bandwidth) , executor_(print()) , instance_no_(instances_++) , cadence_counter_(0) @@ -240,9 +243,9 @@ struct newtek_ndi_producer : public core::frame_producer NDIlib_recv_create_v3_t NDI_recv_create_desc; NDI_recv_create_desc.allow_video_fields = false; - NDI_recv_create_desc.bandwidth = NDIlib_recv_bandwidth_highest; - NDI_recv_create_desc.color_format = NDIlib_recv_color_format_BGRX_BGRA; - std::string src_name = u8(name_); + NDI_recv_create_desc.bandwidth = low_bandwidth_ ? NDIlib_recv_bandwidth_lowest : NDIlib_recv_bandwidth_highest; + NDI_recv_create_desc.color_format = NDIlib_recv_color_format_BGRX_BGRA; + std::string src_name = u8(name_); auto found_source = sources.find(src_name); if (found_source != sources.end()) { @@ -273,11 +276,12 @@ spl::shared_ptr create_ndi_producer(const core::frame_prod if (params.size() < 2 || !boost::iequals(params.at(0), "ndi")) return core::frame_producer::empty(); - auto name = params.at(1); + auto name = params.at(1); + bool low_bandwidth = contains_param(L"LOW_BANDWIDTH", params); try { - auto producer = - spl::make_shared(dependencies.frame_factory, dependencies.format_desc, name); + auto producer = spl::make_shared( + dependencies.frame_factory, dependencies.format_desc, name, low_bandwidth); return core::create_destroy_proxy(std::move(producer)); } catch (...) { CASPAR_LOG_CURRENT_EXCEPTION(); From 09f5f1cea388d88f9deace76191283c9de1663f9 Mon Sep 17 00:00:00 2001 From: ianshade Date: Sat, 17 Nov 2018 13:10:47 +0100 Subject: [PATCH 12/13] Unified consumer and producer naming (newtek-ndi -> ndi) --- src/modules/newtek/newtek.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/newtek/newtek.cpp b/src/modules/newtek/newtek.cpp index 7e21af1638..cacd78cd53 100644 --- a/src/modules/newtek/newtek.cpp +++ b/src/modules/newtek/newtek.cpp @@ -44,7 +44,7 @@ void init(core::module_dependencies dependencies) create_preconfigured_ivga_consumer); dependencies.consumer_registry->register_consumer_factory(L"NDI Consumer", create_ndi_consumer); - dependencies.consumer_registry->register_preconfigured_consumer_factory(L"newtek-ndi", + dependencies.consumer_registry->register_preconfigured_consumer_factory(L"ndi", create_preconfigured_ndi_consumer); dependencies.producer_registry->register_producer_factory(L"NDI Producer", create_ndi_producer); From 056c12a8cbd2c8ebd805f9095621a037689bac35 Mon Sep 17 00:00:00 2001 From: ianshade Date: Sat, 17 Nov 2018 13:43:32 +0100 Subject: [PATCH 13/13] code formating --- src/modules/newtek/consumer/newtek_ndi_consumer.h | 4 ++-- src/modules/newtek/producer/newtek_ndi_producer.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/newtek/consumer/newtek_ndi_consumer.h b/src/modules/newtek/consumer/newtek_ndi_consumer.h index 46a6f684d9..ed03d03559 100644 --- a/src/modules/newtek/consumer/newtek_ndi_consumer.h +++ b/src/modules/newtek/consumer/newtek_ndi_consumer.h @@ -33,9 +33,9 @@ namespace caspar { namespace newtek { spl::shared_ptr create_ndi_consumer(const std::vector& params, - std::vector> channels); + std::vector> channels); spl::shared_ptr create_preconfigured_ndi_consumer(const boost::property_tree::wptree& ptree, - std::vector> channels); + std::vector> channels); }} // namespace caspar::newtek diff --git a/src/modules/newtek/producer/newtek_ndi_producer.h b/src/modules/newtek/producer/newtek_ndi_producer.h index 977c77c1d9..cf9741e450 100644 --- a/src/modules/newtek/producer/newtek_ndi_producer.h +++ b/src/modules/newtek/producer/newtek_ndi_producer.h @@ -33,6 +33,6 @@ namespace caspar { namespace newtek { spl::shared_ptr create_ndi_producer(const core::frame_producer_dependencies& dependencies, - const std::vector& params); + const std::vector& params); }} // namespace caspar::newtek