Files
shairport-sync/audio.c
T
Mike Brady 11052b7896 Lot of changes here.
1. check_classic_mac_basic.yml — update to latest macOS-latest
2. docker-on-push-tag-or-pr — dependant updates
3. FFTConvolver — fix a compilation warning when the DEBUG flag is turned off
4. Reorganise some source files — e.g. move most of the metadata stuff into a subdirectory
5. Remove unused special-purpose AirPlay 2 RC event handler.
6. Reorganise source files and source code relating to the standard event handler.
7. Check configuration settings a little more carefully to offer more meaningful diagnostics.
8. Add password to AirPlay 2 and reorganise initialisation to set AirPlay 2 flags accordingly.  Also, remove redundant airplay_pin.
9. Remove redundant debug facility definitions from common.h
10. Make it possible to omit all the dacp stuff from a build.
11. Make it possible to omit metadata multicast from a build.
12. Add AP2-native metadata by setting bit 50 of features to true.
13. Improve stability when pre-empting an existing play session.
14. Fix a few compiler warnings in the pair_ap library.
15. Ensure that the audio format is known before starting synchronisation.
16. Improve handle_configure to set/clear a new config setting. Doesn’t seem to do anything…
17. Experimentally turn off bit 46 of AirPlay 2 features — seems to stop failing pair-verify attempts.
2026-04-26 10:23:18 +01:00

1049 lines
45 KiB
C

