1

I have a nodejs program which generates raw (rgb24) image(s), which I then pipe into ffmpeg so it saves as png or mp4. My code looks like this:

const fs = require("fs");
// ...
const outputBuffer = Buffer.alloc(outputPngWidth * 3 * outputPngHeight);
// ... write data into outputBuffer
fs.writeSync(process.stdout.fd, outputBuffer);

I then do the following in CLI:

node generate | ffmpeg -f rawvideo -pixel_format rgb24 -video_size 1000x1000 -i - test.png

Alternatively, if I generate lots of images from my program, I do this to generate the video file:

node generate | ffmpeg -f rawvideo -pixel_format rgb24 -video_size 1000x1000 -r 60 -i - -codec:v libx265 test.mp4

On windows this works flawlessly. On linux (either on Ubuntu 20 VM, or Ubuntu 20 installed directly on a physical machine), it consistently fails with:

pipe:: corrupt input packet in stream 0
[rawvideo @ 0x55f5256c8040] Invalid buffer size, packet size 65536 < expected frame_size 3000000
Error while decoding stream #0:0: Invalid argument

If I split this in 2 phases like so, then it works perfectly on linux as well:

node generate > test.raw
cat test.raw | ffmpeg -f rawvideo -pixel_format rgb24 -video_size 1000x1000 -i - test.png

By looking at the error "packet size 65536 < expected frame_size 3000000" it seems that node's fs.writeSync only sends 65536 bytes at a time, but ffmpeg expects 3000000 bytes (that is 1000 width * 1000 height * 3 channels).

If I reduce my image size to something small, e.g 50x50 or 100x100, then it works. As soon as x * y * 3 exceeds 65536, it fails (eg. 160x160 fails with "packet size 65536 < expected frame_size 76800" because 160 * 160 * 3 = 76800).

What I've tried so far to solve the issue without luck:

  • Force node to spit out the whole buffer at once:
fs.writeSync(process.stdout.fd, outputBuffer, 0, outputBuffer.length);

Is there a way to overcome this?

cherouvim
  • 31,725
  • 15
  • 104
  • 153
  • What about `node generate | cat | ffmpeg -f rawvideo -pixel_format rgb24 -video_size 1000x1000 -i - test.png`? – DaBler Feb 05 '21 at 10:48
  • @DaBler: Thanks. It has the exact same behavior: succeeds for 100x100, fails for 160x160 with the same message. – cherouvim Feb 05 '21 at 11:18
  • Also `node generate | pv --buffer-size 1G | ffmpeg -f rawvideo -pixel_format rgb24 -video_size 1000x1000 -i - test.png` could work. – DaBler Feb 05 '21 at 11:24
  • @DaBler: Same behavior. I had already tried that, together with all other solutions listed in https://stackoverflow.com/q/8554568/72478 – cherouvim Feb 05 '21 at 11:36
  • Last attempt: `stdbuf -o1G node generate | ffmpeg -f rawvideo -pixel_format rgb24 -video_size 1000x1000 -i - test.png`. – DaBler Feb 05 '21 at 11:45
  • @DaBler: Same behavior. Thanks for the effort. Maybe it's something else, but all hints I have so far point me to think that node is spitting the data in chunks of 64KB at a time. – cherouvim Feb 05 '21 at 11:58
  • Yet such a shot in the dark: I would also try to add `-packetsize 3000000` on the ffmpeg command line. – DaBler Feb 05 '21 at 12:34
  • @DaBler: Thanks again. It's still the same. – cherouvim Feb 05 '21 at 12:38
  • I tried to feed ffmpeg using 65536-byte buffers, and everything works fine. Which version of ffmpeg are you using? Version 4.3.1 works fine on my side. [Here](https://godbolt.org/z/o3KMdd) is the program used. This also leads me to suspect that the node may cut the output after 64 KiB for some reason. – DaBler Feb 06 '21 at 13:49
  • For me it fails on `4.2.4-1ubuntu0.1` and, yes, it's probably caused by node. On https://nodejs.org/api/process.html#process_a_note_on_process_i_o it says "Pipes (and sockets): synchronous on Windows, asynchronous on POSIX" which may play a role. – cherouvim Feb 06 '21 at 14:10
  • Quoting your reference, the problem could be that the output is "_not written at all if process.exit() is called before an asynchronous write completes._" Can you add waiting for I/O operations to complete at the end of your node script? – DaBler Feb 06 '21 at 14:27
  • @DaBler: I believe all the data wants to be written. That's why redirecting to a file, sends all that data to the file. The problem must be linux's pipe buffer size: https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer which I've confirmed that it's exactly 65536 on my VM. – cherouvim Feb 06 '21 at 16:22
  • Just use the file system as the buffer. Chain it together as one command. node generate > test.raw && cat test.raw | ffmpeg -f rawvideo -pixel_format rgb24 -video_size 1000x1000 -i - test.png Definitely not what you were asking for but if it were me I would do this and move on with other problems. – Nicholas Rees Feb 09 '21 at 14:07
  • @NicholasRees: my other problem would then be that I'd need a 3TB disk, which I do not have. That's why I need to do it on the fly. For context: I'm rendering 25.920.000 8K frames into a single MP4 file. – cherouvim Feb 09 '21 at 15:32
  • How many are you going to do simultaneously? I would think it should only be the same number as the number of CPU cores you have, in which case you’d only need to keep max... 16? 32? 128? .raw files around. Can’t imagine how you would have 3TB of temp files unless these are 1TB .raw files. If the performance of writing to a file system is an issue, then creating a ram disk should solve that. – Nicholas Rees Feb 10 '21 at 01:43

1 Answers1

0

This is a stab in the dark and I know the following might sound outlandish.

Seeing the error message from the tests, it seems that ffmpeg times out while it is waiting for sufficient data and the generator does not fill the pipe fast enough at the same time.

I asked myself how ffmpeg could obviously timeout listening to STDIN although it - hopefully - would use a blocking read call? Well, perhaps it does not make a blocking call to read but calls select on the file descriptor with a short timeout instead and then just runs into that timeout while waiting for sufficient data.

A neat explanation how this could happen is if the system is kept busy either with generating data or with consuming the data, i.e. the counterpart is always idle.

Since you say that it is a VM that is running all of this, I could imagine that the VM is configured to have only a single CPU available. It can then execute only one of the two processes at any given time, the processes compete for the CPU.

I can see this being supported by the other test you ran: you created the test data first and stored it in a file. Then you let ffmpeg consume it. Each process can use the available CPU full time. no competition.

Also a multi-CPU or multi-Hyperthreading system could expose this behaviour if it is just kept busy enough with other processes that have higher priority.

Bottom line: I would make sure that my VM has enough processing power for both processes.

Thomas
  • 41
  • 2
  • Thanks for the answer. Interesting thought. Unfortunately my config is `vb.memory = "4096"` and `vb.cpus = 4`. In addition I'm consistently reproducing the issue on Ubuntu 20 installed directly on metal running on a ryzen 5. So it can't be related to hardware limitations. – cherouvim Feb 10 '21 at 04:58