Skip to content

Commit fba3b73

Browse files
committed
Say the --audio-format pin missed the advertisement, not the device
A pin the advertised list does not carry was reported as a format the output device does not take. The device often takes it: advertised_channels() collapses the channels axis to a single count -- 2 where the device lists it, its narrowest otherwise -- so a mono or multichannel pin misses pin_preferred_format() on any device that also takes stereo, and the refusal blamed the device for a narrowing this player did. The advice compounded it. -l prints the device's own channel list, so an operator who followed it was shown a set that confirmed their pin and told the opposite. Reword the refusal to assert only what was checked -- membership in the advertisement -- and keep the -l pointer with the distinction spelled out. The refusal itself is unchanged: refusing rather than playing something else is what the flag is for. advertised_channels(), supported_formats(), pin_preferred_format() and the preference ladders are untouched. The same claim appears in --help, the packaged conf example, the README, the Configuration wiki page, ROADMAP item 24, and the doc-comments that own the flag in cli.h and supported_formats.h. Correct each.
1 parent 5f5104f commit fba3b73

8 files changed

Lines changed: 44 additions & 33 deletions

File tree

README.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -641,15 +641,19 @@ device, the format, and the fact that the stream's audio is being discarded —
641641
rather than leaving a player that looks healthy and plays nothing.
642642

643643
`--audio-format <codec:rate:depth:channels>` (e.g. `flac:48000:24:2`) pins a
644-
preferred format on top of that derived list — the way to hold a fussy DAC at the
645-
one shape it is happy in. It is a *reorder*, not a narrowing: the pinned entry
646-
moves to the front, which is where a spec-following server picks, and everything
647-
else the device takes is still offered behind it. A pin the device cannot take
648-
**refuses to start** — playing something else instead is the failure the flag
649-
exists to prevent — and `-l` shows what would be accepted. Codecs are `flac`,
650-
`opus` and `pcm`; the grammar is the Python `sendspin-cli`'s, plus `opus`. An
651-
`opus` pin is refused outright at anything but 48 kHz / 16-bit / at most two
652-
channels, since that is the only shape it is ever advertised in.
644+
preferred format on top of that derived list — the way to hold a fussy DAC at
645+
the one shape it is happy in. It is a *reorder*, not a narrowing: the pinned
646+
entry moves to the front, which is where a spec-following server picks, and
647+
everything else the device takes is still offered behind it. A pin that
648+
derived list does not carry **refuses to start** — playing something else
649+
instead is the failure the flag exists to prevent — and `-l` shows what the
650+
device itself reports, which is not the same set. The advertisement carries a
651+
single channel count — stereo where the device takes it, its narrowest count
652+
otherwise — so a mono or multichannel pin is refused on any device that also
653+
takes stereo. Codecs are `flac`, `opus` and `pcm`; the grammar is the Python
654+
`sendspin-cli`'s, plus `opus`. An `opus` pin is refused outright at anything
655+
but 48 kHz / 16-bit / at most two channels, since that is the only shape it is
656+
ever advertised in.
653657

654658
### Running as a daemon
655659

docs/ROADMAP.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,14 +2191,15 @@ exit on failure.
21912191
order, so the front is the whole of what "preferred" means on the wire — and
21922192
everything else the device takes is still offered behind it, so a server that cannot
21932193
encode the pin has the rest of the list to fall back on.
2194-
- **A pin the device cannot take refuses to start**, naming the device, the format, and
2195-
`-l` as the way to see what would be accepted. Checked against the *derived* list — or
2196-
the permissive fallback when the device reported nothing, since that is what actually
2197-
goes out — so the refusal describes the real advertisement.
2198-
- **Parse-time shape validation, startup-time device validation.** The grammar, the
2194+
- **A pin the advertisement does not carry refuses to start**, naming the format, the
2195+
device, and `-l` as the way to see what the device itself reports — not the same set as
2196+
what is advertised, which carries a single channel count. Checked against the *derived*
2197+
list — or the permissive fallback when the device reported nothing, since that is what
2198+
actually goes out — so the refusal describes the real advertisement.
2199+
- **Parse-time shape validation, startup-time advertisement validation.** The grammar, the
21992200
codec names, the four emittable bit depths and the one shape Opus is ever advertised in
22002201
are settled in `parse_format_spec()` when the flag is read — so a config file is
2201-
validated without opening a device — and whether the device takes the format is
2202+
validated without opening a device — and whether the advertisement carries the format is
22022203
answered where the sink is real.
22032204

