fix(transcode): honor bit depth and channel constraints in transcoding selection

selectTranscodingOptions only checked sample rate when deciding whether
same-format transcoding was needed, ignoring requested bit depth and
channel reductions. This caused the streamer to return raw audio when
the transcode decision requested downmix or bit-depth conversion.
This commit is contained in:
Deluan 2026-03-08 17:23:29 -04:00
parent 8f508d08f7
commit c69ed2a913
2 changed files with 57 additions and 25 deletions

View File

@ -83,7 +83,7 @@ func (ms *mediaStreamer) DoStream(ctx context.Context, mf *model.MediaFile, req
"originalFormat", mf.Suffix, "originalBitRate", mf.BitRate)
}()
format, bitRate = selectTranscodingOptions(ctx, ms.ds, mf, req.Format, req.BitRate, req.SampleRate)
format, bitRate = selectTranscodingOptions(ctx, ms.ds, mf, req.Format, req.BitRate, req.SampleRate, req.BitDepth, req.Channels)
s := &Stream{ctx: ctx, mf: mf, format: format, bitRate: bitRate}
filePath := mf.AbsolutePath()
@ -150,13 +150,15 @@ func (s *Stream) EstimatedContentLength() int {
}
// TODO This function deserves some love (refactoring)
func selectTranscodingOptions(ctx context.Context, ds model.DataStore, mf *model.MediaFile, reqFormat string, reqBitRate int, reqSampleRate int) (format string, bitRate int) {
func selectTranscodingOptions(ctx context.Context, ds model.DataStore, mf *model.MediaFile, reqFormat string, reqBitRate int, reqSampleRate int, reqBitDepth int, reqChannels int) (format string, bitRate int) {
format = "raw"
if reqFormat == "raw" {
return format, 0
}
needsResample := reqSampleRate > 0 && reqSampleRate < mf.SampleRate
if reqFormat == mf.Suffix && reqBitRate == 0 && !needsResample {
needsTranscode := (reqSampleRate > 0 && reqSampleRate < mf.SampleRate) ||
(reqBitDepth > 0 && reqBitDepth < mf.BitDepth) ||
(reqChannels > 0 && reqChannels < mf.Channels)
if reqFormat == mf.Suffix && reqBitRate == 0 && !needsTranscode {
bitRate = mf.BitRate
return format, bitRate
}
@ -195,7 +197,7 @@ func selectTranscodingOptions(ctx context.Context, ds model.DataStore, mf *model
bitRate = t.DefaultBitRate
}
}
if format == mf.Suffix && bitRate >= mf.BitRate && !needsResample {
if format == mf.Suffix && bitRate >= mf.BitRate && !needsTranscode {
format = "raw"
bitRate = 0
}

View File

@ -26,39 +26,39 @@ var _ = Describe("MediaStreamer", func() {
It("returns raw if raw is requested", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
format, _ := selectTranscodingOptions(ctx, ds, mf, "raw", 0, 0)
format, _ := selectTranscodingOptions(ctx, ds, mf, "raw", 0, 0, 0, 0)
Expect(format).To(Equal("raw"))
})
It("returns raw if a transcoder does not exists", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
format, _ := selectTranscodingOptions(ctx, ds, mf, "m4a", 0, 0)
format, _ := selectTranscodingOptions(ctx, ds, mf, "m4a", 0, 0, 0, 0)
Expect(format).To(Equal("raw"))
})
It("returns the requested format if a transcoder exists", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "mp3", 0, 0)
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "mp3", 0, 0, 0, 0)
Expect(format).To(Equal("mp3"))
Expect(bitRate).To(Equal(160)) // Default Bit Rate
})
It("returns raw if requested format is the same as the original and it is not necessary to downsample", func() {
mf.Suffix = "mp3"
mf.BitRate = 112
format, _ := selectTranscodingOptions(ctx, ds, mf, "mp3", 128, 0)
format, _ := selectTranscodingOptions(ctx, ds, mf, "mp3", 128, 0, 0, 0)
Expect(format).To(Equal("raw"))
})
It("returns the requested format if requested BitRate is lower than original", func() {
mf.Suffix = "mp3"
mf.BitRate = 320
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "mp3", 192, 0)
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "mp3", 192, 0, 0, 0)
Expect(format).To(Equal("mp3"))
Expect(bitRate).To(Equal(192))
})
It("returns raw if requested format is the same as the original, but requested BitRate is 0", func() {
mf.Suffix = "mp3"
mf.BitRate = 320
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "mp3", 0, 0)
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "mp3", 0, 0, 0, 0)
Expect(format).To(Equal("raw"))
Expect(bitRate).To(Equal(320))
})
@ -66,7 +66,7 @@ var _ = Describe("MediaStreamer", func() {
mf.Suffix = "flac"
mf.BitRate = 2118
mf.SampleRate = 96000
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "flac", 0, 48000)
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "flac", 0, 48000, 0, 0)
Expect(format).To(Equal("flac"))
Expect(bitRate).To(Equal(0))
})
@ -74,14 +74,44 @@ var _ = Describe("MediaStreamer", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
mf.SampleRate = 48000
format, _ := selectTranscodingOptions(ctx, ds, mf, "flac", 0, 48000)
format, _ := selectTranscodingOptions(ctx, ds, mf, "flac", 0, 48000, 0, 0)
Expect(format).To(Equal("raw"))
})
It("returns raw when same format is requested with no sample rate constraint", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
mf.SampleRate = 96000
format, _ := selectTranscodingOptions(ctx, ds, mf, "flac", 0, 0)
format, _ := selectTranscodingOptions(ctx, ds, mf, "flac", 0, 0, 0, 0)
Expect(format).To(Equal("raw"))
})
It("returns the format when same format is requested but with lower bit depth", func() {
mf.Suffix = "flac"
mf.BitRate = 2118
mf.BitDepth = 24
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "flac", 0, 0, 16, 0)
Expect(format).To(Equal("flac"))
Expect(bitRate).To(Equal(0))
})
It("returns raw when same format is requested with same bit depth", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
mf.BitDepth = 16
format, _ := selectTranscodingOptions(ctx, ds, mf, "flac", 0, 0, 16, 0)
Expect(format).To(Equal("raw"))
})
It("returns the format when same format is requested but with fewer channels", func() {
mf.Suffix = "flac"
mf.BitRate = 2118
mf.Channels = 6
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "flac", 0, 0, 0, 2)
Expect(format).To(Equal("flac"))
Expect(bitRate).To(Equal(0))
})
It("returns raw when same format is requested with same channels", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
mf.Channels = 2
format, _ := selectTranscodingOptions(ctx, ds, mf, "flac", 0, 0, 0, 2)
Expect(format).To(Equal("raw"))
})
Context("Downsampling", func() {
@ -91,13 +121,13 @@ var _ = Describe("MediaStreamer", func() {
mf.BitRate = 960
})
It("returns the DefaultDownsamplingFormat if a maxBitrate is requested but not the format", func() {
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "", 128, 0)
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "", 128, 0, 0, 0)
Expect(format).To(Equal("opus"))
Expect(bitRate).To(Equal(128))
})
It("returns raw if maxBitrate is equal or greater than original", func() {
// This happens with DSub (and maybe other clients?). See https://github.com/navidrome/navidrome/issues/2066
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "", 960, 0)
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "", 960, 0, 0, 0)
Expect(format).To(Equal("raw"))
Expect(bitRate).To(Equal(0))
})
@ -112,34 +142,34 @@ var _ = Describe("MediaStreamer", func() {
It("returns raw if raw is requested", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
format, _ := selectTranscodingOptions(ctx, ds, mf, "raw", 0, 0)
format, _ := selectTranscodingOptions(ctx, ds, mf, "raw", 0, 0, 0, 0)
Expect(format).To(Equal("raw"))
})
It("returns configured format/bitrate as default", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "", 0, 0)
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "", 0, 0, 0, 0)
Expect(format).To(Equal("oga"))
Expect(bitRate).To(Equal(96))
})
It("returns requested format", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "mp3", 0, 0)
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "mp3", 0, 0, 0, 0)
Expect(format).To(Equal("mp3"))
Expect(bitRate).To(Equal(160)) // Default Bit Rate
})
It("returns requested bitrate", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "", 80, 0)
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "", 80, 0, 0, 0)
Expect(format).To(Equal("oga"))
Expect(bitRate).To(Equal(80))
})
It("returns raw if selected bitrate and format is the same as original", func() {
mf.Suffix = "mp3"
mf.BitRate = 192
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "mp3", 192, 0)
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "mp3", 192, 0, 0, 0)
Expect(format).To(Equal("raw"))
Expect(bitRate).To(Equal(0))
})
@ -155,27 +185,27 @@ var _ = Describe("MediaStreamer", func() {
It("returns raw if raw is requested", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
format, _ := selectTranscodingOptions(ctx, ds, mf, "raw", 0, 0)
format, _ := selectTranscodingOptions(ctx, ds, mf, "raw", 0, 0, 0, 0)
Expect(format).To(Equal("raw"))
})
It("returns configured format/bitrate as default", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "", 0, 0)
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "", 0, 0, 0, 0)
Expect(format).To(Equal("oga"))
Expect(bitRate).To(Equal(192))
})
It("returns requested format", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "mp3", 0, 0)
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "mp3", 0, 0, 0, 0)
Expect(format).To(Equal("mp3"))
Expect(bitRate).To(Equal(160)) // Default Bit Rate
})
It("returns requested bitrate", func() {
mf.Suffix = "flac"
mf.BitRate = 1000
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "", 160, 0)
format, bitRate := selectTranscodingOptions(ctx, ds, mf, "", 160, 0, 0, 0)
Expect(format).To(Equal("oga"))
Expect(bitRate).To(Equal(160))
})