mirror of
https://github.com/navidrome/navidrome.git
synced 2026-11-06 10:18:43 +01:00
fix(transcoding): declare the duration of a piped mp3 transcode
The mp3 muxer writes the Xing frame that carries the frame count only after rewinding to the start of the stream, which it cannot do on a pipe (-f mp3 -). An uncached transcode therefore declares its length nowhere: no Xing frame, and no Content-Length unless the client asks for the estimate. A decoder has to guess the duration from the bytes it has so far, and an offline copy stays unseekable, the same defect #6105 fixed for FLAC. Transcode now inserts a Xing frame ahead of the first audio frame, carrying the frame count derived from the track duration. Only the frame count is declared: a piped encode cannot know its own byte size, and a wrong size is worse than an absent one. Decoded audio is unchanged, since decoders skip the tag frame. Measured on a 20s 128kbps transcode: ffprobe reports 20.010s instead of 20.036s, and Firefox reports 19.998s at loadedmetadata instead of 2.04s climbing over nine durationchange events. This does not make an uncached transcode resumable; that needs range support, which only the cache path has. Reported in #6170.
This commit is contained in:
parent
76df3bb7fa
commit
c05fbd0edb
4 changed files with 366 additions and 2 deletions
|
|
@ -1,7 +1,9 @@
|
|||
package ffmpeg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
|
@ -719,6 +721,23 @@ var _ = Describe("ffmpeg", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(readTotalSamples(out)).To(Equal(uint64(2 * 44100)))
|
||||
})
|
||||
|
||||
It("inserts a Xing frame on a piped mp3 transcode", func() {
|
||||
stream, err := ff.Transcode(GinkgoT().Context(), TranscodeOptions{
|
||||
Command: "ffmpeg -i %s -map 0:a:0 -v 0 -b:a 128k -f mp3 -",
|
||||
Format: "mp3",
|
||||
FilePath: "tests/fixtures/test.flac",
|
||||
Duration: 1, // the fixture is exactly 1s at 44100Hz
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer stream.Close()
|
||||
|
||||
out, err := io.ReadAll(stream)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
at := bytes.Index(out, []byte("Xing"))
|
||||
Expect(at).To(BeNumerically(">", 0))
|
||||
Expect(binary.BigEndian.Uint32(out[at+8:])).To(Equal(uint32(38)))
|
||||
})
|
||||
})
|
||||
|
||||
Context("stderr capture", func() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue