CoconutFred
CoconutFred

Reputation: 454

ffmpeg corruption when piping input from stdin

I have a program that generates images and creates a video out of them. Currently what works is creating all the images at once, then running FFmpeg in a subprocess and piping the images through stdin to create a video:

cmd = ['ffmpeg', '-y',
       '-s', '{}x{}'.format(OUTPUT_WIDTH, OUTPUT_HEIGHT),
       '-r', str(OUTPUT_VIDEO_FPS),
       '-an',
       '-pix_fmt', colour,
       '-c:v', 'rawvideo', '-f', 'rawvideo',
       '-i', '-',
       '-vcodec', 'libx264',
       '-pix_fmt', 'yuv420p',
       '-preset', 'medium', OUTPUT_VIDEO_PATH]

out_frames = []
for i in range(num_frames):
    out_frame = render_frame(...)
    out_frames.append(out_frame)

with _call_subprocess(sp.Popen(cmd, stdin=sp.PIPE, stderr=sp.PIPE, stdout=DEVNULL)) as pipe:
    for frame_no, frame in enumerate(out_frames):
        pipe.stdin.write(frame)

However, this becomes infeasible when I have thousands of images that don't all fit in memory, since the subprocess fork call requests too much memory and fails. My solution is to fork at the beginning of the program (avoiding the memory error), then pipe the frames to stdin as they are created:

cmd = ['ffmpeg', '-y',
       '-s', '{}x{}'.format(OUTPUT_WIDTH, OUTPUT_HEIGHT),
       '-r', str(OUTPUT_VIDEO_FPS),
       '-an',
       '-pix_fmt', colour,
       '-c:v', 'rawvideo', '-f', 'rawvideo',
       '-i', '-',
       '-vcodec', 'libx264',
       '-pix_fmt', 'yuv420p',
       '-preset', 'medium', OUTPUT_VIDEO_PATH]

with _call_subprocess(sp.Popen(cmd, stdin=sp.PIPE, stderr=sp.PIPE, stdout=DEVNULL)) as pipe:
    for i in range(num_frames):
        out_frame = render_frame(...)
        pipe.stdin.write(out_frame)

However, the output of ffmpeg now becomes corrupted. I'm pretty sure this has something to do with the fact that I now have some processing time between writes to stdin when I'm rendering a frame - I noticed that if I use the first solution, but simply add some sleep time between writes to stdin, the output is also corrupted!

cmd = ['ffmpeg', '-y',
       '-s', '{}x{}'.format(OUTPUT_WIDTH, OUTPUT_HEIGHT),
       '-r', str(OUTPUT_VIDEO_FPS),
       '-an',
       '-pix_fmt', colour,
       '-c:v', 'rawvideo', '-f', 'rawvideo',
       '-i', '-',
       '-vcodec', 'libx264',
       '-pix_fmt', 'yuv420p',
       '-preset', 'medium', OUTPUT_VIDEO_PATH]

out_frames = []
for i in range(num_frames):
    out_frame = render_frame(...)
    out_frames.append(out_frame)

with _call_subprocess(sp.Popen(cmd, stdin=sp.PIPE, stderr=sp.PIPE, stdout=DEVNULL)) as pipe:
    for frame_no, frame in enumerate(out_frames):
        time.sleep(1) # <------------------- This sleep ruins everything!!
        pipe.stdin.write(frame)

However I'm not sure what is even causing this or how to fix it (is FFmpeg somehow polling an empty pipe and then being corrupted from that? I don't understand how the subprocess communication even works...). Any help would be appreciated.

Upvotes: 4

Views: 2421

Answers (1)

CoconutFred
CoconutFred

Reputation: 454

Not sure why this works, but changing the sp.Popen call to run FFmpeg from sp.Popen(cmd, stdin=sp.PIPE, stderr=sp.PIPE, stdout=DEVNULL) to sp.Popen(cmd, stdin=sp.PIPE, stderr=DEVNULL, stdout=DEVNULL) worked. I'm guessing this has something to do with this SO question about piping issues with stderr.

Upvotes: 1

Related Questions