├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── include ├── ndi_receiver.h ├── video_frame.h └── video_monitor.h └── src ├── main.cpp ├── ndi_receiver.cpp ├── video_frame.cpp └── video_monitor.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .cproject 2 | .project 3 | .settings 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 2 | LARCH := $(shell uname -m) 3 | CXX ?= g++ 4 | 5 | # path # 6 | SRC_PATH = src 7 | BUILD_PATH = build_$(LARCH) 8 | BIN_PATH = $(BUILD_PATH)/bin 9 | 10 | # executable # 11 | BIN_NAME = runner_$(LARCH) 12 | 13 | # extensions # 14 | SRC_EXT = cpp 15 | 16 | # code lists # 17 | # Find all source files in the source directory, sorted by 18 | # most recently modified 19 | SOURCES = $(shell find $(SRC_PATH) -name '*.$(SRC_EXT)' | sort -k 1nr | cut -f2-) 20 | # Set the object file names, with the source directory stripped 21 | # from the path, and the build path prepended in its place 22 | OBJECTS = $(SOURCES:$(SRC_PATH)/%.$(SRC_EXT)=$(BUILD_PATH)/%.o) 23 | # Set the dependency files that will be used to add header dependencies 24 | DEPS = $(OBJECTS:.o=.d) 25 | 26 | # flags # 27 | COMPILE_FLAGS = -Wall -Wextra -g 28 | INCLUDES = -I/opt/vc/include -I/opt/vc/include/interface/vmcs_host/ -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux -I include/ -I /usr/include -I /usr/include/libdrm -I /usr/local/include 29 | # Space-separated pkg-config libraries used by this project 30 | #LIBS = -lGLESv2 -lEGL -ldrm -lgbm -lndi -lpthread 31 | LIBS = -L/opt/vc/lib/ -lbcm_host -lvcos -lvchiq_arm -lpthread -lndi 32 | 33 | .PHONY: default_target 34 | default_target: release 35 | 36 | .PHONY: release 37 | release: export CXXFLAGS := $(CXXFLAGS) $(COMPILE_FLAGS) 38 | release: dirs 39 | @$(MAKE) all 40 | 41 | .PHONY: dirs 42 | dirs: 43 | @echo "Creating directories" 44 | @mkdir -p $(dir $(OBJECTS)) 45 | @mkdir -p $(BIN_PATH) 46 | 47 | .PHONY: clean 48 | clean: 49 | @echo "Deleting $(BIN_NAME) symlink" 50 | @$(RM) $(BIN_NAME) 51 | @echo "Deleting directories" 52 | @$(RM) -r $(BUILD_PATH) 53 | @$(RM) -r $(BIN_PATH) 54 | 55 | # checks the executable and symlinks to the output 56 | .PHONY: all 57 | all: $(BIN_PATH)/$(BIN_NAME) 58 | @echo "----------------------------------------------------------------" 59 | @echo "Making symlink: $(BIN_NAME) -> $<" 60 | @$(RM) $(BIN_NAME) 61 | @ln -s $(BIN_PATH)/$(BIN_NAME) $(BIN_NAME) 62 | @echo "----------------------------------------------------------------" 63 | 64 | # Creation of the executable 65 | $(BIN_PATH)/$(BIN_NAME): $(OBJECTS) 66 | @echo "Linking: $@" 67 | $(CXX) $(OBJECTS) -o $@ ${LIBS} 68 | 69 | # Add dependency files, if they exist 70 | -include $(DEPS) 71 | 72 | # Source file rules 73 | # After the first compilation they will be joined with the rules from the 74 | # dependency files to provide header dependencies 75 | $(BUILD_PATH)/%.o: $(SRC_PATH)/%.$(SRC_EXT) 76 | @echo "Compiling: $< -> $@" 77 | $(CXX) $(CXXFLAGS) $(INCLUDES) -MP -MMD -c $< -o $@ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RPI NDI monitor 2 | Newtek NDI monitor based on DispmanX. 3 | 4 | # Requires 5 | - Raspberry Pi 3 w/ Raspbian OS 6 | - Legacy GL Driver turned on 7 | 8 | # Issues 9 | - Although Newtek benchmark tool states Rpi capable to drive 1.84 streams of 1080p60 using 4 cores, library uses single core and no way found to run it multicore. On practice it leads to frame dropping (approx 4 of 5 frames are dropped) and giant latency (more that second). This disaster repeats whenever DispmanX or OpenGLES used to output frames and nothing promices Rpi3 be capable driving 1080p60 using NDI library v5. 10 | - Idea to capture fastest UYVY frames has failed, so code captures BGRX/BGRA and treats it as BGRX. 11 | 12 | # Building 13 | - make && ./runner_arm7l 14 | 15 | -------------------------------------------------------------------------------- /include/ndi_receiver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ndi_receiver.h 3 | * 4 | * Created on: 29 окт. 2021 г. 5 | * Author: rbalykov 6 | */ 7 | 8 | #ifndef SRC_NDI_RECEIVER_H_ 9 | #define SRC_NDI_RECEIVER_H_ 10 | 11 | #include 12 | #include 13 | #include "Processing.NDI.Advanced.h" 14 | 15 | class ndi_receiver 16 | { 17 | public: 18 | ndi_receiver(); 19 | virtual ~ndi_receiver(); 20 | 21 | void start(); 22 | void stop(); 23 | 24 | typedef NDIlib_video_frame_v2_t ndi_video_t; 25 | typedef NDIlib_audio_frame_v3_t ndi_audio_t; 26 | typedef NDIlib_find_instance_t ndi_find_t; 27 | typedef NDIlib_recv_create_v3_t ndi_recv_desc_t; 28 | typedef NDIlib_recv_instance_t ndi_recv_t; 29 | typedef NDIlib_framesync_instance_t ndi_fsync_t; 30 | typedef NDIlib_avsync_instance_t ndi_avsync_t; 31 | typedef NDIlib_source_t ndi_src_t; 32 | 33 | ndi_video_t* capture_video(); 34 | ndi_audio_t* capture_audio(); 35 | 36 | void free_video(ndi_video_t* vf); 37 | void free_audio(ndi_audio_t* af); 38 | 39 | private: 40 | void poll(); 41 | void pend_video(ndi_video_t* vf); 42 | void pend_audio(ndi_audio_t* af); 43 | 44 | void discover_any_source(); 45 | void disconnect(); 46 | void cleanup(); 47 | 48 | static void poll_thread (ndi_receiver &ndi); 49 | std::thread poller; 50 | 51 | ndi_find_t m_pfinder; 52 | ndi_recv_desc_t m_rx_desc; 53 | ndi_recv_t m_precv; 54 | ndi_avsync_t m_pavsync; 55 | 56 | ndi_video_t* m_pending_video; 57 | ndi_audio_t* m_pending_audio; 58 | 59 | uint32_t m_v_received, m_v_captured, m_v_dropped; 60 | uint32_t m_a_received, m_a_captured, m_a_dropped; 61 | std::chrono::high_resolution_clock::time_point m_last_data; 62 | 63 | bool m_run; 64 | bool m_quit; 65 | bool m_connected; 66 | 67 | std::mutex m_discovery_lock; 68 | std::mutex m_video_lock; 69 | std::mutex m_audio_lock; 70 | 71 | static uint32_t timeout_ms; 72 | 73 | }; 74 | 75 | #endif /* SRC_NDI_RECEIVER_H_ */ 76 | -------------------------------------------------------------------------------- /include/video_frame.h: -------------------------------------------------------------------------------- 1 | /* 2 | * video_frame.h 3 | * 4 | * Created on: 28 окт. 2021 г. 5 | * Author: rbalykov 6 | */ 7 | 8 | #ifndef SRC_VIDEO_FRAME_H_ 9 | #define SRC_VIDEO_FRAME_H_ 10 | 11 | #include 12 | #include 13 | #include "ndi_receiver.h" 14 | 15 | class video_monitor; 16 | class video_frame 17 | { 18 | public: 19 | video_frame(); 20 | virtual ~video_frame(); 21 | 22 | uint32_t w() { return m_w; }; 23 | uint32_t h() { return m_h; }; 24 | uint32_t macro_w() { return m_macro_w; }; 25 | uint32_t macro_h() { return m_macro_h; }; 26 | bool good() { return m_good; }; 27 | 28 | VC_DISPMANX_ALPHA_T* alpha() {return &m_alpha_desc; } 29 | 30 | protected: 31 | uint32_t m_w, m_h, m_macro_w, m_macro_h; 32 | bool m_good; 33 | 34 | VC_RECT_T m_src_rect; 35 | VC_RECT_T m_dst_rect; 36 | VC_DISPMANX_ALPHA_T m_alpha_desc; 37 | 38 | // DISPMANX_ELEMENT_HANDLE_T element; 39 | // void *image; 40 | 41 | }; 42 | 43 | class video_frame_uyvy: public video_frame 44 | { 45 | public: 46 | video_frame_uyvy (); 47 | video_frame_uyvy (video_monitor& m, uint32_t layer); 48 | video_frame_uyvy (const char* file, uint32_t w, uint32_t h); 49 | 50 | void capture_file (const char * file, uint32_t w, uint32_t h); 51 | void capture_ndi (ndi_receiver::ndi_video_t* vf); 52 | 53 | DISPMANX_RESOURCE_HANDLE_T pixels() { return m_pixels; }; 54 | DISPMANX_ELEMENT_HANDLE_T element() { return m_element; }; 55 | uint32_t layer() { return m_layer; }; 56 | 57 | private: 58 | DISPMANX_RESOURCE_HANDLE_T m_pixels; 59 | DISPMANX_ELEMENT_HANDLE_T m_element; 60 | 61 | uint32_t m_layer; 62 | uint32_t m_ptr_pixels; 63 | }; 64 | 65 | #endif /* SRC_VIDEO_FRAME_H_ */ 66 | -------------------------------------------------------------------------------- /include/video_monitor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * VideoMonitor.h 3 | * 4 | * Created on: 28 окт. 2021 г. 5 | * Author: rbalykov 6 | */ 7 | 8 | #ifndef SRC_VIDEO_MONITOR_H_ 9 | #define SRC_VIDEO_MONITOR_H_ 10 | 11 | #include "ndi_receiver.h" 12 | #include "video_frame.h" 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | class video_monitor 23 | { 24 | public: 25 | video_monitor(bool* run); 26 | virtual ~video_monitor(); 27 | 28 | int start(); 29 | 30 | DISPMANX_DISPLAY_HANDLE_T display() { return m_display; }; 31 | const DISPMANX_MODEINFO_T* mode() { return &m_modeinfo; }; 32 | DISPMANX_UPDATE_HANDLE_T update_handle() { return m_update; } 33 | 34 | private: 35 | static void vsync_callback(DISPMANX_UPDATE_HANDLE_T u, void* arg); 36 | 37 | void init(); 38 | void loop(); 39 | void cleanup(); 40 | void init_check(bool x, const char* message); 41 | 42 | static void render_thread (video_monitor &m); 43 | std::thread m_renderer; 44 | 45 | bool* m_run; 46 | bool m_quit; 47 | 48 | unsigned m_display_id; 49 | 50 | DISPMANX_DISPLAY_HANDLE_T m_display; 51 | DISPMANX_MODEINFO_T m_modeinfo; 52 | DISPMANX_UPDATE_HANDLE_T m_update; 53 | 54 | video_frame_uyvy* m_current_frame; 55 | ndi_receiver m_ndi; 56 | 57 | std::mutex m_sync_lock; 58 | 59 | 60 | class init_exception: public std::exception 61 | { 62 | public: 63 | explicit init_exception(const char *message) : msg_(message) {} 64 | explicit init_exception(const std::string &message) : msg_(message) {} 65 | virtual ~init_exception() noexcept {} 66 | virtual const char* what() const noexcept { return msg_.c_str(); } 67 | protected: 68 | std::string msg_; 69 | }; 70 | 71 | }; 72 | 73 | #endif /* SRC_VIDEO_MONITOR_H_ */ 74 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "video_monitor.h" 3 | #include 4 | #include 5 | #include 6 | 7 | static bool program_run = true; 8 | 9 | void sigterm_callback(int s) 10 | { 11 | (void) s; 12 | program_run = false; 13 | } 14 | 15 | int main (int argc, char ** argv) 16 | { 17 | (void) argc; 18 | (void) argv; 19 | 20 | // unsigned int n = std::thread::hardware_concurrency(); 21 | 22 | std::signal(SIGTERM, sigterm_callback); 23 | std::signal(SIGINT, sigterm_callback); 24 | 25 | video_monitor m(&program_run); 26 | return m.start(); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/ndi_receiver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ndi_receiver.cpp 3 | * 4 | * Created on: 29 окт. 2021 г. 5 | * Author: rbalykov 6 | */ 7 | 8 | #include "ndi_receiver.h" 9 | #include 10 | #include 11 | #include 12 | 13 | uint32_t ndi_receiver::timeout_ms = 5000; 14 | 15 | ndi_receiver::ndi_receiver() 16 | { 17 | m_pfinder = NULL; 18 | m_precv = NULL; 19 | m_pavsync = NULL; 20 | m_pending_video = NULL; 21 | m_pending_audio = NULL; 22 | 23 | m_run = false; 24 | m_connected = false; 25 | m_quit = false; 26 | 27 | // m_rx_desc.color_format = NDIlib_recv_color_format_fastest; 28 | m_rx_desc.color_format =NDIlib_recv_color_format_BGRX_BGRA; 29 | m_rx_desc.bandwidth = NDIlib_recv_bandwidth_highest; 30 | m_rx_desc.p_ndi_recv_name = "RPI monitor"; 31 | 32 | m_v_received = m_v_captured = m_v_dropped = 0; 33 | m_a_received = m_a_captured = m_a_dropped = 0; 34 | 35 | } 36 | 37 | ndi_receiver::~ndi_receiver() 38 | { 39 | std::cout << "Video frames received: " << m_v_received 40 | << ", captured: " << m_v_captured 41 | << ", dropped: " << m_v_dropped << std::endl; 42 | 43 | std::cout << "Audio frames received: " << m_a_received 44 | << ", captured: " << m_a_captured 45 | << ", dropped: " << m_a_dropped << std::endl; 46 | } 47 | 48 | void ndi_receiver::poll() 49 | { 50 | NDIlib_frame_type_e result; 51 | ndi_video_t* vf = new ndi_video_t; 52 | ndi_audio_t* af = new ndi_audio_t; 53 | std::chrono::high_resolution_clock::time_point time_now; 54 | 55 | if ((!vf) || (!af)) 56 | { 57 | std::cerr << "Out of memory. NDI polling stopped." << std::endl; 58 | if (vf) delete vf; 59 | if (af) delete af; 60 | stop(); 61 | return; 62 | } 63 | 64 | result = NDIlib_recv_capture_v3(m_precv, vf, af, NULL, timeout_ms); 65 | switch(result) 66 | { 67 | case NDIlib_frame_type_video: 68 | delete af; 69 | pend_video(vf); 70 | break; 71 | 72 | case NDIlib_frame_type_audio: 73 | delete vf; 74 | pend_audio(af); 75 | break; 76 | 77 | case NDIlib_frame_type_error: 78 | delete vf; 79 | delete af; 80 | std::cout << "NDI source disconnected, starting discovery." << std::endl; 81 | disconnect(); 82 | break; 83 | 84 | case NDIlib_frame_type_none: 85 | time_now = std::chrono::high_resolution_clock::now(); 86 | if (time_now - m_last_data > std::chrono::milliseconds(timeout_ms)) 87 | { 88 | std::cout << "NDI source timed out, starting discovery." << std::endl; 89 | disconnect(); 90 | } 91 | delete vf; 92 | delete af; 93 | break; 94 | 95 | case NDIlib_frame_type_metadata: 96 | case NDIlib_frame_type_status_change: 97 | default: 98 | delete vf; 99 | delete af; 100 | break; 101 | } 102 | } 103 | 104 | void ndi_receiver::poll_thread (ndi_receiver &ndi) 105 | { 106 | std::cout << "NDI polling started." << std::endl; 107 | if (!NDIlib_initialize()) 108 | { 109 | std::cerr << "Failed to initialize NDIlib." << std:: endl; 110 | ndi.m_run = false; 111 | } 112 | while(ndi.m_run) 113 | { 114 | if (!ndi.m_connected) 115 | { 116 | ndi.discover_any_source(); 117 | } 118 | else 119 | { 120 | ndi.poll(); 121 | } 122 | } 123 | 124 | ndi.cleanup(); 125 | std::cout << "NDI polling stopped." << std::endl; 126 | ndi.m_quit = true; 127 | } 128 | 129 | void ndi_receiver::discover_any_source() 130 | { 131 | uint32_t src_count; 132 | const ndi_src_t* m_psources; 133 | m_discovery_lock.lock(); 134 | 135 | if (m_connected) goto quit; 136 | 137 | if (!m_pfinder) 138 | { 139 | m_pfinder = NDIlib_find_create_v2(); 140 | if (!m_pfinder) 141 | { 142 | std::cerr << "Failed to create finder." << std:: endl; 143 | m_run = false; 144 | goto quit; 145 | } 146 | } 147 | m_psources = NDIlib_find_get_current_sources(m_pfinder, &src_count); 148 | if (!src_count) 149 | goto quit; 150 | 151 | m_precv = NDIlib_recv_create_v3(&m_rx_desc); 152 | if (!m_precv) 153 | { 154 | std::cerr << "Failed to create receiver." << std:: endl; 155 | m_run = false; 156 | goto quit; 157 | } 158 | std::cout << "NDI source found: " << m_psources[0].p_ndi_name << std::endl; 159 | NDIlib_recv_connect(m_precv, m_psources + 0); 160 | NDIlib_find_destroy(m_pfinder); 161 | m_pfinder = NULL; 162 | m_connected = true; 163 | 164 | quit: 165 | m_discovery_lock.unlock(); 166 | } 167 | 168 | void ndi_receiver::disconnect() 169 | { 170 | m_video_lock.lock(); 171 | if (m_pending_video) 172 | { 173 | NDIlib_recv_free_video_v2(m_precv, m_pending_video); 174 | delete m_pending_video; 175 | m_pending_video = NULL; 176 | m_v_dropped++; 177 | } 178 | m_video_lock.unlock(); 179 | 180 | m_audio_lock.lock(); 181 | if (m_pending_audio) 182 | { 183 | NDIlib_recv_free_audio_v3(m_precv, m_pending_audio); 184 | delete m_pending_audio; 185 | m_pending_audio = NULL; 186 | m_a_dropped++; 187 | } 188 | m_audio_lock.unlock(); 189 | 190 | m_discovery_lock.lock(); 191 | if (m_precv) 192 | { 193 | NDIlib_recv_destroy(m_precv); 194 | m_precv = NULL; 195 | m_connected = false; 196 | } 197 | m_discovery_lock.unlock(); 198 | } 199 | 200 | void ndi_receiver::cleanup() 201 | { 202 | disconnect(); 203 | 204 | if(m_pavsync) { NDIlib_avsync_destroy(m_pavsync); m_pavsync = NULL; } 205 | if(m_precv) { NDIlib_recv_destroy(m_precv); m_precv = NULL; } 206 | 207 | NDIlib_destroy(); 208 | } 209 | 210 | void ndi_receiver::start() 211 | { 212 | m_discovery_lock.lock(); 213 | if (m_run) goto quit; 214 | 215 | m_run = true; 216 | m_quit = false; 217 | poller = std::thread(poll_thread, std::ref(*this)); // @suppress("Symbol is not resolved") 218 | poller.detach(); 219 | 220 | quit: 221 | m_discovery_lock.unlock(); 222 | } 223 | 224 | void ndi_receiver::stop() 225 | { 226 | m_discovery_lock.lock(); 227 | m_run = false; 228 | m_connected = false; 229 | m_discovery_lock.unlock(); 230 | 231 | if (poller.joinable()) 232 | poller.join(); 233 | 234 | while(m_quit != true) 235 | { 236 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 237 | } 238 | } 239 | 240 | ndi_receiver::ndi_video_t* ndi_receiver::capture_video() 241 | { 242 | ndi_video_t* result; 243 | 244 | m_video_lock.lock(); 245 | if (m_pending_video == NULL) 246 | { 247 | result = NULL; 248 | } 249 | else 250 | { 251 | result = m_pending_video; 252 | m_pending_video = NULL; 253 | m_v_captured++; 254 | } 255 | m_video_lock.unlock(); 256 | 257 | return result; 258 | } 259 | 260 | ndi_receiver::ndi_audio_t* ndi_receiver::capture_audio() 261 | { 262 | ndi_audio_t* result; 263 | 264 | m_audio_lock.lock(); 265 | if (m_pending_audio == NULL) 266 | { 267 | result = NULL; 268 | } 269 | else 270 | { 271 | result = m_pending_audio; 272 | m_pending_audio = NULL; 273 | m_a_captured++; 274 | } 275 | m_audio_lock.unlock(); 276 | 277 | return result; 278 | } 279 | 280 | void ndi_receiver::pend_video(ndi_receiver::ndi_video_t* vf) 281 | { 282 | assert(vf); 283 | 284 | m_video_lock.lock(); 285 | m_v_received++; 286 | if (m_pending_video == NULL) 287 | { 288 | m_pending_video = vf; 289 | } 290 | else 291 | { 292 | NDIlib_recv_free_video_v2(m_precv, m_pending_video); 293 | delete m_pending_video; 294 | m_pending_video = vf; 295 | m_v_dropped++; 296 | } 297 | m_last_data = std::chrono::high_resolution_clock::now(); 298 | m_video_lock.unlock(); 299 | } 300 | 301 | void ndi_receiver::pend_audio(ndi_receiver::ndi_audio_t* af) 302 | { 303 | assert(af); 304 | 305 | m_audio_lock.lock(); 306 | m_a_received++; 307 | if (m_pending_audio == NULL) 308 | { 309 | m_pending_audio = af; 310 | } 311 | else 312 | { 313 | NDIlib_recv_free_audio_v3(m_precv, m_pending_audio); 314 | delete m_pending_audio; 315 | m_pending_audio = af; 316 | m_a_dropped++; 317 | } 318 | m_last_data = std::chrono::high_resolution_clock::now(); 319 | m_audio_lock.unlock(); 320 | } 321 | 322 | void ndi_receiver::free_video(ndi_receiver::ndi_video_t* vf) 323 | { 324 | assert(m_precv); 325 | assert(vf); 326 | 327 | m_video_lock.lock(); 328 | NDIlib_recv_free_video_v2(m_precv, vf); 329 | delete vf; 330 | m_video_lock.unlock(); 331 | } 332 | 333 | void ndi_receiver::free_audio(ndi_receiver::ndi_audio_t* af) 334 | { 335 | assert(m_precv); 336 | assert(af); 337 | 338 | m_audio_lock.lock(); 339 | NDIlib_recv_free_audio_v3(m_precv, af); 340 | delete af; 341 | m_audio_lock.unlock(); 342 | } 343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /src/video_frame.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * video_frame.cpp 3 | * 4 | * Created on: 28 окт. 2021 г. 5 | * Author: rbalykov 6 | */ 7 | 8 | #include "video_frame.h" 9 | #include "video_monitor.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | video_frame::video_frame() 20 | { 21 | m_w = m_h = m_macro_w = m_macro_h = 1; 22 | 23 | vc_dispmanx_rect_set(&m_src_rect, 0, 0, m_w << 16, m_h << 16); 24 | vc_dispmanx_rect_set(&m_dst_rect, 0, 0, m_w, m_h); 25 | 26 | m_alpha_desc.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS; 27 | m_alpha_desc.opacity = ~0; 28 | m_alpha_desc.mask = 0; 29 | 30 | m_good = false; 31 | } 32 | 33 | video_frame::~video_frame() 34 | { 35 | // TODO Auto-generated destructor stub 36 | } 37 | 38 | video_frame_uyvy::video_frame_uyvy() 39 | :video_frame() 40 | { 41 | m_pixels = DISPMANX_NO_HANDLE; 42 | m_ptr_pixels = DISPMANX_NO_HANDLE; 43 | m_layer = DISPMANX_NO_HANDLE; 44 | m_element = DISPMANX_NO_HANDLE; 45 | } 46 | 47 | video_frame_uyvy::video_frame_uyvy(video_monitor& m, uint32_t layer) 48 | :video_frame() 49 | { 50 | m_w = m.mode()->width; 51 | m_h = m.mode()->height; 52 | m_macro_w = (m_w + 1)/2; 53 | m_macro_h = m_h; 54 | 55 | vc_dispmanx_rect_set(&m_src_rect, 0, 0, m_w << 16, m_h << 16); 56 | vc_dispmanx_rect_set(&m_dst_rect, 0, 0, m_w, m_h); 57 | 58 | m_pixels = vc_dispmanx_resource_create( VC_IMAGE_YUV422UYVY, 59 | m_w, m_h, &m_ptr_pixels); 60 | m_element = vc_dispmanx_element_add(m.update_handle(), m.display(), 61 | m_layer, &m_dst_rect, m_pixels, 62 | &m_src_rect, DISPMANX_PROTECTION_NONE, 63 | &m_alpha_desc, 64 | NULL, DISPMANX_NO_ROTATE ); 65 | m_layer = layer; 66 | m_good = ((m_pixels != DISPMANX_NO_HANDLE) 67 | && (m_element != DISPMANX_NO_HANDLE)); 68 | } 69 | 70 | 71 | 72 | video_frame_uyvy::video_frame_uyvy(const char* file, uint32_t w, uint32_t h) 73 | :video_frame() 74 | { 75 | if (m_good) 76 | capture_file(file, w, h); 77 | } 78 | 79 | 80 | void video_frame_uyvy::capture_file(const char * file, uint32_t w, uint32_t h) 81 | { 82 | int result; 83 | 84 | m_h = h; m_w = w; 85 | m_macro_w = (m_w+1)/2; 86 | m_macro_h = m_h; 87 | 88 | if (m_pixels == DISPMANX_NO_HANDLE) 89 | { 90 | m_pixels = vc_dispmanx_resource_create( VC_IMAGE_YUV422UYVY, 91 | m_w, m_h, &m_ptr_pixels); 92 | } 93 | assert(m_pixels); 94 | 95 | std::ifstream input( file, std::ios::binary ); 96 | if (!input.good()) 97 | { 98 | std::cerr << "can't open file: " << file << std::endl; 99 | m_good = false; 100 | return; 101 | } 102 | 103 | std::vector buffer(std::istreambuf_iterator(input), {}); 104 | if (buffer.size() != m_macro_w * m_h * 4) 105 | { 106 | std::cerr << "wrong stub data for file " << file 107 | <<", expected "<< m_macro_w * m_h * 4 108 | << ", got " << buffer.size() << std::endl; // @suppress("Invalid overload") 109 | m_good = false; 110 | return; 111 | } 112 | input.close(); 113 | 114 | vc_dispmanx_rect_set( &m_dst_rect, 0, 0, m_w, m_h); 115 | result = vc_dispmanx_resource_write_data(m_pixels, VC_IMAGE_YUV422UYVY, 116 | m_macro_w * 4, buffer.data(), &m_dst_rect); 117 | 118 | m_good = (result == 0); 119 | } 120 | 121 | void video_frame_uyvy::capture_ndi(ndi_receiver::ndi_video_t* vf) 122 | { 123 | assert(vf); 124 | assert(vf->p_data); 125 | // assert(m_pixels); 126 | 127 | int result; 128 | VC_RECT_T frame_size; 129 | 130 | m_w = vf->xres; 131 | m_h = vf->yres; 132 | m_macro_w = vf->line_stride_in_bytes/4; 133 | m_macro_h = vf->yres; 134 | 135 | vc_dispmanx_rect_set( &frame_size, 0, 0, m_w, m_h); 136 | result = vc_dispmanx_resource_write_data(m_pixels, VC_IMAGE_YUV422UYVY, 137 | vf->line_stride_in_bytes, vf->p_data, &frame_size); 138 | 139 | 140 | m_good = (result == 0); 141 | } 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/video_monitor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * VideoMonitor.cpp 3 | * 4 | * Created on: 28 окт. 2021 г. 5 | * Author: rbalykov 6 | */ 7 | 8 | #include "video_monitor.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | static std::mutex m_render_lock; 15 | static std::condition_variable m_render_trigger; 16 | static bool m_render_flag; 17 | 18 | #undef PRINT_VSYNC_TIME 19 | 20 | video_monitor::video_monitor(bool* run) 21 | { 22 | assert(run); 23 | 24 | m_run = run; 25 | m_display_id = 0; 26 | m_display = 0; 27 | m_update = 0; 28 | 29 | m_current_frame = NULL; 30 | m_quit = false; 31 | m_render_flag = false; 32 | } 33 | 34 | video_monitor::~video_monitor() 35 | { 36 | // TODO Auto-generated destructor stub 37 | std::cout << "\nProgram quit." << std::endl; 38 | } 39 | 40 | void video_monitor::init_check(bool x, const char* message) 41 | { 42 | if (x) throw init_exception(message); 43 | } 44 | 45 | 46 | int video_monitor::start() 47 | { 48 | try 49 | { 50 | init(); 51 | } 52 | catch (const init_exception &e) 53 | { 54 | std::cerr << "Init failed: " << e.what() << '.' << std::endl; 55 | cleanup(); 56 | return -1; 57 | } 58 | std::cout << "Init done." << std::endl; 59 | 60 | m_renderer = std::thread(render_thread, std::ref(*this)); // @suppress("Symbol is not resolved") 61 | m_renderer.detach(); 62 | 63 | while(!m_quit) 64 | { 65 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 66 | } 67 | cleanup(); 68 | return 0; 69 | } 70 | 71 | void video_monitor::render_thread (video_monitor &m) 72 | { 73 | assert(m.m_display); 74 | 75 | DISPMANX_RESOURCE_HANDLE_T pixels = DISPMANX_NO_HANDLE; 76 | DISPMANX_ELEMENT_HANDLE_T element = DISPMANX_NO_HANDLE; 77 | DISPMANX_UPDATE_HANDLE_T update = DISPMANX_NO_HANDLE; 78 | VC_RECT_T frame_size, src_rect, dst_rect; 79 | uint32_t ptr_pixels; 80 | int result; 81 | VC_DISPMANX_ALPHA_T alpha = 82 | { DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, 255, 0 }; 83 | std::cout << "Render started." << std::endl; 84 | std::unique_lock lock(m_render_lock); 85 | 86 | pixels = vc_dispmanx_resource_create( VC_IMAGE_ARGB8888 87 | , 88 | 1920, 1080, &ptr_pixels); 89 | assert(pixels); 90 | 91 | update = vc_dispmanx_update_start( 10 ); 92 | assert(update); 93 | 94 | vc_dispmanx_rect_set( &src_rect, 0, 0, 1920 << 16, 1080 << 16 ); 95 | vc_dispmanx_rect_set( &dst_rect, 0, 0, m.m_modeinfo.width, m.m_modeinfo.height); 96 | 97 | element = vc_dispmanx_element_add(update, m.m_display, 100, 98 | &dst_rect, pixels, &src_rect, DISPMANX_PROTECTION_NONE, 99 | &alpha, 100 | NULL, // clamp 101 | DISPMANX_NO_ROTATE ); 102 | assert(element); 103 | 104 | while( *m.m_run ) 105 | { 106 | m_render_trigger.wait(lock, []{return m_render_flag;}); 107 | 108 | #ifdef PRINT_VSYNC_TIME 109 | using namespace std::chrono; 110 | static auto t1 = high_resolution_clock::now(); 111 | static auto t2 = high_resolution_clock::now(); 112 | static uint32_t i = 0; 113 | #endif 114 | ndi_receiver::ndi_video_t* vf = m.m_ndi.capture_video(); 115 | if (vf && vf->p_data) 116 | { 117 | vc_dispmanx_rect_set( &frame_size, 0, 0, vf->xres, vf->yres); 118 | result = vc_dispmanx_resource_write_data(pixels, VC_IMAGE_XRGB8888, 119 | vf->line_stride_in_bytes, vf->p_data, &frame_size); 120 | assert(result == 0); 121 | m.m_ndi.free_video(vf); 122 | 123 | vc_dispmanx_update_submit_sync( update ); 124 | 125 | } 126 | #ifdef PRINT_VSYNC_TIME 127 | if (i) 128 | { 129 | t2 = high_resolution_clock::now(); 130 | nanoseconds d = (t2 - t1); 131 | std::cerr << d.count() << std::endl; 132 | } 133 | t1 = t2; 134 | i++; 135 | #endif 136 | m_render_flag = false; 137 | } 138 | 139 | std::cout << "Render stopped." << std::endl; 140 | m.m_quit = true; 141 | } 142 | 143 | 144 | void video_monitor::vsync_callback(DISPMANX_UPDATE_HANDLE_T u, void* arg) 145 | { 146 | (void) u; 147 | (void) arg; 148 | 149 | { 150 | std::lock_guard lock(m_render_lock); 151 | m_render_flag = true; 152 | m_render_trigger.notify_one(); 153 | } 154 | } 155 | 156 | void video_monitor::init() 157 | { 158 | int result; 159 | 160 | m_current_frame = new video_frame_uyvy; 161 | 162 | bcm_host_init(); 163 | m_display = vc_dispmanx_display_open(m_display_id); 164 | init_check( m_display == DISPMANX_NO_HANDLE ,"can't open display"); 165 | 166 | result = vc_dispmanx_display_get_info(m_display, &m_modeinfo); 167 | init_check( result != 0, "can't get display info"); 168 | 169 | m_current_frame = new video_frame_uyvy(*this, 100); 170 | init_check( m_current_frame->good() != true, "can't create video frame"); 171 | 172 | vc_dispmanx_vsync_callback(m_display, vsync_callback, this); 173 | (void) result; 174 | 175 | m_ndi.start(); 176 | } 177 | 178 | void video_monitor::loop() 179 | {} 180 | 181 | void video_monitor::cleanup() 182 | { 183 | m_ndi.stop(); 184 | vc_dispmanx_vsync_callback(m_display, NULL, NULL); 185 | vc_dispmanx_display_close(m_display); 186 | bcm_host_deinit(); 187 | } 188 | --------------------------------------------------------------------------------