From c69ed2a91395bbf7ff0b7abf6dd6a0fc8f237171 Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 8 Mar 2026 17:23:29 -0400 Subject: [PATCH] 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. --- core/media_streamer.go | 12 +++-- core/media_streamer_Internal_test.go | 70 ++++++++++++++++++++-------- 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/core/media_streamer.go b/core/media_streamer.go index 50f0ae262..8b7e89490 100644 --- a/core/media_streamer.go +++ b/core/media_streamer.go @@ -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 } diff --git a/core/media_streamer_Internal_test.go b/core/media_streamer_Internal_test.go index 930ffe0ba..cda5274f4 100644 --- a/core/media_streamer_Internal_test.go +++ b/core/media_streamer_Internal_test.go @@ -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)) })