offline

Navidrome

The short version

There are two settings layers:

  1. The client app may request an audio format and/or a maximum bitrate.
  2. Navidrome can apply a matching per-player transcoding profile that selects or overrides the resulting stream codec and bitrate.

The final truth is in Navidrome’s stream log:

selectedFormat=...
selectedBitrate=...
format=...
bitRate=...

Those fields show what Navidrome actually served.

The roles

Component What it controls
Music library file Original codec and bitrate, e.g. MP3 at 252 kbps
Client app May request codec, maximum bitrate, both, or neither
Navidrome player profile Server-side format/bitrate policy for the matching player
Navidrome/FFmpeg Creates and serves the final stream

How a stream is chosen

For each play request, Navidrome:

  1. Identifies the API client/player.
  2. Matches a saved player record when one exists.
  3. Determines whether the original file is allowed for that player/request.
  4. Applies an applicable per-player transcode profile.
  5. Streams either the original media or a transcoded output.

Typical successful transcode sequence:

Found matching player ... type=Arpeggi
Transcode decision: transcode ... targetBitrate=128 targetFormat=opus
Streaming file ... format=opus bitRate=128 transcoding=true

Client requests vs player profiles

A Navidrome per-player profile can override the format and bitrate asked for in a legacy Subsonic /rest/stream.view request.

Example: Amperfy can request:

format=mp3&maxBitRate=128

If Navidrome’s matching player profile is configured as Opus / 96 kbps, Navidrome can select and deliver:

selectedFormat=opus
selectedBitrate=96
format=opus
bitRate=96

The format query parameter alone is therefore not the final answer. Always inspect Navidrome’s selected and streamed values.

“Server choose codec” in a client

When a client is set to Server choose codec, it may omit format= but still request a maximum bitrate.

For example:

.../stream.view?...&maxBitRate=128

Navidrome can then use the matching player profile to choose the codec. “Server choose codec” means the client permits the server to decide; it does not guarantee that every codec selected by the server will play properly in that particular client.

Your tested clients

Client/setup Client request or behavior Navidrome output from logs Observed result
Amcfy, profile removed Low-bitrate streaming behavior MP3 / 128 kbps Navidrome fallback result
Amcfy, player profile set Transcode flow with server profile Opus / 80 kbps Profile honored
Substreamer Explicit format=opus, maxBitRate=64 Opus / 64 kbps Working
Amperfy, client MP3 + server MP3 Requests MP3 / max 128 kbps MP3 / 96 kbps Working
Amperfy, server chooses codec + AAC server profile No explicit format; max 128 kbps AAC / 96 kbps No audible playback in your test
Amperfy, server overridden to Opus Client request overridden Opus / 96 kbps No playback in your test
Arpeggi Explicit format=opus, maxBitRate=128 Opus / 128 kbps Working; reports playing and later paused at 28 seconds
Navidrome Web UI in Firefox No applicable transcode requirement Original MP3 / 196 kbps Direct play

Arpeggi: confirmed configuration

Arpeggi is the clearest working Opus example in your logs.

The request sent to Navidrome is:

format=opus
maxBitRate=128
estimateContentLength=false

Navidrome responds by selecting:

targetFormat=opus
targetBitrate=128
selectedFormat=opus
selectedBitrate=128

The delivered stream is:

format=opus
bitRate=128
sampleRate=48000
channels=2
transcoding=true

Arpeggi then reports successful playback:

positionMs=1000 state=playing

and later:

positionMs=28000 state=paused

The first stream connection was cancelled after a short initial transfer, then a second connection completed with approximately 3.08 MB delivered. This is normal-looking iOS stream setup behavior rather than a server failure.

Recommended Arpeggi cellular profile

Arpeggi app: Opus, maximum 128 kbps
Navidrome player profile: Optional; do not override it with another codec unless tested

Because Arpeggi already explicitly asks for Opus / 128 kbps and successfully plays it, the simplest setup is to let Arpeggi control its own streaming request. If you create a Navidrome profile for it, use the same target:

Opus audio, 128 kbps

Do not set a lower server profile unless you intentionally want Navidrome to override Arpeggi’s requested quality.

Recommended practical settings

Configure each client according to tested compatibility.

Client Recommended cellular target Notes
Arpeggi Opus, 128 kbps Confirmed working from your logs
Substreamer Opus, 64 kbps Confirmed working; raise to 96 kbps if you want more quality
Amcfy Opus, 96–128 kbps if confirmed in playback Server successfully delivered Opus; verify in app
Amperfy MP3, 96–128 kbps Keep app and Navidrome profile aligned on MP3
Navidrome Web UI Original stream Avoid needless lossy re-encoding on a capable desktop connection

Codec and bitrate

Useful cellular choices:

Goal Opus MP3 Approximate data per hour
Minimum data/background listening 64 kbps 96 kbps 29 MB/h at 64 kbps; 43 MB/h at 96 kbps
Balanced mobile streaming 96 kbps 128 kbps 43 MB/h at 96 kbps; 58 MB/h at 128 kbps
Better music quality 128 kbps 160–192 kbps 58 MB/h at 128 kbps
Maximum quality Original Original Depends on the source

How to verify a client

  1. Start playback in the desired app.
  2. Watch the Navidrome container log:
docker logs -f navidrome 2>&1 | grep -E "Found matching player|Transcode decision|Streaming file|selectedFormat|selectedBitrate"
  1. Find the player Navidrome matched:
Found matching player ... client=... type=...
  1. Confirm the actual stream:
Streaming file ... format=... bitRate=... transcoding=true

Do not rely only on:

Reading common log messages

Log message Meaning
Transcode decision: transcode Navidrome will convert the source before streaming
reasons=[audio bitrate not supported] Source bitrate exceeds the applicable bitrate limit
Applying server-side transcoding override A Navidrome player profile is overriding the client request
cached=true Navidrome reused a cached transcode rather than generating it again
httpStatus=206 Partial-content/range response; normal for seeking or player probing
broken pipe / context canceled Client closed a stream connection; commonly harmless during retries/prebuffering unless playback fails
Last.fm error(9): Invalid session key Last.fm authentication expired; unrelated to audio playback

Bottom line

For Arpeggi, use Opus at 128 kbps for cellular streaming. It explicitly requests that format, Navidrome serves it unchanged, and the playback reports confirm that it plays successfully.

More generally, use the codec/bitrate each individual client has demonstrated it can play, and verify every change through Navidrome’s selectedFormat, selectedBitrate, format, and bitRate log fields.

#streaming, #music, #codec
Marcus Obst
Güterweg 89b
09474 Crottendorf
Germany

+49 37344 133407
info@marcus-obst.de