alsa: add enumeration of iec958 format

For hdmi and iec958 devices, enumerate the iec958 formats and
codecs. Initially only PCM is supported as a codec but with
a property or an init option, the list of codecs can be dynamically
configured.
This commit is contained in:
Wim Taymans 2021-08-12 14:08:20 +02:00
parent 8147772cf5
commit 9dfe35b17c
4 changed files with 267 additions and 71 deletions

View file

@ -100,7 +100,7 @@ static int impl_node_enum_params(void *object, int seq,
struct state *this = object;
struct spa_pod *param;
struct spa_pod_builder b = { 0 };
uint8_t buffer[1024];
uint8_t buffer[2048];
struct spa_result_node_params result;
uint32_t count = 0;
@ -169,6 +169,17 @@ static int impl_node_enum_params(void *object, int seq,
SPA_PROP_INFO_name, SPA_POD_String("Latency offset (ns)"),
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(0, 0, INT64_MAX));
break;
case 7:
if (this->is_iec958 || this->is_hdmi) {
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_iec958Codecs),
SPA_PROP_INFO_name, SPA_POD_String("Enabled IEC958 (S/PDIF) codecs"),
SPA_PROP_INFO_type, SPA_POD_Id(SPA_AUDIO_IEC958_CODEC_UNKNOWN),
SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array));
break;
}
SPA_FALLTHROUGH
default:
return 0;
}
@ -177,18 +188,30 @@ static int impl_node_enum_params(void *object, int seq,
case SPA_PARAM_Props:
{
struct props *p = &this->props;
struct spa_pod_frame f;
uint32_t codecs[16], n_codecs;
switch (result.index) {
case 0:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, id,
spa_pod_builder_push_object(&b, &f,
SPA_TYPE_OBJECT_Props, id);
spa_pod_builder_add(&b,
SPA_PROP_device, SPA_POD_Stringn(p->device, sizeof(p->device)),
SPA_PROP_deviceName, SPA_POD_Stringn(p->device_name, sizeof(p->device_name)),
SPA_PROP_cardName, SPA_POD_Stringn(p->card_name, sizeof(p->card_name)),
SPA_PROP_minLatency, SPA_POD_Int(p->min_latency),
SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency),
SPA_PROP_START_CUSTOM, SPA_POD_Bool(p->use_chmap),
SPA_PROP_latencyOffsetNsec, SPA_POD_Long(this->process_latency.ns));
SPA_PROP_latencyOffsetNsec, SPA_POD_Long(this->process_latency.ns),
0);
n_codecs = spa_alsa_get_iec958_codecs(this, codecs, SPA_N_ELEMENTS(codecs));
if (n_codecs > 0) {
spa_pod_builder_prop(&b, SPA_PROP_iec958Codecs, 0);
spa_pod_builder_array(&b, sizeof(uint32_t), SPA_TYPE_Id,
n_codecs, codecs);
}
param = spa_pod_builder_pop(&b, &f);
break;
default:
return 0;
@ -296,6 +319,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
{
struct props *p = &this->props;
struct spa_process_latency_info info;
struct spa_pod *iec958_codecs = NULL;
if (param == NULL) {
reset_props(p);
@ -310,8 +334,17 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
SPA_PROP_minLatency, SPA_POD_OPT_Int(&p->min_latency),
SPA_PROP_maxLatency, SPA_POD_OPT_Int(&p->max_latency),
SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&info.ns),
SPA_PROP_START_CUSTOM, SPA_POD_OPT_Bool(&p->use_chmap));
SPA_PROP_START_CUSTOM, SPA_POD_OPT_Bool(&p->use_chmap),
SPA_PROP_iec958Codecs, SPA_POD_OPT_Pod(&iec958_codecs));
if (iec958_codecs != NULL) {
uint32_t i, codecs[16], n_codecs;
n_codecs = spa_pod_copy_array(iec958_codecs, SPA_TYPE_Id,
codecs, SPA_N_ELEMENTS(codecs));
this->iec958_codecs = 0;
for (i = 0; i < n_codecs; i++)
this->iec958_codecs |= 1ULL << codecs[i];
}
handle_process_latency(this, &info);
break;
}
@ -916,6 +949,8 @@ impl_init(const struct spa_handle_factory *factory,
this->process_latency.rate = atoi(s);
} else if (spa_streq(k, "latency.internal.ns")) {
this->process_latency.ns = atoi(s);
} else if (spa_streq(k, "iec958.codecs")) {
spa_alsa_parse_iec958_codecs(&this->iec958_codecs, s, strlen(s));
} else if (spa_streq(k, "api.alsa.period-size")) {
this->default_period_size = atoi(s);
} else if (spa_streq(k, "api.alsa.headroom")) {

View file

@ -22,6 +22,12 @@ int spa_alsa_init(struct state *state)
snd_config_update_free_global();
if (state->stream == SND_PCM_STREAM_PLAYBACK) {
state->is_iec958 = spa_strstartswith(state->props.device, "iec958");
state->is_hdmi = spa_strstartswith(state->props.device, "hdmi");
state->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_PCM;
}
if (state->open_ucm) {
char card_name[64];
const char *alibpref = NULL;
@ -57,6 +63,7 @@ int spa_alsa_init(struct state *state)
free((void*)alibpref);
}
}
return 0;
}
@ -313,40 +320,21 @@ static void sanitize_map(snd_pcm_chmap_t* map)
}
}
int
spa_alsa_enum_format(struct state *state, int seq, uint32_t start, uint32_t num,
const struct spa_pod *filter)
static int enum_pcm_formats(struct state *state, uint32_t index, uint32_t *next,
struct spa_pod **result, struct spa_pod_builder *b)
{
int err, dir;
size_t i, j;
snd_pcm_t *hndl;
snd_pcm_hw_params_t *params;
snd_pcm_chmap_query_t **maps;
struct spa_pod_frame f[2];
snd_pcm_format_mask_t *fmask;
snd_pcm_access_mask_t *amask;
snd_pcm_chmap_query_t **maps;
size_t i, j;
int err, dir;
unsigned int min, max;
unsigned int rrate, rchannels;
uint8_t buffer[4096];
struct spa_pod_builder b = { 0 };
struct spa_pod_choice *choice;
struct spa_pod *fmt;
int res;
bool opened;
struct spa_pod_frame f[2];
struct spa_result_node_params result;
uint32_t count = 0, rate;
opened = state->opened;
if ((err = spa_alsa_open(state)) < 0)
return err;
result.id = SPA_PARAM_EnumFormat;
result.next = start;
next:
result.index = result.next++;
spa_pod_builder_init(&b, buffer, sizeof(buffer));
uint32_t rate;
hndl = state->hndl;
snd_pcm_hw_params_alloca(&params);
@ -371,8 +359,8 @@ spa_alsa_enum_format(struct state *state, int seq, uint32_t start, uint32_t num,
}
}
spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
spa_pod_builder_add(&b,
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
spa_pod_builder_add(b,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
0);
@ -383,10 +371,10 @@ spa_alsa_enum_format(struct state *state, int seq, uint32_t start, uint32_t num,
snd_pcm_access_mask_alloca(&amask);
snd_pcm_hw_params_get_access_mask(params, amask);
spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_format, 0);
spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_format, 0);
spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_None, 0);
choice = (struct spa_pod_choice*)spa_pod_builder_frame(&b, &f[1]);
spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0);
choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]);
for (i = 1, j = 0; i < SPA_N_ELEMENTS(format_info); i++) {
const struct format_info *fi = &format_info[i];
@ -397,15 +385,15 @@ spa_alsa_enum_format(struct state *state, int seq, uint32_t start, uint32_t num,
fi->spa_pformat != SPA_AUDIO_FORMAT_UNKNOWN &&
(state->default_format == 0 || state->default_format == fi->spa_pformat)) {
if (j++ == 0)
spa_pod_builder_id(&b, fi->spa_pformat);
spa_pod_builder_id(&b, fi->spa_pformat);
spa_pod_builder_id(b, fi->spa_pformat);
spa_pod_builder_id(b, fi->spa_pformat);
}
if ((snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_MMAP_INTERLEAVED) ||
snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_RW_INTERLEAVED)) &&
(state->default_format == 0 || state->default_format == fi->spa_format)) {
if (j++ == 0)
spa_pod_builder_id(&b, fi->spa_format);
spa_pod_builder_id(&b, fi->spa_format);
spa_pod_builder_id(b, fi->spa_format);
spa_pod_builder_id(b, fi->spa_format);
}
}
}
@ -440,7 +428,7 @@ spa_alsa_enum_format(struct state *state, int seq, uint32_t start, uint32_t num,
}
if (j > 1)
choice->body.type = SPA_CHOICE_Enum;
spa_pod_builder_pop(&b, &f[1]);
spa_pod_builder_pop(b, &f[1]);
CHECK(snd_pcm_hw_params_get_rate_min(params, &min, &dir), "get_rate_min");
CHECK(snd_pcm_hw_params_get_rate_max(params, &max, &dir), "get_rate_max");
@ -452,20 +440,20 @@ spa_alsa_enum_format(struct state *state, int seq, uint32_t start, uint32_t num,
max = state->default_rate;
}
spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_rate, 0);
spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0);
spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_None, 0);
choice = (struct spa_pod_choice*)spa_pod_builder_frame(&b, &f[1]);
spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0);
choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]);
rate = state->position ? state->position->clock.rate.denom : DEFAULT_RATE;
spa_pod_builder_int(&b, SPA_CLAMP(rate, min, max));
spa_pod_builder_int(b, SPA_CLAMP(rate, min, max));
if (min != max) {
spa_pod_builder_int(&b, min);
spa_pod_builder_int(&b, max);
spa_pod_builder_int(b, min);
spa_pod_builder_int(b, max);
choice->body.type = SPA_CHOICE_Range;
}
spa_pod_builder_pop(&b, &f[1]);
spa_pod_builder_pop(b, &f[1]);
CHECK(snd_pcm_hw_params_get_channels_min(params, &min), "get_channels_min");
CHECK(snd_pcm_hw_params_get_channels_max(params, &max), "get_channels_max");
@ -480,55 +468,55 @@ spa_alsa_enum_format(struct state *state, int seq, uint32_t start, uint32_t num,
min = SPA_MIN(min, SPA_AUDIO_MAX_CHANNELS);
max = SPA_MIN(max, SPA_AUDIO_MAX_CHANNELS);
spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_channels, 0);
spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_channels, 0);
if (state->props.use_chmap && (maps = snd_pcm_query_chmaps(hndl)) != NULL) {
uint32_t channel;
snd_pcm_chmap_t* map;
skip_channels:
if (maps[result.index] == NULL) {
if (maps[index] == NULL) {
snd_pcm_free_chmaps(maps);
goto enum_end;
return 0;
}
map = &maps[result.index]->map;
map = &maps[index]->map;
spa_log_debug(state->log, "map %d channels (%d %d)", map->channels, min, max);
if (map->channels < min || map->channels > max) {
result.index = result.next++;
index = (*next)++;
goto skip_channels;
}
sanitize_map(map);
spa_pod_builder_int(&b, map->channels);
spa_pod_builder_int(b, map->channels);
spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_position, 0);
spa_pod_builder_push_array(&b, &f[1]);
spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_position, 0);
spa_pod_builder_push_array(b, &f[1]);
for (j = 0; j < map->channels; j++) {
spa_log_debug(state->log, NAME" %p: position %zd %d", state, j, map->pos[j]);
channel = chmap_position_to_channel(map->pos[j]);
spa_pod_builder_id(&b, channel);
spa_pod_builder_id(b, channel);
}
spa_pod_builder_pop(&b, &f[1]);
spa_pod_builder_pop(b, &f[1]);
snd_pcm_free_chmaps(maps);
}
else {
const struct channel_map *map = NULL;
if (result.index > 0)
goto enum_end;
if (index > 0)
return 0;
spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_None, 0);
choice = (struct spa_pod_choice*)spa_pod_builder_frame(&b, &f[1]);
spa_pod_builder_int(&b, max);
spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0);
choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]);
spa_pod_builder_int(b, max);
if (min != max) {
spa_pod_builder_int(&b, min);
spa_pod_builder_int(&b, max);
spa_pod_builder_int(b, min);
spa_pod_builder_int(b, max);
choice->body.type = SPA_CHOICE_Range;
}
spa_pod_builder_pop(&b, &f[1]);
spa_pod_builder_pop(b, &f[1]);
if (min == max) {
if (state->default_pos.channels == min)
@ -537,17 +525,147 @@ skip_channels:
map = &default_map[min];
}
if (map) {
spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_position, 0);
spa_pod_builder_push_array(&b, &f[1]);
spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_position, 0);
spa_pod_builder_push_array(b, &f[1]);
for (j = 0; j < map->channels; j++) {
spa_log_debug(state->log, NAME" %p: position %zd %d", state, j, map->pos[j]);
spa_pod_builder_id(&b, map->pos[j]);
spa_pod_builder_id(b, map->pos[j]);
}
spa_pod_builder_pop(&b, &f[1]);
spa_pod_builder_pop(b, &f[1]);
}
}
*result = spa_pod_builder_pop(b, &f[0]);
return 1;
}
static int enum_iec958_formats(struct state *state, uint32_t index, uint32_t *next,
struct spa_pod **result, struct spa_pod_builder *b)
{
int err, dir;
snd_pcm_t *hndl;
snd_pcm_hw_params_t *params;
struct spa_pod_frame f[2];
struct spa_pod_choice *choice;
unsigned int rmin, rmax;
unsigned int chmin, chmax;
if ((index & 0xffff) > 0)
return 0;
if (!(state->is_iec958 || state->is_hdmi))
return 0;
if (state->iec958_codecs == 0)
return 0;
hndl = state->hndl;
snd_pcm_hw_params_alloca(&params);
CHECK(snd_pcm_hw_params_any(hndl, params), "Broken configuration: no configurations available");
CHECK(snd_pcm_hw_params_set_rate_resample(hndl, params, 0), "set_rate_resample");
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
spa_pod_builder_add(b,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_iec958),
0);
CHECK(snd_pcm_hw_params_get_channels_min(params, &chmin), "get_channels_min");
CHECK(snd_pcm_hw_params_get_channels_max(params, &chmax), "get_channels_max");
spa_log_debug(state->log, "channels (%d %d)", chmin, chmax);
CHECK(snd_pcm_hw_params_get_rate_min(params, &rmin, &dir), "get_rate_min");
CHECK(snd_pcm_hw_params_get_rate_max(params, &rmax, &dir), "get_rate_max");
spa_log_debug(state->log, "rate (%d %d)", rmin, rmax);
if (state->default_rate != 0) {
if (rmin < state->default_rate)
rmin = state->default_rate;
if (rmax > state->default_rate)
rmax = state->default_rate;
}
fmt = spa_pod_builder_pop(&b, &f[0]);
spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_iec958Codec, 0);
spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0);
if (chmax >= 2) {
spa_pod_builder_id(b, SPA_AUDIO_IEC958_CODEC_PCM);
spa_pod_builder_id(b, SPA_AUDIO_IEC958_CODEC_PCM);
if (state->iec958_codecs & (1ULL << SPA_AUDIO_IEC958_CODEC_DTS))
spa_pod_builder_id(b, SPA_AUDIO_IEC958_CODEC_DTS);
if (state->iec958_codecs & (1ULL << SPA_AUDIO_IEC958_CODEC_AC3))
spa_pod_builder_id(b, SPA_AUDIO_IEC958_CODEC_AC3);
if (state->iec958_codecs & (1ULL << SPA_AUDIO_IEC958_CODEC_MPEG))
spa_pod_builder_id(b, SPA_AUDIO_IEC958_CODEC_MPEG);
if (state->iec958_codecs & (1ULL << SPA_AUDIO_IEC958_CODEC_MPEG2_AAC))
spa_pod_builder_id(b, SPA_AUDIO_IEC958_CODEC_MPEG2_AAC);
if (rmax >= 48000 * 4 &&
(state->iec958_codecs & (1ULL << SPA_AUDIO_IEC958_CODEC_EAC3)))
spa_pod_builder_id(b, SPA_AUDIO_IEC958_CODEC_EAC3);
}
if (chmax >= 8) {
if (state->iec958_codecs & (1ULL << SPA_AUDIO_IEC958_CODEC_TRUEHD))
spa_pod_builder_id(b, SPA_AUDIO_IEC958_CODEC_TRUEHD);
if (state->iec958_codecs & (1ULL << SPA_AUDIO_IEC958_CODEC_DTSHD))
spa_pod_builder_id(b, SPA_AUDIO_IEC958_CODEC_DTSHD);
}
spa_pod_builder_pop(b, &f[1]);
spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0);
spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0);
choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]);
spa_pod_builder_int(b, SPA_CLAMP(DEFAULT_RATE, rmin, rmax));
if (rmin != rmax) {
spa_pod_builder_int(b, rmin);
spa_pod_builder_int(b, rmax);
choice->body.type = SPA_CHOICE_Range;
}
spa_pod_builder_pop(b, &f[1]);
(*next)++;
*result = spa_pod_builder_pop(b, &f[0]);
return 1;
}
int
spa_alsa_enum_format(struct state *state, int seq, uint32_t start, uint32_t num,
const struct spa_pod *filter)
{
uint8_t buffer[4096];
struct spa_pod_builder b = { 0 };
struct spa_pod *fmt;
int err, res;
bool opened;
struct spa_result_node_params result;
uint32_t count = 0;
opened = state->opened;
if ((err = spa_alsa_open(state)) < 0)
return err;
result.id = SPA_PARAM_EnumFormat;
result.next = start;
next:
result.index = result.next++;
spa_pod_builder_init(&b, buffer, sizeof(buffer));
if (result.index < 0x10000) {
if ((res = enum_pcm_formats(state, result.index, &result.next, &fmt, &b)) != 1) {
result.next = 0x10000;
goto next;
}
}
else if (result.index < 0x20000) {
if ((res = enum_iec958_formats(state, result.index, &result.next, &fmt, &b)) != 1) {
result.next = 0x20000;
goto next;
}
}
else
goto enum_end;
if (spa_pod_filter(&b, &result.param, fmt, filter) < 0)
goto next;

View file

@ -185,6 +185,10 @@ struct state {
unsigned int planar:1;
unsigned int freewheel:1;
unsigned int open_ucm:1;
unsigned int is_iec958:1;
unsigned int is_hdmi:1;
uint64_t iec958_codecs;
int64_t sample_count;
@ -262,6 +266,44 @@ static inline void spa_alsa_parse_position(struct channel_map *map, const char *
}
}
static inline uint32_t spa_alsa_iec958_codec_from_name(const char *name)
{
int i;
for (i = 0; spa_type_audio_iec958_codec[i].name; i++) {
if (strcmp(name, spa_debug_type_short_name(spa_type_audio_iec958_codec[i].name)) == 0)
return spa_type_audio_iec958_codec[i].type;
}
return SPA_AUDIO_IEC958_CODEC_UNKNOWN;
}
static inline void spa_alsa_parse_iec958_codecs(uint64_t *codecs, const char *val, size_t len)
{
struct spa_json it[2];
char v[256];
spa_json_init(&it[0], val, len);
if (spa_json_enter_array(&it[0], &it[1]) <= 0)
spa_json_init(&it[1], val, len);
*codecs = 0;
while (spa_json_get_string(&it[1], v, sizeof(v)) > 0)
*codecs |= 1ULL << spa_alsa_iec958_codec_from_name(v);
}
static inline uint32_t spa_alsa_get_iec958_codecs(struct state *state, uint32_t *codecs,
uint32_t max_codecs)
{
uint64_t mask = state->iec958_codecs;
uint32_t i = 0, j = 0;
while (mask && i < max_codecs) {
if (mask & 1)
codecs[i++] = j;
mask >>= 1;
j++;
}
return i;
}
#ifdef __cplusplus
} /* extern "C" */
#endif

View file

@ -111,6 +111,7 @@ rules = [
#api.alsa.disable-mmap = false
#api.alsa.disable-batch = false
#api.alsa.use-chmap = false
#iec958.codecs = [ PCM DTS AC3 MPEG MPEG2-AAC EAC3 TrueHD DTS-HD ]
}
}
}