/*
* Audio driver handler. This file is part of Shairport.
* Copyright (c) James Laird 2013
* Modifications (c) Mike Brady 2014--2025
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "audio.h"
#include "common.h"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef CONFIG_JACK
extern audio_output audio_jack;
#endif
#ifdef CONFIG_SNDIO
extern audio_output audio_sndio;
#endif
#ifdef CONFIG_AO
extern audio_output audio_ao;
#endif
#ifdef CONFIG_SOUNDIO
extern audio_output audio_soundio;
#endif
#ifdef CONFIG_PIPEWIRE
extern audio_output audio_pw;
#endif
#ifdef CONFIG_PULSEAUDIO
extern audio_output audio_pa;
#endif
#ifdef CONFIG_ALSA
extern audio_output audio_alsa;
#endif
#ifdef CONFIG_DUMMY
extern audio_output audio_dummy;
#endif
#ifdef CONFIG_PIPE
extern audio_output audio_pipe;
#endif
#ifdef CONFIG_STDOUT
extern audio_output audio_stdout;
#endif
static audio_output *outputs[] = {
#ifdef CONFIG_ALSA
&audio_alsa,
#endif
#ifdef CONFIG_SNDIO
&audio_sndio,
#endif
#ifdef CONFIG_PIPEWIRE
&audio_pw,
#endif
#ifdef CONFIG_PULSEAUDIO
&audio_pa,
#endif
#ifdef CONFIG_JACK
&audio_jack,
#endif
#ifdef CONFIG_AO
&audio_ao,
#endif
#ifdef CONFIG_SOUNDIO
&audio_soundio,
#endif
#ifdef CONFIG_PIPE
&audio_pipe,
#endif
#ifdef CONFIG_STDOUT
&audio_stdout,
#endif
#ifdef CONFIG_DUMMY
&audio_dummy,
#endif
NULL};
audio_output *audio_get_output(const char *name) {
audio_output **out;
// default to the first
if (!name)
return outputs[0];
for (out = outputs; *out; out++)
if (!strcasecmp(name, (*out)->name))
return *out;
return NULL;
}
void audio_ls_outputs(void) {
audio_output **out;
printf("Available audio backends:\n");
for (out = outputs; *out; out++)
printf(" %s%s\n", (*out)->name, out == outputs ? " (default)" : "");
for (out = outputs; *out; out++) {
printf("\n");
if ((*out)->help) {
printf("Settings and options for the audio backend \"%s\":\n", (*out)->name);
(*out)->help();
} else {
printf("There are no settings or options for the audio backend \"%s\".\n", (*out)->name);
}
}
}
void parse_audio_options(const char *named_stanza, uint32_t default_format_set,
uint32_t default_rate_set, uint32_t default_channel_set) {
/* this must be called after the output device has been initialised, so that the default values
* are set before any options are chosen */
int value;
double dvalue;
const char *str = 0;
if (config.cfg != NULL) {
/* Get the desired buffer size setting (deprecated). */
if (config_lookup_int(config.cfg, "general.audio_backend_buffer_desired_length", &value)) {
inform("The setting general.audio_backend_buffer_desired_length is no longer supported. "
"Please use general.audio_backend_buffer_desired_length_in_seconds instead.");
}
/* Get the desired backend buffer size setting in seconds. */
/* This is the size of the buffer in the output system, e.g. in the DAC itself in ALSA */
if (config_lookup_float(config.cfg, "general.audio_backend_buffer_desired_length_in_seconds",
&dvalue)) {
if (dvalue < 0) {
die("Invalid audio_backend_buffer_desired_length_in_seconds value: \"%f\". It "
"should be 0.0 or greater."
" The default is %.3f seconds",
dvalue, config.audio_backend_buffer_desired_length);
} else {
config.audio_backend_buffer_desired_length = dvalue;
}
}
/* Get the desired decoded buffer size setting in seconds. */
/* This is the size of the buffer of decoded and deciphered audio held in the player's output
* queue prior to sending it to the output system */
if (config_lookup_float(config.cfg, "general.audio_decoded_buffer_desired_length_in_seconds",
&dvalue)) {
if (dvalue < 0) {
die("Invalid audio_decoded_buffer_desired_length_in_seconds value: \"%f\". It "
"should be 0.0 or greater."
" The default is %.3f seconds",
dvalue, config.audio_decoded_buffer_desired_length);
} else {
config.audio_decoded_buffer_desired_length = dvalue;
}
}
/* Get the minimum buffer size for fancy interpolation setting in seconds. */
if (config_lookup_float(config.cfg,
"general.audio_backend_buffer_interpolation_threshold_in_seconds",
&dvalue)) {
if ((dvalue < 0) || (dvalue > config.audio_backend_buffer_desired_length)) {
die("Invalid audio_backend_buffer_interpolation_threshold_in_seconds value: \"%f\". It "
"should be between 0 and "
"audio_backend_buffer_desired_length_in_seconds of %.3f, default is %.3f seconds",
dvalue, config.audio_backend_buffer_desired_length,
config.audio_backend_buffer_interpolation_threshold_in_seconds);
} else {
config.audio_backend_buffer_interpolation_threshold_in_seconds = dvalue;
}
}
/* Get the latency offset (deprecated). */
if (config_lookup_int(config.cfg, "general.audio_backend_latency_offset", &value)) {
inform("The setting general.audio_backend_latency_offset is no longer supported. "
"Please use general.audio_backend_latency_offset_in_seconds instead.");
}
/* Get the latency offset in seconds. */
if (config_lookup_float(config.cfg, "general.audio_backend_latency_offset_in_seconds",
&dvalue)) {
config.audio_backend_latency_offset = dvalue;
}
/* Check if the length of the silent lead-in ia set to \"auto\". */
if (config_lookup_string(config.cfg, "general.audio_backend_silent_lead_in_time", &str)) {
if (strcasecmp(str, "auto") == 0) {
config.audio_backend_silent_lead_in_time_auto = 1;
} else {
if (config.audio_backend_silent_lead_in_time_auto == 1)
warn("Invalid audio_backend_silent_lead_in_time \"%s\". It should be \"auto\" or the "
"lead-in time in seconds. "
"It remains set to \"auto\". Note: numbers should not be placed in quotes.",
str);
else
warn("Invalid output rate \"%s\". It should be \"auto\" or the lead-in time in seconds. "
"It remains set to %f. Note: numbers should not be placed in quotes.",
str, config.audio_backend_silent_lead_in_time);
}
}
/* Get the desired length of the silent lead-in. */
if (config_lookup_float(config.cfg, "general.audio_backend_silent_lead_in_time", &dvalue)) {
if ((dvalue < 0.0) || (dvalue > 4)) {
if (config.audio_backend_silent_lead_in_time_auto == 1)
warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
"must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. "
"The setting remains at \"auto\".",
dvalue);
else
warn("Invalid audio_backend_silent_lead_in_time \"%f\". It "
"must be between 0.0 and 4.0 seconds. Omit the setting to use the automatic value. "
"It remains set to %f.",
dvalue, config.audio_backend_silent_lead_in_time);
} else {
config.audio_backend_silent_lead_in_time = dvalue;
config.audio_backend_silent_lead_in_time_auto = 0;
}
}
}
if (named_stanza != NULL) {
config.format_set = get_format_settings(named_stanza, "output_format");
config.rate_set = get_rate_settings(named_stanza, "output_rate");
config.channel_set = get_channel_settings(named_stanza, "output_channels");
}
// use the supplied defaults if no settings were given
if (config.format_set == 0)
config.format_set = default_format_set;
if (config.rate_set == 0)
config.rate_set = default_rate_set;
if (config.channel_set == 0)
config.channel_set = default_channel_set;
}
uint32_t get_format_settings(const char *stanza_name, const char *setting_name) {
uint32_t format_set = 0;
if (config.cfg != NULL) {
char setting_path[256];
snprintf(setting_path, sizeof(setting_path) - 1, "%s.%s", stanza_name, setting_name);
// get any format settings -- can be "auto", a single format e.g. "S16" or a list of formats
config_setting_t *format_setting = config_lookup(config.cfg, setting_path);
if (format_setting != NULL) {
const char **format_settings;
int format_settings_count =
config_get_string_settings_as_string_array(format_setting, &format_settings);
if (format_settings_count > 0) {
int i;
for (i = 0; i < format_settings_count; i++) {
debug(3, "format setting %u: \"%s\".", i, format_settings[i]);
if (strcmp(format_settings[i], "auto") == 0) {
if (format_settings_count != 1)
warn("in the \"%s\" setting in the \"%s\" section of the configuration file, "
"multiple formats, including \"auto\", "
"are specified, but \"auto\" includes all "
"formats anyway...",
setting_name, stanza_name);
format_set = SPS_FORMAT_SET; // all valid formats
} else if (strcmp(format_settings[i], "S16") == 0) {
format_set |= (1 << SPS_FORMAT_S16_LE) | (1 << SPS_FORMAT_S16_BE);
} else if (strcmp(format_settings[i], "S24") == 0) {
format_set |= (1 << SPS_FORMAT_S24_LE) | (1 << SPS_FORMAT_S24_BE) |
(1 << SPS_FORMAT_S24_3LE) | (1 << SPS_FORMAT_S24_3BE);
} else if (strcmp(format_settings[i], "S32") == 0) {
format_set |= (1 << SPS_FORMAT_S32_LE) | (1 << SPS_FORMAT_S32_BE);
} else {
sps_format_t f;
int valid = 0;
for (f = SPS_FORMAT_LOWEST; f <= SPS_FORMAT_HIGHEST_NATIVE; f++) {
if (strcmp(format_settings[i], sps_format_description_string(f)) == 0) {
format_set |= (1 << f);
valid = 1;
}
}
if (valid == 0) {
warn("in the \"%s\" setting in the \"%s\" section of the configuration file, an "
"invalid format: \"%s\" has been detected.",
setting_name, stanza_name, format_settings[i]);
}
}
}
free(format_settings);
} else {
debug(1,
"in the \"%s\" setting in the \"%s\" section of the configuration file, an error has "
"been detected at argument %d.",
setting_name, stanza_name, -format_settings_count);
}
}
}
sps_format_t f;
uint32_t t_format_set = format_set;
char buf[256];
char *p = buf;
for (f = SPS_FORMAT_UNKNOWN; f <= SPS_FORMAT_S32_BE; f++) {
if ((t_format_set & (1 << f)) != 0) {
snprintf(p, sizeof(buf) - (p - buf) - 1, "%s", sps_format_description_string(f));
p = p + strlen(sps_format_description_string(f));
t_format_set -= (1 << f);
if (t_format_set != 0) {
snprintf(p, sizeof(buf) - (p - buf) - 1, ", ");
p = p + strlen(", ");
}
}
}
if (p != buf) {
*p = '.';
p++;
}
*p = '\0';
if (strlen(buf) == 0)
debug(3, "No \"%s\" output format settings.", stanza_name);
else
debug(3, "The \"%s\" output format settings are: \"%s\".", stanza_name, buf);
return format_set;
}
uint32_t get_rate_settings(const char *stanza_name, const char *setting_name) {
uint32_t rate_set = 0;
if (config.cfg != NULL) {
char setting_path[256];
snprintf(setting_path, sizeof(setting_path) - 1, "%s.%s", stanza_name, setting_name);
config_setting_t *rate_setting = config_lookup(config.cfg, setting_path);
if (rate_setting != NULL) {
if (config_setting_type(rate_setting) == CONFIG_TYPE_STRING) {
// see if it is "auto"
if (strcmp(config_setting_get_string(rate_setting), "auto") == 0) {
#ifdef CONFIG_FFMPEG
rate_set = SPS_RATE_SET; // all valid rates
#else
rate_set = SPS_RATE_NON_FFMPEG_SET;
#endif
} else {
warn("In the \"%s\" setting in the \"%s\" section of the configuration file, an invalid "
"character string -- \"%s\" -- has been detected. (Note that numbers must not be "
"enclosed in quotes.)",
setting_name, stanza_name, config_setting_get_string(rate_setting));
}
} else {
int *rates;
int rate_settings_count = config_get_int_settings_as_int_array(rate_setting, &rates);
if (rate_settings_count > 0) {
debug(3, "%d rate settings found.", rate_settings_count);
int i;
for (i = 0; i < rate_settings_count; i++) {
debug(3, "rate setting %d: %d.", i, rates[i]);
sps_rate_t r;
int valid = 0;
for (r = SPS_RATE_5512; r <= SPS_RATE_384000; r++) {
if ((unsigned int)rates[i] == sps_rate_actual_rate(r)) {
valid = 1;
#ifdef CONFIG_FFMPEG
rate_set |= (1 << r);
#else
if (((1 << r) & SPS_RATE_NON_FFMPEG_SET) != 0) {
rate_set |= (1 << r);
} else {
warn("In the \"%s\" setting in the \"%s\" section of the configuration file, "
"the rate selected -- %d -- can not be used because Shairport Sync has been "
"built without FFmpeg support.",
setting_name, stanza_name, rates[i]);
}
#endif
}
}
if (valid == 0) {
warn("In the \"%s\" setting in the \"%s\" section of the configuration file, an "
"invalid rate: %d has been detected.",
setting_name, stanza_name, rates[i]);
}
}
free(rates);
} else {
warn("in the \"%s\" setting in the \"%s\" section of the configuration file, an error "
"has been detected at argument %d. (Note that numbers must not be enclosed in "
"quotes.)",
setting_name, stanza_name, -rate_settings_count);
}
}
}
}
sps_rate_t r;
char buf[256];
char *p = buf;
uint32_t t_rate_set = rate_set;
char numbuf[32];
for (r = SPS_RATE_UNKNOWN; r <= SPS_RATE_384000; r++) {
if ((t_rate_set & (1 << r)) != 0) {
snprintf(numbuf, sizeof(numbuf) - 1, "%u", sps_rate_actual_rate(r));
snprintf(p, sizeof(buf) - (p - buf) - 1, "%s", numbuf);
p = p + strlen(numbuf);
t_rate_set -= (1 << r);
if (t_rate_set != 0) {
snprintf(p, sizeof(buf) - (p - buf) - 1, ", ");
p = p + strlen(", ");
}
}
}
if (p != buf) {
*p = '.';
p++;
}
*p = '\0';
if (strlen(buf) == 0)
debug(3, "No \"%s\" output rate settings.", stanza_name);
else
debug(3, "The \"%s\" output rate settings are: \"%s\".", stanza_name, buf);
return rate_set;
}
uint32_t get_channel_settings(const char *stanza_name, const char *setting_name) {
uint32_t channel_set = 0;
if (config.cfg != NULL) {
char setting_path[256];
snprintf(setting_path, sizeof(setting_path) - 1, "%s.%s", stanza_name, setting_name);
// now get the channels count set
config_setting_t *channels_setting = config_lookup(config.cfg, setting_path);
if (channels_setting != NULL) {
if (config_setting_type(channels_setting) == CONFIG_TYPE_STRING) {
// see if it is "auto"
if (strcmp(config_setting_get_string(channels_setting), "auto") == 0) {
#ifdef CONFIG_FFMPEG
channel_set = SPS_CHANNEL_SET; // all valid channels
#else
channel_set = SPS_CHANNNEL_NON_FFMPEG_SET; // just two channels
#endif
} else {
warn("in the \"%s\" setting in the \"%s\" section of the configuration file, an invalid "
"setting: \"%s\" has been detected.",
setting_name, stanza_name, config_setting_get_string(channels_setting));
}
} else {
int *channel_counts;
int channel_counts_count =
config_get_int_settings_as_int_array(channels_setting, &channel_counts);
if (channel_counts_count > 0) {
debug(3, "%d channel count settings found.", channel_counts_count);
int i;
for (i = 0; i < channel_counts_count; i++) {
debug(3, "channel count setting %d: %d.", i, channel_counts[i]);
if ((channel_counts[i] >= 1) && (channel_counts[i] <= 8)) {
#ifdef CONFIG_FFMPEG
channel_set |= (1 << channel_counts[i]);
#else
if (((1 << channel_counts[i]) & SPS_CHANNNEL_NON_FFMPEG_SET) != 0) {
channel_set |= (1 << channel_counts[i]);
} else {
warn("in the \"%s\" setting in the \"%s\" section of the configuration file, "
"the channel count selected -- %d -- can not be used because Shairport Sync "
"has been built without FFmpeg support.",
setting_name, stanza_name, channel_counts[i]);
}
#endif
} else {
warn("in the \"%s\" setting in the \"%s\" section of the configuration file, an "
"invalid channel count: %d has been detected.",
setting_name, stanza_name, channel_counts[i]);
}
}
free(channel_counts);
} else {
debug(1,
"in the \"%s\" setting in the \"%s\" section of the configuration file, an error "
"has been detected at argument %d.",
setting_name, stanza_name, -channel_counts_count);
}
}
}
}
char buf[256];
char *p = buf;
char numbuf[32];
unsigned int c;
uint32_t t_channel_set = channel_set;
for (c = 0; c <= 8; c++) {
if ((t_channel_set & (1 << c)) != 0) {
snprintf(numbuf, sizeof(numbuf) - 1, "%u", c);
snprintf(p, sizeof(buf) - (p - buf) - 1, "%s", numbuf);
p = p + strlen(numbuf);
t_channel_set -= (1 << c);
if (t_channel_set != 0) {
snprintf(p, sizeof(buf) - (p - buf) - 1, ", ");
p = p + strlen(", ");
}
}
}
if (p != buf) {
*p = '.';
p++;
}
*p = '\0';
if (strlen(buf) == 0)
debug(3, "No \"%s\" output channel settings.", stanza_name);
else
debug(3, "The \"%s\" output channel settings are: \"%s\".", stanza_name, buf);
return channel_set;
}
sps_format_t check_configuration_with_formats(
unsigned int channels, unsigned int rate, sps_format_t format,
int (*check_configuration)(unsigned int channels, unsigned int rate, unsigned int format)) {
sps_format_t response = SPS_FORMAT_UNKNOWN; // this is encoded as zero
// first, check that the channels are permissible...
if ((config.channel_set & (1 << channels)) != 0) {
// now check the rate...
sps_rate_t r = SPS_RATE_LOWEST;
int rate_is_permissible = 0;
while ((rate_is_permissible == 0) && (r <= SPS_RATE_HIGHEST)) {
if (((config.rate_set & (1 << r)) != 0) && (rate == sps_rate_actual_rate(r)))
rate_is_permissible = 1;
else
r++;
}
if (rate_is_permissible != 0) {
// check the actual requested format first with the initial_search array
// and if it fails, then check in the subsequent_search array.
// the choice of subsequent search array is determined by the machine's endianness
// clang-format off
sps_format_t ordered_initial_search_U8[] = {SPS_FORMAT_U8, SPS_FORMAT_S8};
sps_format_t ordered_initial_search_S8[] = {SPS_FORMAT_S8, SPS_FORMAT_U8};
sps_format_t ordered_subsequent_search_8_LE[] = {SPS_FORMAT_S16_LE, SPS_FORMAT_S16_BE, SPS_FORMAT_S24_LE, SPS_FORMAT_S24_BE, SPS_FORMAT_S24_3LE, SPS_FORMAT_S24_3BE, SPS_FORMAT_S32_LE, SPS_FORMAT_S32_BE};
sps_format_t ordered_subsequent_search_8_BE[] = {SPS_FORMAT_S16_BE, SPS_FORMAT_S16_LE, SPS_FORMAT_S24_BE, SPS_FORMAT_S24_LE, SPS_FORMAT_S24_3BE, SPS_FORMAT_S24_3LE, SPS_FORMAT_S32_BE, SPS_FORMAT_S32_LE};
sps_format_t ordered_initial_search_S16_LE[] = {SPS_FORMAT_S16_LE, SPS_FORMAT_S16_BE};
sps_format_t ordered_initial_search_S16_BE[] = {SPS_FORMAT_S16_BE, SPS_FORMAT_S16_LE};
sps_format_t ordered_subsequent_search_S16_LE[] = {SPS_FORMAT_S24_LE, SPS_FORMAT_S24_BE, SPS_FORMAT_S24_3LE, SPS_FORMAT_S24_3BE, SPS_FORMAT_S32_LE, SPS_FORMAT_S32_BE, SPS_FORMAT_S8, SPS_FORMAT_U8};
sps_format_t ordered_subsequent_search_S16_BE[] = {SPS_FORMAT_S24_BE, SPS_FORMAT_S24_LE, SPS_FORMAT_S24_3BE, SPS_FORMAT_S24_3LE, SPS_FORMAT_S32_BE, SPS_FORMAT_S32_LE, SPS_FORMAT_S8, SPS_FORMAT_U8};
sps_format_t ordered_initial_search_S24_LE[] = {SPS_FORMAT_S24_LE, SPS_FORMAT_S24_BE};
sps_format_t ordered_initial_search_S24_BE[] = {SPS_FORMAT_S24_BE, SPS_FORMAT_S24_LE};
sps_format_t ordered_subsequent_search_S24_LE[] = {SPS_FORMAT_S24_3LE, SPS_FORMAT_S24_3BE, SPS_FORMAT_S32_LE, SPS_FORMAT_S32_BE, SPS_FORMAT_S16_LE, SPS_FORMAT_S16_BE, SPS_FORMAT_S8, SPS_FORMAT_U8};
sps_format_t ordered_subsequent_search_S24_BE[] = {SPS_FORMAT_S24_3BE, SPS_FORMAT_S24_3LE, SPS_FORMAT_S32_BE, SPS_FORMAT_S32_LE, SPS_FORMAT_S16_BE, SPS_FORMAT_S16_LE, SPS_FORMAT_S8, SPS_FORMAT_U8};
sps_format_t ordered_initial_search_S24_3le[] = {SPS_FORMAT_S24_3LE, SPS_FORMAT_S24_3BE};
sps_format_t ordered_initial_search_S24_3be[] = {SPS_FORMAT_S24_3BE, SPS_FORMAT_S24_3LE};
sps_format_t ordered_subsequent_search_S24_3le[] = {SPS_FORMAT_S24_LE, SPS_FORMAT_S24_BE, SPS_FORMAT_S32_LE, SPS_FORMAT_S32_BE, SPS_FORMAT_S16_LE, SPS_FORMAT_S16_BE, SPS_FORMAT_S8, SPS_FORMAT_U8};
sps_format_t ordered_subsequent_search_S24_3be[] = {SPS_FORMAT_S24_BE, SPS_FORMAT_S24_LE, SPS_FORMAT_S32_BE, SPS_FORMAT_S32_LE, SPS_FORMAT_S16_BE, SPS_FORMAT_S16_LE, SPS_FORMAT_S8, SPS_FORMAT_U8};
sps_format_t ordered_initial_search_S32_LE[] = {SPS_FORMAT_S32_LE, SPS_FORMAT_S32_BE};
sps_format_t ordered_initial_search_S32_BE[] = {SPS_FORMAT_S32_BE, SPS_FORMAT_S32_LE};
sps_format_t ordered_subsequent_search_S32_LE[] = {SPS_FORMAT_S24_LE, SPS_FORMAT_S24_BE, SPS_FORMAT_S24_3LE, SPS_FORMAT_S24_3BE, SPS_FORMAT_S16_LE, SPS_FORMAT_S16_BE, SPS_FORMAT_S8, SPS_FORMAT_U8};
sps_format_t ordered_subsequent_search_S32_BE[] = {SPS_FORMAT_S24_BE, SPS_FORMAT_S24_LE, SPS_FORMAT_S24_3BE, SPS_FORMAT_S24_3LE, SPS_FORMAT_S16_BE, SPS_FORMAT_S16_LE, SPS_FORMAT_S8, SPS_FORMAT_U8};
// clang-format on
// set up the initial_search pointer
sps_format_t *initial_formats_to_check = NULL;
unsigned int number_of_initial_formats_to_check = 0;
// set up the subsequent_search pointer
sps_format_t *subsequent_formats_to_check = NULL;
unsigned int number_of_subsequent_formats_to_check = 0;
// Set up the initial and subsequent search pointers.
// Searching is done with an initial search and, if necessary, a subsequent search.
// Searching stops when a suitable format is confirmed as being acceptable.
// The initial search checks the requested format first, followed by its equivalents of the
// same bit size. The subsequent search puts the formats to check in order of suitability but
// with the native-endian format first. SPS_FORMAT_S16, SPS_FORMAT_S24 and SPS_FORMAT_S24 are
// treated the same as their equivalent with native endianness. AUTO is treated as
// SPS_FORMAT_S24 with native endianness.
switch (format) {
case SPS_FORMAT_U8:
case SPS_FORMAT_S8:
if (config.endianness == SS_LITTLE_ENDIAN) {
subsequent_formats_to_check = ordered_subsequent_search_8_LE;
number_of_subsequent_formats_to_check =
sizeof(ordered_subsequent_search_8_LE) / sizeof(sps_format_t);
} else {
subsequent_formats_to_check = ordered_subsequent_search_8_BE;
number_of_subsequent_formats_to_check =
sizeof(ordered_subsequent_search_8_BE) / sizeof(sps_format_t);
}
if (format == SPS_FORMAT_S8) {
initial_formats_to_check = ordered_initial_search_S8;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S8) / sizeof(sps_format_t);
} else {
initial_formats_to_check = ordered_initial_search_U8;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_U8) / sizeof(sps_format_t);
}
break;
case SPS_FORMAT_S16:
case SPS_FORMAT_S16_LE:
case SPS_FORMAT_S16_BE:
if (config.endianness == SS_LITTLE_ENDIAN) {
subsequent_formats_to_check = ordered_subsequent_search_S16_LE;
number_of_subsequent_formats_to_check =
sizeof(ordered_subsequent_search_S16_LE) / sizeof(sps_format_t);
if (format == SPS_FORMAT_S16) {
initial_formats_to_check = ordered_initial_search_S16_LE;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S16_LE) / sizeof(sps_format_t);
}
} else {
subsequent_formats_to_check = ordered_subsequent_search_S16_BE;
number_of_subsequent_formats_to_check =
sizeof(ordered_subsequent_search_S16_BE) / sizeof(sps_format_t);
if (format == SPS_FORMAT_S16) {
initial_formats_to_check = ordered_initial_search_S16_BE;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S16_BE) / sizeof(sps_format_t);
}
}
if (format == SPS_FORMAT_S16_LE) {
initial_formats_to_check = ordered_initial_search_S16_LE;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S16_LE) / sizeof(sps_format_t);
} else if (format == SPS_FORMAT_S16_BE) {
initial_formats_to_check = ordered_initial_search_S16_BE;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S16_BE) / sizeof(sps_format_t);
}
break;
case SPS_FORMAT_S24:
case SPS_FORMAT_S24_LE:
case SPS_FORMAT_S24_BE:
if (config.endianness == SS_LITTLE_ENDIAN) {
subsequent_formats_to_check = ordered_subsequent_search_S24_LE;
number_of_subsequent_formats_to_check =
sizeof(ordered_subsequent_search_S24_LE) / sizeof(sps_format_t);
if (format == SPS_FORMAT_S24) {
initial_formats_to_check = ordered_initial_search_S24_LE;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S24_LE) / sizeof(sps_format_t);
}
} else {
subsequent_formats_to_check = ordered_subsequent_search_S24_BE;
number_of_subsequent_formats_to_check =
sizeof(ordered_subsequent_search_S24_BE) / sizeof(sps_format_t);
if (format == SPS_FORMAT_S24) {
initial_formats_to_check = ordered_initial_search_S24_BE;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S24_BE) / sizeof(sps_format_t);
}
}
if (format == SPS_FORMAT_S24_LE) {
initial_formats_to_check = ordered_initial_search_S24_LE;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S24_LE) / sizeof(sps_format_t);
} else if (format == SPS_FORMAT_S24_BE) {
initial_formats_to_check = ordered_initial_search_S24_BE;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S24_BE) / sizeof(sps_format_t);
}
break;
case SPS_FORMAT_S32:
case SPS_FORMAT_S32_LE:
case SPS_FORMAT_S32_BE:
// case SPS_FORMAT_AUTO:
if (config.endianness == SS_LITTLE_ENDIAN) {
subsequent_formats_to_check = ordered_subsequent_search_S32_LE;
number_of_subsequent_formats_to_check =
sizeof(ordered_subsequent_search_S32_LE) / sizeof(sps_format_t);
if ((format == SPS_FORMAT_S32) || (format == SPS_FORMAT_AUTO)) {
initial_formats_to_check = ordered_initial_search_S32_LE;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S32_LE) / sizeof(sps_format_t);
}
} else {
subsequent_formats_to_check = ordered_subsequent_search_S32_BE;
number_of_subsequent_formats_to_check =
sizeof(ordered_subsequent_search_S32_BE) / sizeof(sps_format_t);
if ((format == SPS_FORMAT_S32) || (format == SPS_FORMAT_AUTO)) {
initial_formats_to_check = ordered_initial_search_S32_BE;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S32_BE) / sizeof(sps_format_t);
}
}
if (format == SPS_FORMAT_S32_LE) {
initial_formats_to_check = ordered_initial_search_S32_LE;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S32_LE) / sizeof(sps_format_t);
} else if (format == SPS_FORMAT_S32_BE) {
initial_formats_to_check = ordered_initial_search_S32_BE;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S32_BE) / sizeof(sps_format_t);
}
break;
case SPS_FORMAT_S24_3LE:
case SPS_FORMAT_S24_3BE:
if (config.endianness == SS_LITTLE_ENDIAN) {
subsequent_formats_to_check = ordered_subsequent_search_S24_3le;
number_of_subsequent_formats_to_check =
sizeof(ordered_subsequent_search_S24_3le) / sizeof(sps_format_t);
} else {
subsequent_formats_to_check = ordered_subsequent_search_S24_3be;
number_of_subsequent_formats_to_check =
sizeof(ordered_subsequent_search_S24_3be) / sizeof(sps_format_t);
}
if (format == SPS_FORMAT_S24_3LE) {
initial_formats_to_check = ordered_initial_search_S24_3le;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S24_3le) / sizeof(sps_format_t);
} else {
initial_formats_to_check = ordered_initial_search_S24_3be;
number_of_initial_formats_to_check =
sizeof(ordered_initial_search_S24_3be) / sizeof(sps_format_t);
}
break;
default:
debug(1, "unknown format %u to check.", format);
break;
}
if ((initial_formats_to_check == NULL) || (subsequent_formats_to_check == NULL)) {
debug(1, "error initialising for format %s.", sps_format_description_string(format));
}
// here, we know the channel and rate are permissible according to the settings,
// but we don't yet know about each of the formats
// do initial search
unsigned int i = 0;
while ((i < number_of_initial_formats_to_check) && (response == SPS_FORMAT_UNKNOWN)) {
// only call check_configuration() if the format is permissible
if ((((1 << initial_formats_to_check[i]) & config.format_set) != 0) &&
((check_configuration == NULL) ||
(check_configuration(channels, rate, initial_formats_to_check[i]) == 0)))
response = initial_formats_to_check[i];
else
i++;
}
// if no joy, do subsequent search
if (response == 0) {
i = 0;
while ((i < number_of_subsequent_formats_to_check) && (response == SPS_FORMAT_UNKNOWN)) {
// only call check_configuration() if the format is permissible
if ((((1 << subsequent_formats_to_check[i]) & config.format_set) != 0) &&
((check_configuration == NULL) ||
(check_configuration(channels, rate, subsequent_formats_to_check[i]) == 0)))
response = subsequent_formats_to_check[i];
else
i++;
}
}
}
}
return response;
}
/*
sps_format_t all_formats_to_check_le[] = {SPS_FORMAT_S32_LE, SPS_FORMAT_S32_BE, SPS_FORMAT_S24_LE,
SPS_FORMAT_S24_BE, SPS_FORMAT_S24_3LE, SPS_FORMAT_S24_3BE,
SPS_FORMAT_S16_LE, SPS_FORMAT_S16_BE};
// look at little-endian formats first
sps_format_t all_formats_to_check_le[] = {SPS_FORMAT_S32_LE, SPS_FORMAT_S32_BE, SPS_FORMAT_S24_LE,
SPS_FORMAT_S24_BE, SPS_FORMAT_S24_3LE, SPS_FORMAT_S24_3BE,
SPS_FORMAT_S16_LE, SPS_FORMAT_S16_BE};
// look at big-endian formats first
sps_format_t all_formats_to_check_be[] = {SPS_FORMAT_S32_BE, SPS_FORMAT_S32_LE, SPS_FORMAT_S24_BE,
SPS_FORMAT_S24_LE, SPS_FORMAT_S24_3BE, SPS_FORMAT_S24_3LE,
SPS_FORMAT_S16_BE, SPS_FORMAT_S16_LE};
// look at little-endian formats first
sps_format_t s32_formats_to_check_le[] = {SPS_FORMAT_S32_LE, SPS_FORMAT_S32_BE};
// look at big-endian formats first
sps_format_t s32_formats_to_check_be[] = {SPS_FORMAT_S32_BE, SPS_FORMAT_S32_LE};
// look at little-endian formats first
sps_format_t s24_formats_to_check_le[] = {
SPS_FORMAT_S24_LE,
SPS_FORMAT_S24_BE,
SPS_FORMAT_S24_3LE,
SPS_FORMAT_S24_3BE,
};
// look at big-endian formats first
sps_format_t s24_formats_to_check_be[] = {
SPS_FORMAT_S24_BE,
SPS_FORMAT_S24_LE,
SPS_FORMAT_S24_3BE,
SPS_FORMAT_S24_3LE,
};
// look at little-endian formats first
sps_format_t s16_formats_to_check_le[] = {SPS_FORMAT_S16_LE, SPS_FORMAT_S16_BE};
// look at big-endian formats first
sps_format_t s16_formats_to_check_be[] = {SPS_FORMAT_S16_BE, SPS_FORMAT_S16_LE};
sps_format_t single_format_to_check[1];
sps_format_t check_configuration_with_formats(
unsigned int channels, unsigned int rate, sps_format_t format,
int (*check_configuration)(unsigned int channels, unsigned int rate, unsigned int format)) {
sps_format_t response = SPS_FORMAT_UNKNOWN; // this is encoded as zero
// assume there is one single format to check
unsigned int number_of_formats_to_check = 1;
sps_format_t single_format_to_check[] = {format};
sps_format_t *formats_to_check = single_format_to_check;
// now, check to see if there are multiple formats to check, e.g. if the format is given as AUTO
// or if it doesn't specify _LE or _BE. Try to favour the host endianness:
if (config.endianness == SS_LITTLE_ENDIAN) {
switch (format) {
case SPS_FORMAT_AUTO:
case SPS_FORMAT_S32:
case SPS_FORMAT_32_LE:
case SPS_FORMAT_32_BE:
formats_to_check = all_formats_to_check_le;
number_of_formats_to_check = sizeof(all_formats_to_check_le) / sizeof(sps_format_t);
break;
case SPS_FORMAT_S24:
case SPS_FORMAT_S24_LE:
case SPS_FORMAT_S24_BE:
formats_to_check = s32_formats_to_check_le;
number_of_formats_to_check = sizeof(s32_formats_to_check_le) / sizeof(sps_format_t);
break;
case SPS_FORMAT_S24:
formats_to_check = s24_formats_to_check_le;
number_of_formats_to_check = sizeof(s24_formats_to_check_le) / sizeof(sps_format_t);
break;
case SPS_FORMAT_S16:
formats_to_check = s16_formats_to_check_le;
number_of_formats_to_check = sizeof(s16_formats_to_check_le) / sizeof(sps_format_t);
break;
default:
break;
};
} else { // big-endian
switch (format) {
case SPS_FORMAT_AUTO:
formats_to_check = all_formats_to_check_be;
number_of_formats_to_check = sizeof(all_formats_to_check_be) / sizeof(sps_format_t);
break;
case SPS_FORMAT_S32:
formats_to_check = s32_formats_to_check_be;
number_of_formats_to_check = sizeof(s32_formats_to_check_be) / sizeof(sps_format_t);
break;
case SPS_FORMAT_S24:
formats_to_check = s24_formats_to_check_be;
number_of_formats_to_check = sizeof(s24_formats_to_check_be) / sizeof(sps_format_t);
break;
case SPS_FORMAT_S16:
formats_to_check = s16_formats_to_check_be;
number_of_formats_to_check = sizeof(s16_formats_to_check_be) / sizeof(sps_format_t);
break;
default:
break;
};
}
unsigned int i = 0;
while ((i < number_of_formats_to_check) && (response == SPS_FORMAT_UNKNOWN)) {
if (check_configuration(channels, rate, formats_to_check[i]) == 0)
response = formats_to_check[i];
else
i++;
}
return response;
}
*/
// This looks for a suitable configuration in the right order and checks possible
// configurations using the check_configuration method passed in.
// If it's the same as the most recent request, it sends back the cached reply.
// The cache is keyed to the address of the checker method in order to distinguish
// between requests from different backends. It's moot, as only one back end can be
// active, but anyway... ya never just know.
// but it does mean that it only remembers the last response, whichever backend it was for
typedef struct {
int32_t encoded_request; // this will have the channels and the rate requested but not the format
int32_t encoded_response; // this will have the channels, rate and format suggested
int (*check_configuration)(unsigned int channels, unsigned int rate, unsigned int format);
} configuration_request_and_response_t;
static configuration_request_and_response_t search_for_suitable_configuration_last_response = {
0, 0, NULL};
// First it looks for an exact match of channels, rate or with a rate that is an even multiple.
// If that fails it will look for an exact match of channels with the next highest rate
// If that fails it will look for an exact match of channels with the next lowest rate
// If that fails it increases the number channels, if available, and checks all over again
// Finally, if that fails it reduces the number channels, if possible, and checks all over again.
// Regarding the requested format: ask for the exact format only if no audio processing will be
// done in the software or (as far as can be known) in the hardware mixer.
// So, if ignore_volume_control is set and volume_max_db is
// not set, and the number of output channels is greater than or equal to those requested,
// and mixing to mono is not requested and the rate being checked is the rate requested,
// then look for the exact rate format.
// Otherwise ask for the deepest format, which could be useful
// for attenuation, mixing, transcoding or other audio processing.
// (Haven't included checking for convolution here.)
int32_t search_for_suitable_configuration(unsigned int channels, unsigned int rate,
unsigned int format,
int (*check_configuration)(unsigned int in_channels,
unsigned int in_rate,
unsigned int in_format)) {
int32_t reply = 0; // means nothing was found
if ((channels == 0) || (rate == 0))
debug(1, "invalid channel %u or rate %u parameters to search_for_suitable_configuration",
channels, rate);
int32_t encoded_request = CHANNELS_TO_ENCODED_FORMAT(channels) | RATE_TO_ENCODED_FORMAT(rate) |
FORMAT_TO_ENCODED_FORMAT(format);
if ((search_for_suitable_configuration_last_response.check_configuration ==
check_configuration) && // same method (implies same backend)
(search_for_suitable_configuration_last_response.encoded_request ==
encoded_request)) { // same request
reply = search_for_suitable_configuration_last_response
.encoded_response; // provide cached response...
}
if (reply == 0) { // no luck with the last response generated, if any...
// check for native number of channels or more... and then, if not successful, with fewer
// channels
#ifdef CONFIG_FFMPEG
unsigned int channel_count_check_sequence[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1};
unsigned int rates[] = {44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000,
64000, 32000, 22050, 16000, 11025, 8000, 5512};
#else
unsigned int channel_count_check_sequence[] = {0, 1, 2};
// unsigned int rates[] = {44100};
#endif
unsigned int channel_count_index = channels;
// start looking with the required number of channels
unsigned int local_channels;
unsigned int local_rate;
sps_format_t local_format = SPS_FORMAT_UNKNOWN;
uint32_t channel_check_bit_field = 0; // set (1 << channel_count) when check done.
// Use it to stop checking the same channel count on the way down as well as on the way up...
while ((local_format == SPS_FORMAT_UNKNOWN) &&
(channel_count_index < (sizeof(channel_count_check_sequence) / sizeof(unsigned int)))) {
local_channels = channel_count_check_sequence[channel_count_index];
// if we haven't already checked....
if ((channel_check_bit_field & (1 << local_channels)) == 0) {
channel_check_bit_field |= (1 << local_channels); // flag that we have checked it
int rate_multiplier = 2;
local_rate = rate; // begin with the requested rate
debug(3, "check for an exact multiple of %u with %u channels.", rate, local_channels);
while ((local_rate <= 384000) && (local_format == SPS_FORMAT_UNKNOWN)) {
if (
// clang-format off
// check for the exact format only under these conditions, otherwise look for the best
(config.ignore_volume_control != 0) &&
(config.volume_max_db_set == 0) &&
(local_rate == rate) &&
(local_channels >= channels) &&
(config.playback_mode != ST_mono)
// clang-format on
) {
// debug(1, "check exact");
local_format = check_configuration_with_formats(
local_channels, local_rate, (sps_format_t)format, check_configuration);
} else {
// debug(1, "check best");
local_format = check_configuration_with_formats(local_channels, local_rate,
SPS_FORMAT_S32, check_configuration);
}
if (local_format == SPS_FORMAT_UNKNOWN) {
local_rate = rate * rate_multiplier;
rate_multiplier = rate_multiplier * 2;
}
}
#ifdef CONFIG_FFMPEG
if (local_format == SPS_FORMAT_UNKNOWN) {
debug(3, "check for the next highest rate above %u with %u channels.", rate,
local_channels);
unsigned int rate_pointer = 0;
while ((rate_pointer < sizeof(rates) / sizeof(unsigned int)) &&
(local_format == SPS_FORMAT_UNKNOWN)) {
local_rate = rates[rate_pointer];
if (local_rate > rate) {
local_format = check_configuration_with_formats(
local_channels, local_rate, (sps_format_t)format, check_configuration);
}
if (local_format == SPS_FORMAT_UNKNOWN) {
rate_pointer++;
}
}
}
if (local_format == SPS_FORMAT_UNKNOWN) {
int rate_pointer = (int)(sizeof(rates) / sizeof(unsigned int) - 1);
debug(3, "check for the next lowest rate below %u with %u channels.", rate,
local_channels);
while ((rate_pointer >= 0) && (local_format == SPS_FORMAT_UNKNOWN)) {
local_rate = rates[rate_pointer];
if (local_rate < rate) {
local_format = check_configuration_with_formats(
local_channels, local_rate, (sps_format_t)format, check_configuration);
}
if (local_format == SPS_FORMAT_UNKNOWN) {
rate_pointer--;
}
}
}
#endif
}
// if unsuccessful, try with, firstly, more channels, and then, later, with fewer channels
if (local_format == SPS_FORMAT_UNKNOWN)
channel_count_index++;
}
if (local_format != SPS_FORMAT_UNKNOWN) {
reply = CHANNELS_TO_ENCODED_FORMAT(local_channels) | RATE_TO_ENCODED_FORMAT(local_rate) |
FORMAT_TO_ENCODED_FORMAT(local_format);
// cache the response
search_for_suitable_configuration_last_response.check_configuration = check_configuration;
search_for_suitable_configuration_last_response.encoded_request = encoded_request;
search_for_suitable_configuration_last_response.encoded_response = reply;
debug(3,
"output configuration search for request: %s/%u/%u, succeeded with response: %u/%s/%u.",
sps_format_description_string(format), rate, channels,
RATE_FROM_ENCODED_FORMAT(reply), // rate
sps_format_description_string(FORMAT_FROM_ENCODED_FORMAT(reply)), // format
CHANNELS_FROM_ENCODED_FORMAT(reply) // channels
);
} else {
debug(1, "output configuration search for request: %s/%u/%u failed.",
sps_format_description_string(format), rate, channels);
}
}
return reply;
}