@@ -12,10 +12,14 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/.
1212#include < godot_cpp/classes/engine.hpp>
1313#include < godot_cpp/classes/os.hpp>
1414#include < godot_cpp/classes/project_settings.hpp>
15+ #include < godot_cpp/classes/rd_texture_format.hpp>
16+ #include < godot_cpp/classes/rd_texture_view.hpp>
17+ #include < godot_cpp/classes/rendering_server.hpp>
1518
1619using namespace godot ;
1720
1821VideoStreamPlaybackNDI::VideoStreamPlaybackNDI () {
22+ rd = RenderingServer::get_singleton ()->get_rendering_device ();
1923 texture.instantiate ();
2024}
2125
@@ -26,6 +30,12 @@ VideoStreamPlaybackNDI::VideoStreamPlaybackNDI(NDIlib_recv_create_v3_t p_recv_de
2630
2731VideoStreamPlaybackNDI::~VideoStreamPlaybackNDI () {
2832 _stop ();
33+
34+ RID texture_rd_rid = texture->get_texture_rd_rid ();
35+ if (texture_rd_rid.is_valid ()) {
36+ rd->free_rid (texture_rd_rid);
37+ }
38+
2939 texture.unref ();
3040}
3141
@@ -99,10 +109,13 @@ Ref<Texture2D> VideoStreamPlaybackNDI::_get_texture() const {
99109void VideoStreamPlaybackNDI::_update (double p_delta) {
100110 ERR_FAIL_COND_MSG (recv == nullptr || sync == nullptr , " VideoStreamPlaybackNDI wasn't setup properly" );
101111
112+ // NDI framesync delivers frames instantly but first ones are 0x0
113+ // Since the VideoStreamPlayer used to only adjust its size for the first frame, this workaround was needed before 4.5
114+ // PR that fixed it: https://github.com/godotengine/godot/pull/103912
115+
102116 if (p_delta == 0 && (int )Engine::get_singleton ()->get_version_info ().get (" hex" , 0 ) < 0x040402 ) {
103117 // First frame is ticked with delta of 0
104- // Workaround for https://github.com/godotengine/godot/pull/103912, required for Godot versions before 4.4.2
105- render_first_frame ();
118+ wait_for_non_empty_frame ();
106119 } else {
107120 render_audio (p_delta);
108121 render_video ();
@@ -111,48 +124,77 @@ void VideoStreamPlaybackNDI::_update(double p_delta) {
111124
112125void VideoStreamPlaybackNDI::_bind_methods () {}
113126
114- void VideoStreamPlaybackNDI::render_first_frame () {
127+ void VideoStreamPlaybackNDI::update_texture (NDIlib_video_frame_v2_t p_video_frame) {
128+ Vector2i new_texture_size = Vector2i (p_video_frame.xres , p_video_frame.yres );
129+
130+ PackedByteArray data;
131+ data.resize (p_video_frame.line_stride_in_bytes * p_video_frame.yres );
132+ memcpy (data.ptrw (), p_video_frame.p_data , data.size ());
133+
134+ RID texture_rd_rid = texture->get_texture_rd_rid ();
135+
136+ if (new_texture_size == texture_size && texture_rd_rid.is_valid ()) {
137+ rd->texture_update (texture_rd_rid, 0 , data);
138+ } else {
139+ if (texture_rd_rid.is_valid ()) {
140+ rd->free_rid (texture_rd_rid);
141+ }
142+
143+ Ref<RDTextureFormat> tf;
144+ tf.instantiate ();
145+ tf->set_format (RenderingDevice::DATA_FORMAT_B8G8R8A8_UNORM);
146+ tf->set_width (new_texture_size.x );
147+ tf->set_height (new_texture_size.y );
148+ tf->set_usage_bits (RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT | RenderingDevice::TEXTURE_USAGE_CAN_UPDATE_BIT);
149+
150+ // tf->set_depth(1);
151+ // tf->set_array_layers(1);
152+ // tf->set_mipmaps(1);
153+ // tf->set_samples(RenderingDevice::TEXTURE_SAMPLES_1);
154+ // tf->set_texture_type(RenderingDevice::TEXTURE_TYPE_2D);
155+
156+ Ref<RDTextureView> tv;
157+ tv.instantiate ();
158+
159+ Array texture_data = Array ();
160+ texture_data.append (data);
161+
162+ texture_rd_rid = rd->texture_create (tf, tv, texture_data);
163+ tf.unref ();
164+ tv.unref ();
165+
166+ texture->set_texture_rd_rid (texture_rd_rid);
167+
168+ texture_size = new_texture_size;
169+ }
170+ }
171+
172+ void VideoStreamPlaybackNDI::wait_for_non_empty_frame () {
115173 for (size_t i = 0 ; i < 1000 ; i++) {
116- ndi->framesync_capture_video (sync, &video_frame, NDIlib_frame_format_type_progressive);
117- if (video_frame.xres != 0 && video_frame.yres != 0 ) {
118- texture->set_image (Image::create_empty (video_frame.xres , video_frame.yres , false , Image::FORMAT_RGBA8));
119- ndi->framesync_free_video (sync, &video_frame);
174+ if (render_video ()) {
120175 return ;
121176 }
122- ndi->framesync_free_video (sync, &video_frame);
123177 OS::get_singleton ()->delay_msec (10 );
124178 }
125179
126180 // Fallback resolution
127- texture->set_image (Image::create_empty (1920 , 1080 , false , Image::FORMAT_RGBA8));
128- ERR_FAIL_MSG (" NDI: Source not found at playback start. It will play at fallback resolution of 1920x1080 once discovered. See docs." );
181+ ERR_FAIL_MSG (" NDI: Source not found at playback start. It will play at fallback resolution of 1920x1080 once discovered. See docs for NDIOutput." );
129182}
130183
131- void VideoStreamPlaybackNDI::render_video () {
132- if (img.is_valid ()) {
133- if (texture->get_width () == img->get_width () && texture->get_height () == img->get_height ()) {
134- texture->update (img);
135- } else {
136- texture->set_image (img);
137- }
138- }
184+ bool VideoStreamPlaybackNDI::render_video () {
185+ bool updated = false ;
139186
187+ NDIlib_video_frame_v2_t video_frame;
140188 ndi->framesync_capture_video (sync, &video_frame, NDIlib_frame_format_type_progressive);
141189
142- if (video_frame.p_data != nullptr && (video_frame.FourCC == NDIlib_FourCC_type_BGRA || video_frame.FourCC == NDIlib_FourCC_type_BGRX)) {
143- video_buffer.resize (video_frame.xres * video_frame.yres * 4 );
144-
145- for (size_t i = 0 ; i < video_frame.xres * video_frame.yres ; i++) {
146- video_buffer.set (i * 4 + 0 , video_frame.p_data [i * 4 + 2 ]);
147- video_buffer.set (i * 4 + 1 , video_frame.p_data [i * 4 + 1 ]);
148- video_buffer.set (i * 4 + 2 , video_frame.p_data [i * 4 + 0 ]);
149- video_buffer.set (i * 4 + 3 , video_frame.p_data [i * 4 + 3 ]);
150- }
151-
152- img = Image::create_from_data (video_frame.xres , video_frame.yres , false , Image::Format::FORMAT_RGBA8, video_buffer);
190+ if (video_frame.p_data != nullptr && video_frame.xres != 0 && video_frame.yres != 0 && (video_frame.FourCC == NDIlib_FourCC_type_BGRA || video_frame.FourCC == NDIlib_FourCC_type_BGRX)) {
191+ update_texture (video_frame);
192+ updated = true ;
153193 }
154194
155195 ndi->framesync_free_video (sync, &video_frame);
196+
197+ return updated;
156198}
157199
158200void VideoStreamPlaybackNDI::render_audio (double p_delta) {
@@ -173,9 +215,14 @@ void VideoStreamPlaybackNDI::render_audio(double p_delta) {
173215 audio_buffer_interleaved.set (i, audio_buffer_planar[stride_index + stride_offset]);
174216 }
175217
176- int processed_samples = Math::min (audio_frame.no_samples , 8192 ); // FIXME: dont hardcode this
218+ // There is a maximum to how many samples can be submitted at once, because otherwise the buffer will overflow
219+ int processed_samples = Math::min (audio_frame.no_samples , 4096 ); // FIXME: dont hardcode this (https://github.com/unvermuthet/godot-ndi/issues/4)
177220 int skipped_samples = audio_frame.no_samples - processed_samples;
178221
222+ if (skipped_samples > 0 ) {
223+ print_verbose (" NDI: Skipped " + String::num_int64 (skipped_samples) + " audio samples!" );
224+ }
225+
179226 // Skip the older samples by playing the last ones in the array
180227 mix_audio (processed_samples, audio_buffer_interleaved, skipped_samples * _get_channels ());
181228 }
0 commit comments