Set variables for a Docker build (used by another workflow). / Set variables required for docker build. (push) Successful in 1s
Build and conditionally push docker image / docker-vars (push) Successful in 2s
Build and conditionally push docker image / Build and conditionally push docker image (SHAIRPORT_SYNC_BRANCH=.
, ./docker/classic/Dockerfile, classic) (push) Failing after 31m52s
Build and conditionally push docker image / Build and conditionally push docker image (SHAIRPORT_SYNC_BRANCH=.
NQPTP_BRANCH=${{ needs.docker-vars.outputs.nqptp_branch }}
, ./docker/Dockerfile, main) (push) Failing after 31m49s
Streams AirPlay audio to OpenHome/Songcast receivers as OHU unicast PCM (16/24-bit). Sends are paced on the PTP master clock so receivers slave to the AirPlay group clock through their buffer-fill servo; MediaLatency carries the phase, matching the wire behaviour of Linn's own sender (zero flags and timestamps). delay() reports the newest frame's playtime lead so shairport feeds a receiver latency ahead. Handles Join, Listen, Leave and Resend with a frame history, and forwards track metadata and metatext from the metadata hub. With -r <name>, ohm_upnp controls the receiver over UPnP: discovered by friendly name via SSDP at time of use, woken, switched to its Songcast source and pointed at this sender when a session starts; volume follows the AirPlay slider capped at the device's VolumeLimit; standby follows some minutes after the session ends.
1064 lines
46 KiB
C
1064 lines
46 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
|
|
#ifdef CONFIG_OHM
|
|
extern audio_output audio_ohm;
|
|
#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
|
|
#ifdef CONFIG_OHM
|
|
&audio_ohm,
|
|
#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] <= SPS_GREATEST_CHANNEL_COUNT)) {
|
|
#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 <= SPS_GREATEST_CHANNEL_COUNT; 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)) {
|
|
// first, if we in a circumstance where no further audio processing,
|
|
// e.g. volume adjustment or convolution is required, check_
|
|
// for an exact rate and bit depth match with the same or more channels
|
|
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) &&
|
|
#ifdef CONFIG_CONVOLUTION
|
|
(config.convolution_enabled == 0) &&
|
|
#endif
|
|
(config.loudness_enabled == 0) &&
|
|
(local_rate == rate) &&
|
|
(local_channels >= channels) &&
|
|
(config.playback_mode != ST_mono)
|
|
// clang-format on
|
|
) {
|
|
// debug(1, "first, check exact rate and format");
|
|
local_format = check_configuration_with_formats(
|
|
local_channels, local_rate, (sps_format_t)format, check_configuration);
|
|
}
|
|
|
|
if (local_format == SPS_FORMAT_UNKNOWN) {
|
|
// debug(1, "check best, with any format");
|
|
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 and the best format.", 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_S32, 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 and the best format.", 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_S32, 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;
|
|
}
|