22042205
Verified against a real `aiosendspin` server: with `pcm:44100:16:2` pinned on a device

docs/wiki/Configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ already does that.
5555
| `server` | `-s`, `--server` | `<host>[:<port>]`, a `ws://` URL, or `mdns:[<name>]` | none — wait to be discovered |
5656
| `port` | `--port` | the port this player's own WebSocket server listens on | `8928` |
5757
| `buffer-ms` | `--buffer-ms` | audio the output backend keeps queued, 10–2000 | `100` |
58-
| `audio-format` | `--audio-format` | pin a preferred format to the front of the advertised list: `codec:rate:depth:channels`, e.g. `flac:48000:24:2`; refuses to start if the device cannot take it, and an `opus` pin at anything but 48000/16 and at most 2 channels is refused outright | none — device-derived order |
58+
| `audio-format` | `--audio-format` | pin a preferred format to the front of the advertised list: `codec:rate:depth:channels`, e.g. `flac:48000:24:2`; refuses to start if the advertised list does not carry it — it carries a single channel count — and an `opus` pin at anything but 48000/16 and at most 2 channels is refused outright | none — device-derived order |
5959
| `static-delay` | `--static-delay` | latency this endpoint's hardware adds after the audio port, 0–5000 | `0` |
6060
| `no-mdns` | `--no-mdns` | `true`/`false` — do not advertise `_sendspin._tcp` | `false` |
6161
| `mdns-name` | `--mdns-name` | the instance label to advertise, when it should differ from `name` | `name` |

packaging/sendspin-cli.conf.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
# Pin a preferred audio format -- codec:rate:depth:channels, codecs flac/opus/pcm -- for
5252
# the DAC that is only happy in one shape. Moves that entry to the front of the advertised
5353
# list, where a server picks first; the rest of what the device takes is still offered.
54-
# A format the device cannot take refuses to start; `sendspin-cli -l` shows what it accepts.
54+
# A format the advertised list does not carry refuses to start; `sendspin-cli -l` shows what
55+
# the device itself reports -- not the same set as what goes out.
5556
#audio-format = flac:48000:24:2
5657

5758
# The port this player's own WebSocket server listens on, for a server that connects in.

