diff --git a/spa/plugins/alsa/alsa-pcm-sink.c b/spa/plugins/alsa/alsa-pcm-sink.c index 1bb52099b..fcdf16ba6 100644 --- a/spa/plugins/alsa/alsa-pcm-sink.c +++ b/spa/plugins/alsa/alsa-pcm-sink.c @@ -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")) { diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 299bf03ff..95f12bb0f 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -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(¶ms); @@ -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; +} - fmt = spa_pod_builder_pop(&b, &f[0]); +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(¶ms); + 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; + } + + 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; diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 01aadc863..081184809 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -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 diff --git a/src/daemon/media-session.d/alsa-monitor.conf b/src/daemon/media-session.d/alsa-monitor.conf index f44f97d90..764af1bc8 100644 --- a/src/daemon/media-session.d/alsa-monitor.conf +++ b/src/daemon/media-session.d/alsa-monitor.conf @@ -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 ] } } }