leadedge / ofxNDI

An Openframeworks addon to allow sending and receiving images over a network using the NewTek Network Device Protocol.
GNU General Public License v3.0
134 stars 48 forks source link

Locked FPS? #53

Open PeteHaughie opened 2 weeks ago

PeteHaughie commented 2 weeks ago

No matter what I do I can't seem to get over 30fps between my sender and receiver. My sender can happily chunder through HD video at 90fps but even on the same machine I can't get the NDI receiver to pass 30fps.

Is the FPS locked to 30 and if not what can I do to bump this up to something more reasonable like 60?

leadedge commented 2 weeks ago

What sender and receiver are you referring to?

The NDI frame rate is controlled by timing. The example sender starts with NDI fps at 30 and the receiver shows the NDI received rate (1920x1080/30 fps) as well as the rendering rate which should be the monitor refresh rate of 60fps. If you change the sender NDI fps with the 'F' key you should see the corresponding rate in the receiver.

The sender fps can be changed. The key function is "SetFrameRate(double fps)".

The Windows sender example, includes an example option to set the frame rate to 30 fps.

The Openframeworks example has the same option but it is not activated.

The default is 60fps.

If you still have trouble I would have to see your source code.

PeteHaughie commented 2 weeks ago

Thanks for the response.

I'm talking in particular about the example-sender-receiver in the repository.

My receiver says that it's being sent a 60fps stream and the sender application says the same but I'm also being told that it's only outputting 20fps.

Screenshot 2024-08-28 at 00 14 58 Screenshot 2024-08-28 at 00 15 08

The receiver I've built is the default one with #define BUILDRECEIVER enabled although I have changed it so that It's fullscreen by default.

leadedge commented 2 weeks ago

Try the receiver version of the example with the sender to see if you get the same result. It shows both the NDI and receiving rate.

RGBA/YUV sending makes a significant difference. Set it to YUV.

"Readback" has less effect but should be left enabled. "Async" frees the sender/receiver from locking sync together. It does no affect fps a great deal but try it on or off.

Otherwise it could be a system setting. Nvidia has "Max frame rate".

Edit: I realised that you are already using the example receiver source. Is the "PETES-PRO (Shader output)" sender also built from the example source? If you have both example sender and example receiver working together I have something to compare with.

Somehow the render rate is being slowed, but I can't reproduce it. I can achieve 60pfs at 4K with YUV, Readback and Async all enabled.

leadedge commented 1 week ago

I can reproduce the symptom to some degree by setting readback off (the "P" key in the example sender). In the source code of your sender, make sure that you enable it -

ndiSender.SetReadback(true);

The YUV option has the most significant effect. Before creating the sender -

ndiSender.SetFormat(NDIlib_FourCC_video_type_UYVY);

There is also a timing utility function that will reveal the bottleneck. It's in a namespace -

ofxNDIutils::StartTiming();
double elapsed = ofxNDIutils::EndTiming();

I can't suggest any more without further information.

PeteHaughie commented 2 days ago

I think I can also replicate it by changing the bReadback value. I'll dig into the example and see what I've not implemented properly. Thank you for the help!

PeteHaughie commented 2 days ago

I'm really confused as to why I would be getting the current output.

This is my source:

Screenshot 2024-09-12 at 19 43 20

This is the receiver bReadback set to true:

Screenshot 2024-09-12 at 19 45 23

Notice I get a solid 20fps

This is the receiver bReadback set to false:

Screenshot 2024-09-12 at 17 35 48

The fps is now a fantastic 60fps but something has gone really awry with the image.

I'm sending the fbo directly, the ndiSender image format is set to UYVY and it only does this when I hit the P key as per the example.

leadedge commented 2 days ago

When "SetReadback" is activated for the sender, fbo texture data is read using two OpenGL pbos and this seems to be working OK, apart from the slow fps.

Otherwise fbo data copy is by way of "glReadPixels" and I am thinking that this could be a data alignment problem. The 4 identical sections horizontally give some hint. I am not sure why the bottom section is OK though.