src/cli.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,8 +1133,9 @@ void print_usage(std::FILE* out, const char* prog) {
11331133
std::fprintf(out, " DAC that is only happy in one shape. Moves that entry to\n");
11341134
std::fprintf(out, " the front of the advertised list, where a server picks\n");
11351135
std::fprintf(out, " first; the rest of what the device takes is still\n");
1136-
std::fprintf(out, " offered behind it. A format the device does not take\n");
1137-
std::fprintf(out, " refuses to start -- run -l to see what it accepts\n");
1136+
std::fprintf(out, " offered behind it. A format the advertised list does\n");
1137+
std::fprintf(out, " not carry refuses to start -- run -l to see what the\n");
1138+
std::fprintf(out, " device reports -- not the same set as what goes out\n");
11381139
std::fprintf(out, " --static-delay <ms>\n");
11391140
std::fprintf(out, " How much latency this endpoint's hardware adds AFTER the\n");
11401141
std::fprintf(out, " audio port -- an amplifier, an external speaker, a DSP.\n");

src/cli.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,8 @@ struct Options {
214214
///
215215
/// A *reorder*, not a narrowing: the pinned entry moves to the front of the advertised
216216
/// list, which is what "preferred" means on the wire, and everything the device takes is
217-
/// still offered behind it. Parsing settles the shape here; whether the device really
218-
/// takes it is answered at startup, where a pin the derived advertisement does not
217+
/// still offered behind it. Parsing settles the shape here; whether the advertisement
218+
/// carries it is answered at startup, where a pin the derived advertisement does not
219219
/// contain is a hard refusal to start -- playing something else instead is the failure
220220
/// this flag exists to prevent. Grammar and behaviour match the Python CLI's flag of the
221221
/// same name, extended with `opus`.

src/main.cpp

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -789,17 +789,20 @@ int main(int argc, char* argv[]) {
789789

790790
std::vector<sendspin::AudioSupportedFormatObject> formats = advertised_formats(*sink);
791791
// The --audio-format pin, applied to the derived list because that is the promise being
792-
// reordered: an entry in front of it is one the device really takes. A pin the list does
793-
// not contain is a hard stop -- the operator asked for the one shape their DAC is happy
794-
// in, and starting anyway would play everything except that. Against the *fallback* list
795-
// when the device reported nothing, deliberately: that run advertises the permissive set,
796-
// so the pin is checked against what actually goes out.
792+
// reordered: an entry in front of it is one already going out. That list can be narrower
793+
// than the device's own report -- advertised_channels() collapses the channels axis to one
794+
// count -- so a pin missing from it is not necessarily one the device refuses. It is a
795+
// hard stop either way: the operator asked for the one shape their DAC is happy in, and
796+
// starting anyway would play everything except that. Against the *fallback* list when the
797+
// device reported nothing, deliberately: that run advertises the permissive set, so the
798+
// pin is checked against what actually goes out.
797799
if (opts.audio_format.has_value()) {
798800
if (!pin_preferred_format(formats, *opts.audio_format)) {
799801
log_fatal(LOG_TAG_AUDIO,
800-
"--audio-format asked for %s, which output device '%s' does not take -- "
801-
"refusing to start rather than play something else. Run with -l to see "
802-
"what the device accepts.",
802+
"--audio-format asked for %s, which is not among the formats advertised "
803+
"for output device '%s' -- refusing to start rather than play something "
804+
"else. Run with -l to see what the device itself reports -- not the "
805+
"same set as what gets advertised.",
803806
describe_formats({*opts.audio_format}).c_str(), sink->name().c_str());
804807
return 1;
805808
}

src/supported_formats.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ std::vector<sendspin::AudioSupportedFormatObject> supported_formats(const SinkCa
6767
/// same reason and a stronger one: that is the only shape supported_formats() ever emits it in,
6868
/// whatever the device would take, so any other is unreachable rather than merely unadvertised.
6969
///
70-
/// Only the *shape* is settled here. Whether the device actually takes the format is a
70+
/// Only the *shape* is settled here. Whether the advertisement carries the format is a
7171
/// property of the host, answered at startup by pin_preferred_format() against the derived
7272
/// advertisement -- the split that lets a config file be validated without opening a device.
7373
/// @param error Set to the reason when false comes back, without the flag's name -- the
@@ -84,9 +84,10 @@ bool parse_format_spec(const std::string& spec, sendspin::AudioSupportedFormatOb
8484
/// the front is the whole of what "preferred" means on the wire.
8585
///
8686
/// Absence is the caller's to act on, and the intended action is to refuse to start: an
87-
/// operator who pinned a format their device cannot take asked for something this player
88-
/// cannot do, and playing something else instead is the failure --audio-format exists to
89-
/// prevent.
87+
/// operator who pinned a format the advertisement does not carry asked for something no
88+
/// conforming server will send, and playing something else instead is the failure
89+
/// --audio-format exists to prevent. Absence is not proof the device refuses the format --
90+
/// the advertisement is narrowed on the channels axis before it gets here.
9091
/// @return true if `preferred` was found (and is now first).
9192
bool pin_preferred_format(std::vector<sendspin::AudioSupportedFormatObject>& formats,
9293
const sendspin::AudioSupportedFormatObject& preferred);

0 commit comments

Comments
 (0)