Conversation
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
tempusfrangit
left a comment
There was a problem hiding this comment.
I reproduced the failure described in the existing inline comment: 600 video frames at 30 fps with 20 seconds of 48 kHz audio make write_mp4 raise ArgumentError while encoding the video. Please interleave stream encoding or muxing and add a longer video/audio round-trip regression test before merging.
498bd52 to
3e06488
Compare
f5bf9c5 to
e6ae667
Compare
|
@tempusfrangit reproduced the 600-frame + 20 s audio failure and fixed it in e6ae667: every stream's packets are merged by decode time before muxing. |
e6ae667 to
a510431
Compare
fcfd87c to
9036aff
Compare
A saved step result has to be one file a client can download and play, holding every track the step produced. write_mp4() encodes a MediaBundle that way: each video track becomes an H.264 or H.265 stream at the model's frame rate, each audio track an audio stream at its own rate, and the file is laid out to play while it downloads. Codecs, preset, quality and audio bitrate come from the runtime.step_results block. A frame with an odd size is padded by repeating its edge, because the encoder's pixel format needs even dimensions. Every stream is added before the first packet, because the muxer writes the file header with it. A payload the writer cannot encode is refused before the file opens, and a file that fails part-way is removed, so the path holds a whole file or nothing. Signed-off-by: Dere-Wah <derexcontact@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
The writer encoded each stream to the end before feeding the next, so a longer step with video and audio failed: the muxer buffers only a bounded span of one stream while it waits for the others, and 600 frames at 30 fps reached that limit before any audio packet arrived, which surfaced as an Invalid argument error from FFmpeg. Each stream is now encoded into its own list of packets, and the lists are merged by decode time before they reach the muxer. A step is short, so its compressed packets fit in memory. A twenty-second video and audio round trip covers it. Signed-off-by: Dere-Wah <derexcontact@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
9036aff to
966a3b6
Compare
a510431 to
355b7ab
Compare

Why
A saved step has to be one file a caller can download and play, holding every track the step produced. The recorder's encoder writes segmented fMP4 for HLS, with a timeline that spans the session; a step is written once, whole, so it needs none of that and a small writer of its own.
recording/does not change.What Changed
write_mp4(path, bundle, fps, config)encodes aMediaBundleinto one MP4 with PyAV. Video tracks come first, as H.264 or H.265 streams played atfps, then audio tracks, encoded with the configured codec and bitrate at the track's own rate (48 kHz when it declares none). Each keeps the order the output declares. The file is laid out with+faststart, so it plays while it downloads. Codecs, preset, quality, and audio bitrate come fromStepResultsConfig, and the pixel format and profile are the recorder's.A frame with an odd width or height is padded by one row or column, repeating the edge, because the encoder's pixel format needs even dimensions. Every stream is added before the first packet is written: the muxer writes the file header with that packet, and a stream added after it crashes libav, which is how a step with two video tracks first failed. Each stream is encoded into its own list of packets, and the lists are merged by decode time before they reach the muxer: the muxer buffers only a bounded span of one stream while it waits for the others, so encoding video to the end before any audio fails for a longer step (600 frames at 30 fps with 20 s of audio raised
Invalid argument).A payload the writer cannot encode (no tracks,
fps <= 0, a frame of the wrong shape) is refused before the file opens, and a file that fails part-way is removed, so the path holds a whole file or nothing.The tests decode every file they write back: stream count, frame count, size, rate, codec (
h264,hevc,aac), audio length, the progressive layout, the errors, the cleanup after a failure mid-write, and a twenty-second video and audio round trip.