I will see what I can find.

Meanwhile, do you see the same data corruption in the NDI "Studio monitor" receiver application?

Edit - A quick test. In your sender, could you disable ndiSender.SetFormat(NDIlib_FourCC_video_type_UYVY); Then glReadPixels is not used. Is the image still corrupted?.

PeteHaughie commented 1 day ago
Screenshot 2024-09-13 at 16 53 47

As you can see it's still corrupted when I turn async off even though the format is not set as per your suggestion.

Screenshot 2024-09-13 at 16 59 45

However if I'm not mistaken does the 1080/60p in the title bar not suggest that it's still being received at 60fps in the video monitor?

leadedge commented 1 day ago

NDI Studio Monitor shows that the sender output is OK, so the corruption is not originating on the sender side.

To clarify the difference between between "readback" and "async".

"SetReadback" applies only for a sender. Data transfer from the GPU texture to CPU pixels is optimized using two pbos. This process is asynchronous in that two pbos are used in succession, but it is applied locally to the sender.

"Async" or the "SetAsync" function refers to an NDI sending method. By default the sender and receiver are locked together at a rate specified by the sender and the receiver receives at that rate. If "Async" is applied, the sender is freed from this constraint and sends frames as they are created and the receiver receives frames as they arrive. But this does not explain the data corruption that you observe in the receiver.

I will examine how such corruption could occur, but without being able to reproduce the problem, my chances of finding the cause are not great. Meanwhile, if you can bypass it then do so. As for the frame rate, I am unable to reproduce that either.

I suggest to use the timing functions -

ofxNDIutils::StartTiming();
double elapsed = ofxNDIutils::EndTiming();

Starting before and ending after the complete render cycle, what is the frame time? Then narrow it down.

As for the NDI Studio Monitor frame rate display. It is simply the rate that the sender specifies, not the actual received rate.

EDIT - This NDI tool shows the actual received frame rate .. https://ndi.video/tools/ndi-analysis/

PeteHaughie commented 20 hours ago

Again, thanks for even taking the time to help me solve this problem.

I had to edit the ofxNDIutils.h and ofxNDIutils.cpp slightly (defining USE_CHRONOS and changing UINT to use the cstdint std::uint32_t) to get them to run under Mac OS but I'm getting frame time values of between 6.32 and 18.255 for a mean of 11.327 which feels nice and clippy to me. As I say I'm locking my app to 60fps and the output rock solid.

If I derestrict the fps I get 75fps and a range of 6.28, 18.547 and a mean of 10.675 so they're very comparable and I would say both are probably within acceptable margins of error.

PeteHaughie commented 20 hours ago

I'm wondering if it's a size reporting mismatch.

Everything I'm doing with FBOs is set using the senderHeight and senderWidth variables but my screen is considerably larger. I wanted to focus on HD output because my target hardware is lower-end living room TVs and so on. I'll worry about higher definitions later.

It is definitely only an issue when I change the bReadback value from false to true.

Sending updated dimensions to the receiver achieved nothing unfortunately.

Then I remembered that the NDI Monitor app also showed the same distortions so it probably can't be that.

leadedge commented 2 hours ago

It's a good thing to catch the definition problems in ofxNDIutils. I will fix those.

Sending an fbo uses the dimensions of the texture attached to it. If the fbo is allocated to 1920x1080 in the first place, there should be no problem, but if the dimensions of the fbo/texture are not 1920x1080, there will be a size mis-match.

Also I am still not sure whether you can successfully build and run the example-sender-receiver as a sender as well as a receiver. If these work together OK, you can experiment by changing the sender options.

If NDI Studio Monitor shows the same distortions, it isolates the problem to the sender. If you can duplicate this distortion with the example sender code with the rotating cube, it gives me something to work on.

The frame timing is well within 60 fps. Does the timing include the ofxNDI SendImage function? You can also use the NDI Analysis tool to find out the received timing.