gst-launch transcoding an AVI to MP4 hangs forever with a 0-byte file — x264’s lookahead fills the default audio queue

A one-line gst-launch-1.0 pipeline to turn a 31-second AVI into an MP4 ran for 17 minutes, then another 10, and both times left a 0-byte output file. No error, no warning, CPU mostly idle. With one change to the queues it finished in 3 seconds.

TL;DR — When avidemux feeds a video branch through x264enc and an audio branch through an AAC encoder into mp4mux, the muxer waits for the first video buffer, x264 holds ~50 frames of lookahead before producing one, the audio queue hits its default 1-second cap, and the demuxer blocks. Give the branch queues max-size-buffers=0 max-size-bytes=0 max-size-time=0.

The setup

Godot’s Movie Maker mode records gameplay to an AVI: MJPEG frames with uncompressed PCM audio in the same file (there is no separate .wav unless you pick the PNG writer). The store wants H.264 + AAC in MP4. This machine has no ffmpeg, but GStreamer with x264enc, voaacenc and mp4mux is installed, so the natural pipeline is one demuxer with two branches meeting in the muxer:

gst-launch-1.0 -e filesrc location=promo.avi ! avidemux name=d \
  d.video_0 ! queue ! jpegdec ! videoconvert ! videoscale \
    ! video/x-raw,width=720,height=1280 ! x264enc bitrate=3500 ! h264parse ! mux. \
  d.audio_0 ! queue ! audioconvert ! audioresample ! voaacenc ! aacparse ! mux. \
  mp4mux name=mux ! filesink location=promo.mp4

The input was 934 frames at 30 fps, 1080×1856 MJPEG, 108 MB.

What I expected

A minute of x264 at 720p, then a playable file. Every element is standard, the graph is the textbook « demux → encode both → mux » shape, and the same layout with a WAV file as a second filesrc had failed only because the WAV did not exist.

What actually happens

The pipeline goes to PLAYING and stays there. filesink never receives a buffer, so the output stays at 0 bytes. There is no message on stderr with -q and nothing useful without it. I let it run 17 minutes the first time and 10 minutes the second, before killing it; pgrep showed the process alive with a few percent CPU.

Data-flow diagram of the deadlock: avidemux feeds a video queue into jpegdec and x264enc, and an audio queue into the AAC encoder; both meet in mp4mux. The audio queue is marked full at its 1-second default, the muxer waits for the first video buffer, and x264enc is still holding about 50 frames of lookahead.

The fix

gst-launch-1.0 -e -q filesrc location=promo.avi ! avidemux name=d \
  d.video_0 ! queue max-size-buffers=0 max-size-bytes=0 max-size-time=0 \
    ! jpegdec ! videoconvert ! videoscale ! video/x-raw,width=720,height=1280 \
    ! x264enc bitrate=3500 speed-preset=faster key-int-max=60 ! h264parse ! mux. \
  d.audio_0 ! queue max-size-buffers=0 max-size-bytes=0 max-size-time=0 \
    ! audioconvert ! audioresample ! voaacenc bitrate=128000 ! aacparse ! mux. \
  mp4mux name=mux ! filesink location=promo.mp4

Result on the same input: 3.1 s wall-clock (30 s CPU across cores), an 11.5 MB MP4, 720×1280, 31.1 s, with both an avc1 video track and an mp4a audio track. Setting the three max-size-* properties to 0 makes the queue unbounded; for a 31-second clip that is a few megabytes of PCM at worst.

Why it works

mp4mux interleaves tracks, so it will not write anything until it has data on every sink pad — and it waits on the pad whose timestamps are behind, which at the start is the video pad. x264enc with a normal preset keeps a lookahead window (around 40–60 frames at medium) and emits nothing until that window is full. Meanwhile avidemux reads the file in interleaved order and pushes audio and video alternately. Audio is cheap to encode, so the AAC branch quickly delivers buffers to the muxer, which cannot consume them yet; they pile up in the audio queue, whose default limits are 200 buffers / 10 MB / 1 second. One second of audio arrives long before 50 frames of video have been decoded and pushed through, the queue refuses the next audio buffer, the demuxer’s push blocks — and since the demuxer is single-threaded, it stops pushing video too. x264 never reaches its lookahead depth, the muxer never gets its first video buffer, and the graph is deadlocked with every element « healthy ».

The video queue is not the problem here, but making it unbounded as well costs nothing and protects the mirror case (a slow audio encoder). -e is still needed so that killing the pipeline or hitting EOS finalizes the MP4’s moov atom; without it the file is written but unplayable.

A related trap from the same tooling: Godot capture tools under XWayland run 100× slower until you pass –disable-vsync — both failures look like « it is just slow » when they are actually blocked.

What I did not test

Only this input shape: MJPEG + 16-bit PCM from Godot 4.7’s AVI writer, GStreamer 1.x on Ubuntu with x264enc, voaacenc and mp4mux. A large but finite queue (say max-size-time=10000000000) would very likely also work and bound memory; I went with unbounded because the clips are seconds long. I did not check whether x264enc tune=zerolatency alone avoids the stall by shrinking the lookahead — it would change the encode quality, which I did not want.

Facts

context: gst-launch-1.0 transcode of a Godot Movie Maker AVI (MJPEG + PCM) to MP4 (x264enc + voaacenc → mp4mux)
problem: pipeline stays PLAYING forever, output file 0 bytes, no error — audio queue full at its 1 s default while x264 buffers lookahead, demuxer blocks
solution: queue max-size-buffers=0 max-size-bytes=0 max-size-time=0 on the audio (and video) branch
verified_on: 2026-08-30
applies_to: [GStreamer 1.x, x264enc, mp4mux, avidemux, two-branch demux→encode→mux pipelines]
does_not_apply_to: [single-stream pipelines, pipelines where the muxer receives video first]

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Retour en haut