diff options
Diffstat (limited to 'audio')
-rw-r--r-- | audio/audio.h | 2 | ||||
-rw-r--r-- | audio/chmap.c | 28 | ||||
-rw-r--r-- | audio/chmap.h | 1 | ||||
-rw-r--r-- | audio/chmap_sel.c | 11 | ||||
-rw-r--r-- | audio/chmap_sel.h | 1 | ||||
-rw-r--r-- | audio/decode/ad_lavc.c | 15 | ||||
-rw-r--r-- | audio/decode/ad_spdif.c | 7 | ||||
-rw-r--r-- | audio/decode/dec_audio.c | 21 | ||||
-rw-r--r-- | audio/filter/af_lavcac3enc.c | 37 | ||||
-rw-r--r-- | audio/filter/af_scaletempo.c | 3 | ||||
-rw-r--r-- | audio/format.c | 2 | ||||
-rw-r--r-- | audio/out/ao_alsa.c | 430 | ||||
-rw-r--r-- | audio/out/ao_jack.c | 6 |
13 files changed, 288 insertions, 276 deletions
diff --git a/audio/audio.h b/audio/audio.h index bf5358274a..c74d0f778c 100644 --- a/audio/audio.h +++ b/audio/audio.h @@ -37,6 +37,8 @@ struct mp_audio { int num_planes; // number of planes int bps; // size of sub-samples (af_fmt_to_bytes(format)) + double pts; // currently invalid within the filter chain + // --- private // These do not necessarily map directly to planes[]. They can have // different order or count. There shouldn't be more buffers than planes. diff --git a/audio/chmap.c b/audio/chmap.c index fb98be52df..e0f485c4dc 100644 --- a/audio/chmap.c +++ b/audio/chmap.c @@ -93,7 +93,7 @@ static const char *const std_layout_names[][2] = { {"7.1(alsa)", "fl-fr-bl-br-fc-lfe-sl-sr"}, // not in lavc {"7.1(wide)", "fl-fr-fc-lfe-bl-br-flc-frc"}, {"7.1(wide-side)", "fl-fr-fc-lfe-flc-frc-sl-sr"}, - {"7.1(rear)", "fl-fr-fc-lfe-bl-br-sdl-sdr"}, + {"7.1(rear)", "fl-fr-fc-lfe-bl-br-sdl-sdr"}, // not in lavc {"octagonal", "fl-fr-fc-bl-br-bc-sl-sr"}, {"downmix", "dl-dr"}, {"auto", ""}, // not in lavc @@ -112,19 +112,6 @@ static const struct mp_chmap default_layouts[] = { MP_CHMAP8(FL, FR, FC, LFE, BL, BR, SL, SR), // 7.1 }; -// The channel order was lavc/waveex, but differs from lavc for 5, 6 and 8 -// channels. 3 and 7 channels were likely undefined (no ALSA support). -// I'm not sure about the 4 channel case: ALSA uses "quad", while the ffmpeg -// default layout is "4.0". -static const char *const mplayer_layouts[MP_NUM_CHANNELS + 1] = { - [1] = "mono", - [2] = "stereo", - [4] = "quad", - [5] = "5.0(alsa)", - [6] = "5.1(alsa)", - [8] = "7.1(alsa)", -}; - // Returns true if speakers are mapped uniquely, and there's at least 1 channel. bool mp_chmap_is_valid(const struct mp_chmap *src) { @@ -227,19 +214,6 @@ void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels) mp_chmap_set_unknown(dst, num_channels); } -// Try to do what mplayer/mplayer2/mpv did before channel layouts were -// introduced, i.e. get the old default channel order. -void mp_chmap_from_channels_alsa(struct mp_chmap *dst, int num_channels) -{ - if (num_channels < 0 || num_channels > MP_NUM_CHANNELS) { - *dst = (struct mp_chmap) {0}; - } else { - mp_chmap_from_str(dst, bstr0(mplayer_layouts[num_channels])); - if (!dst->num) - mp_chmap_from_channels(dst, num_channels); - } -} - // Set *dst to an unknown layout for the given numbers of channels. // If the number of channels is invalid, an invalid map is set, and // mp_chmap_is_valid(dst) will return false. diff --git a/audio/chmap.h b/audio/chmap.h index 77769ec3ff..b32c63b6fc 100644 --- a/audio/chmap.h +++ b/audio/chmap.h @@ -108,7 +108,6 @@ void mp_chmap_fill_na(struct mp_chmap *map, int num); void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels); void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels); -void mp_chmap_from_channels_alsa(struct mp_chmap *dst, int num_channels); void mp_chmap_remove_useless_channels(struct mp_chmap *map, const struct mp_chmap *requested); diff --git a/audio/chmap_sel.c b/audio/chmap_sel.c index 51a3eecf8b..8ddb7131e7 100644 --- a/audio/chmap_sel.c +++ b/audio/chmap_sel.c @@ -95,17 +95,6 @@ void mp_chmap_sel_add_waveext(struct mp_chmap_sel *s) s->allow_waveext = true; } -// Classic ALSA-based MPlayer layouts. -void mp_chmap_sel_add_alsa_def(struct mp_chmap_sel *s) -{ - for (int n = 1; n <= MP_NUM_CHANNELS; n++) { - struct mp_chmap t; - mp_chmap_from_channels_alsa(&t, n); - if (t.num) - mp_chmap_sel_add_map(s, &t); - } -} - // Add a channel map that should be allowed. void mp_chmap_sel_add_map(struct mp_chmap_sel *s, const struct mp_chmap *map) { diff --git a/audio/chmap_sel.h b/audio/chmap_sel.h index 786f06f239..12ded3b466 100644 --- a/audio/chmap_sel.h +++ b/audio/chmap_sel.h @@ -37,7 +37,6 @@ struct mp_chmap_sel { void mp_chmap_sel_add_any(struct mp_chmap_sel *s); void mp_chmap_sel_add_waveext(struct mp_chmap_sel *s); void mp_chmap_sel_add_waveext_def(struct mp_chmap_sel *s); -void mp_chmap_sel_add_alsa_def(struct mp_chmap_sel *s); void mp_chmap_sel_add_map(struct mp_chmap_sel *s, const struct mp_chmap *map); void mp_chmap_sel_add_speaker(struct mp_chmap_sel *s, int id); bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map); diff --git a/audio/decode/ad_lavc.c b/audio/decode/ad_lavc.c index ce628877be..3188834830 100644 --- a/audio/decode/ad_lavc.c +++ b/audio/decode/ad_lavc.c @@ -191,12 +191,6 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out) AVPacket pkt; mp_set_av_packet(&pkt, mpkt, NULL); - // If we don't have a PTS yet, use the first packet PTS we can get. - if (da->pts == MP_NOPTS_VALUE && mpkt && mpkt->pts != MP_NOPTS_VALUE) { - da->pts = mpkt->pts; - da->pts_offset = 0; - } - int got_frame = 0; av_frame_unref(priv->avframe); int ret = avcodec_decode_audio4(avctx, priv->avframe, &got_frame, &pkt); @@ -225,10 +219,6 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out) return mpkt ? AD_OK : AD_EOF; double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, NULL); - if (out_pts != MP_NOPTS_VALUE) { - da->pts = out_pts; - da->pts_offset = 0; - } struct mp_audio *mpframe = mp_audio_from_avframe(priv->avframe); if (!mpframe) @@ -244,6 +234,8 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out) } mp_audio_set_channels(mpframe, &lavc_chmap); + mpframe->pts = out_pts; + #if HAVE_AVFRAME_SKIP_SAMPLES AVFrameSideData *sd = av_frame_get_side_data(priv->avframe, AV_FRAME_DATA_SKIP_SAMPLES); @@ -254,7 +246,8 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out) uint32_t skip = MPMIN(priv->skip_samples, mpframe->samples); if (skip) { mp_audio_skip_samples(mpframe, skip); - da->pts_offset += skip; + if (mpframe->pts != MP_NOPTS_VALUE) + mpframe->pts += skip / (double)mpframe->rate; priv->skip_samples -= skip; } if (pad <= mpframe->samples) diff --git a/audio/decode/ad_spdif.c b/audio/decode/ad_spdif.c index 88f3f2ba2b..5e9dcf1c4f 100644 --- a/audio/decode/ad_spdif.c +++ b/audio/decode/ad_spdif.c @@ -253,13 +253,11 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out) if (!mpkt) return AD_EOF; + double pts = mpkt->pts; + AVPacket pkt; mp_set_av_packet(&pkt, mpkt, NULL); pkt.pts = pkt.dts = 0; - if (mpkt->pts != MP_NOPTS_VALUE) { - da->pts = mpkt->pts; - da->pts_offset = 0; - } if (!spdif_ctx->lavf_ctx) { if (init_filter(da, &pkt) < 0) return AD_ERR; @@ -276,6 +274,7 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out) return AD_ERR; memcpy((*out)->planes[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len); + (*out)->pts = pts; return 0; } diff --git a/audio/decode/dec_audio.c b/audio/decode/dec_audio.c index edf26951f7..03172ed294 100644 --- a/audio/decode/dec_audio.c +++ b/audio/decode/dec_audio.c @@ -18,6 +18,7 @@ #include <stdio.h> #include <stdlib.h> #include <unistd.h> +#include <math.h> #include <assert.h> #include <libavutil/mem.h> @@ -170,14 +171,28 @@ static int decode_new_frame(struct dec_audio *da) if (ret < 0) return ret; - if (da->pts == MP_NOPTS_VALUE && da->header->missing_timestamps) - da->pts = 0; - if (da->waiting) { + if (da->waiting->pts != MP_NOPTS_VALUE) { + if (da->pts != MP_NOPTS_VALUE) { + da->pts += da->pts_offset / (double)da->waiting->rate; + da->pts_offset = 0; + } + // Keep the interpolated timestamp if it doesn't deviate more + // than 1 ms from the real one. (MKV rounded timestamps.) + if (da->pts == MP_NOPTS_VALUE || da->pts_offset != 0 || + fabs(da->pts - da->waiting->pts) > 0.001) + { + da->pts = da->waiting->pts; + da->pts_offset = 0; + } + } da->pts_offset += da->waiting->samples; da->decode_format = *da->waiting; mp_audio_set_null_data(&da->decode_format); } + + if (da->pts == MP_NOPTS_VALUE && da->header->missing_timestamps) + da->pts = 0; } return mp_audio_config_valid(da->waiting) ? AD_OK : AD_ERR; } diff --git a/audio/filter/af_lavcac3enc.c b/audio/filter/af_lavcac3enc.c index c1e6b964ad..973e688aaa 100644 --- a/audio/filter/af_lavcac3enc.c +++ b/audio/filter/af_lavcac3enc.c @@ -49,7 +49,6 @@ const uint16_t ac3_bitrate_tab[19] = { typedef struct af_ac3enc_s { struct AVCodec *lavc_acodec; struct AVCodecContext *lavc_actx; - AVPacket pkt; int bit_rate; struct mp_audio *input; // frame passed to libavcodec struct mp_audio *pending; // unconsumed input data @@ -146,7 +145,6 @@ static void uninit(struct af_instance* af) af_ac3enc_t *s = af->priv; if (s) { - av_packet_unref(&s->pkt); if(s->lavc_actx) { avcodec_close(s->lavc_actx); av_free(s->lavc_actx); @@ -212,6 +210,8 @@ static int filter_out(struct af_instance *af) MP_FATAL(af, "Could not allocate memory \n"); return -1; } + int err = -1; + frame->nb_samples = s->in_samples; frame->format = s->lavc_actx->sample_fmt; frame->channel_layout = s->lavc_actx->channel_layout; @@ -221,30 +221,33 @@ static int filter_out(struct af_instance *af) frame->data[n] = s->input->planes[n]; frame->linesize[0] = s->input->samples * s->input->sstride; + AVPacket pkt = {0}; + av_init_packet(&pkt); + int ok; - int lavc_ret = avcodec_encode_audio2(s->lavc_actx, &s->pkt, frame, &ok); + int lavc_ret = avcodec_encode_audio2(s->lavc_actx, &pkt, frame, &ok); av_frame_free(&frame); s->input->samples = 0; if (lavc_ret < 0 || !ok) { MP_FATAL(af, "Encode failed.\n"); - return -1; + goto done; } MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n", - s->pkt.size, s->pending->samples); + pkt.size, s->pending->samples); struct mp_audio *out = mp_audio_pool_get(af->out_pool, af->data, s->out_samples); if (!out) - return -1; + goto done; mp_audio_copy_attributes(out, s->pending); - int frame_size = s->pkt.size; + int frame_size = pkt.size; int header_len = 0; char hdr[8]; - if (s->cfg_add_iec61937_header && s->pkt.size > 5) { - int bsmod = s->pkt.data[5] & 0x7; + if (s->cfg_add_iec61937_header && pkt.size > 5) { + int bsmod = pkt.data[5] & 0x7; int len = frame_size; frame_size = AC3_FRAME_SIZE * 2 * 2; @@ -262,14 +265,18 @@ static int filter_out(struct af_instance *af) char *buf = (char *)out->planes[0]; memcpy(buf, hdr, header_len); - memcpy(buf + header_len, s->pkt.data, s->pkt.size); - memset(buf + header_len + s->pkt.size, 0, - frame_size - (header_len + s->pkt.size)); - swap_16((uint16_t *)(buf + header_len), s->pkt.size / 2); + memcpy(buf + header_len, pkt.data, pkt.size); + memset(buf + header_len + pkt.size, 0, + frame_size - (header_len + pkt.size)); + swap_16((uint16_t *)(buf + header_len), pkt.size / 2); out->samples = frame_size / out->sstride; af_add_output_frame(af, out); update_delay(af); - return 0; + + err = 0; +done: + av_packet_unref(&pkt); + return err; } static int af_open(struct af_instance* af){ @@ -307,8 +314,6 @@ static int af_open(struct af_instance* af){ MP_VERBOSE(af, "[af_lavcac3enc]: in sample format: %s\n", af_fmt_to_str(s->in_sampleformat)); - av_init_packet(&s->pkt); - s->input = talloc_zero(s, struct mp_audio); if (s->cfg_bit_rate) { diff --git a/audio/filter/af_scaletempo.c b/audio/filter/af_scaletempo.c index 131dc5d511..4f0eb13091 100644 --- a/audio/filter/af_scaletempo.c +++ b/audio/filter/af_scaletempo.c @@ -286,9 +286,6 @@ static void update_speed(struct af_instance *af, float speed) s->frames_stride_scaled = s->scale * s->frames_stride; s->frames_stride_error = MPMIN(s->frames_stride_error, s->frames_stride_scaled); - - MP_VERBOSE(af, "%.3f speed * %.3f scale_nominal = %.3f\n", - s->speed, s->scale_nominal, s->scale); } // Initialization and runtime control diff --git a/audio/format.c b/audio/format.c index 100a0f3225..06360b884b 100644 --- a/audio/format.c +++ b/audio/format.c @@ -51,7 +51,7 @@ int af_fmt_change_bytes(int format, int bytes) if (af_fmt_to_bytes(fmt) == bytes && af_fmt_is_float(fmt) == af_fmt_is_float(format) && af_fmt_is_planar(fmt) == af_fmt_is_planar(format) && - af_fmt_is_spdif(fmt) == af_fmt_is_spdif(format)) + (fmt == format || (!af_fmt_is_spdif(fmt) && !af_fmt_is_spdif(format)))) return fmt; } return 0; diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c index 2b3e8e0658..4112ab3c83 100644 --- a/audio/out/ao_alsa.c +++ b/audio/out/ao_alsa.c @@ -52,11 +52,11 @@ struct priv { snd_pcm_t *alsa; bool device_lost; snd_pcm_format_t alsa_fmt; - int can_pause; + bool can_pause; snd_pcm_sframes_t prepause_frames; double delay_before_pause; - int buffersize; // in frames - int outburst; // in frames + snd_pcm_uframes_t buffersize; + snd_pcm_uframes_t outburst; char *cfg_device; char *cfg_mixer_device; @@ -227,24 +227,19 @@ static const int mp_to_alsa_format[][2] = { MP_SELECT_LE_BE(SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S24_3BE)}, {AF_FORMAT_FLOAT, SND_PCM_FORMAT_FLOAT}, {AF_FORMAT_DOUBLE, SND_PCM_FORMAT_FLOAT64}, + {AF_FORMAT_S_MP3, SND_PCM_FORMAT_MPEG}, {AF_FORMAT_UNKNOWN, SND_PCM_FORMAT_UNKNOWN}, }; static int find_alsa_format(int af_format) { - if (af_fmt_is_spdif(af_format)) { - if (af_format == AF_FORMAT_S_MP3) { - return SND_PCM_FORMAT_MPEG; - } else { - return SND_PCM_FORMAT_S16; - } - } - af_format = af_fmt_from_planar(af_format); for (int n = 0; mp_to_alsa_format[n][0] != AF_FORMAT_UNKNOWN; n++) { if (mp_to_alsa_format[n][0] == af_format) return mp_to_alsa_format[n][1]; } + if (af_fmt_is_spdif(af_format)) + return SND_PCM_FORMAT_S16; return SND_PCM_FORMAT_UNKNOWN; } @@ -275,6 +270,7 @@ static const int alsa_to_mp_channels[][2] = { {SND_CHMAP_RLC, MP_SP(SDL)}, {SND_CHMAP_MONO, MP_SP(FC)}, {SND_CHMAP_NA, MP_SPEAKER_ID_NA}, + {SND_CHMAP_UNKNOWN, MP_SPEAKER_ID_NA}, {SND_CHMAP_LAST, MP_SPEAKER_ID_COUNT} }; @@ -288,28 +284,48 @@ static int find_mp_channel(int alsa_channel) return MP_SPEAKER_ID_COUNT; } -static int find_alsa_channel(int mp_channel) +#define CHMAP(n, ...) &(struct mp_chmap) MP_CONCAT(MP_CHMAP, n) (__VA_ARGS__) + +// Replace each channel in a with b (a->num == b->num) +static void replace_submap(struct mp_chmap *dst, struct mp_chmap *a, + struct mp_chmap *b) { - for (int i = 0; alsa_to_mp_channels[i][1] != MP_SPEAKER_ID_COUNT; i++) { - if (alsa_to_mp_channels[i][1] == mp_channel) - return alsa_to_mp_channels[i][0]; + struct mp_chmap t = *dst; + if (!mp_chmap_is_valid(&t) || mp_chmap_diffn(a, &t) != 0) + return; + assert(a->num == b->num); + for (int n = 0; n < t.num; n++) { + for (int i = 0; i < a->num; i++) { + if (t.speaker[n] == a->speaker[i]) { + t.speaker[n] = b->speaker[i]; + break; + } + } } - - return SND_CHMAP_UNKNOWN; + if (mp_chmap_is_valid(&t)) + *dst = t; } -static int mp_chmap_from_alsa(struct mp_chmap *dst, snd_pcm_chmap_t *src) +static bool mp_chmap_from_alsa(struct mp_chmap *dst, snd_pcm_chmap_t *src) { *dst = (struct mp_chmap) {0}; if (src->channels > MP_NUM_CHANNELS) - return -1; + return false; dst->num = src->channels; for (int c = 0; c < dst->num; c++) dst->speaker[c] = find_mp_channel(src->pos[c]); - return 0; + // Assume anything with 1 channel is mono. + if (dst->num == 1) + dst->speaker[0] = MP_SP(FC); + + // Remap weird Intel HDA HDMI 7.1 layouts correctly. + replace_submap(dst, CHMAP(6, FL, FR, BL, BR, SDL, SDR), + CHMAP(6, FL, FR, SL, SR, BL, BR)); + + return mp_chmap_is_valid(dst); } static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap) @@ -322,20 +338,21 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap) return false; for (int i = 0; maps[i] != NULL; i++) { - struct mp_chmap entry; - mp_chmap_from_alsa(&entry, &maps[i]->map); + char aname[128]; + if (snd_pcm_chmap_print(&maps[i]->map, sizeof(aname), aname) <= 0) + aname[0] = '\0'; - if (mp_chmap_is_valid(&entry)) { + struct mp_chmap entry; + if (mp_chmap_from_alsa(&entry, &maps[i]->map)) { + struct mp_chmap reorder = entry; if (maps[i]->type == SND_CHMAP_TYPE_VAR) - mp_chmap_reorder_norm(&entry); - MP_DBG(ao, "Got supported channel map: %s (type %s)\n", - mp_chmap_to_str(&entry), - snd_pcm_chmap_type_name(maps[i]->type)); - mp_chmap_sel_add_map(&chmap_sel, &entry); + mp_chmap_reorder_norm(&reorder); + MP_DBG(ao, "Got supported channel map: %s (type %s) -> %s -> %s\n", + aname, snd_pcm_chmap_type_name(maps[i]->type), + mp_chmap_to_str(&entry), mp_chmap_to_str(&reorder)); + mp_chmap_sel_add_map(&chmap_sel, &reorder); } else { - char tmp[128]; - if (snd_pcm_chmap_print(&maps[i]->map, sizeof(tmp), tmp) > 0) - MP_VERBOSE(ao, "skipping unknown ALSA channel map: %s\n", tmp); + MP_VERBOSE(ao, "skipping unknown ALSA channel map: %s\n", aname); } } @@ -344,6 +361,108 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap) return ao_chmap_sel_adjust(ao, &chmap_sel, chmap); } +// Map back our selected channel layout to an ALSA one. This is done this way so +// that our ALSA->mp_chmap mapping function only has to go one way. +// The return value is to be freed with free(). +static snd_pcm_chmap_t *map_back_chmap(struct ao *ao, struct mp_chmap *chmap) +{ + struct priv *p = ao->priv; + if (!mp_chmap_is_valid(chmap)) + return NULL; + + snd_pcm_chmap_query_t **maps = snd_pcm_query_chmaps(p->alsa); + if (!maps) + return NULL; + + snd_pcm_chmap_t *alsa_chmap = NULL; + + for (int i = 0; maps[i] != NULL; i++) { + struct mp_chmap entry; + if (!mp_chmap_from_alsa(&entry, &maps[i]->map)) + continue; + + if (mp_chmap_equals(chmap, &entry) || + (mp_chmap_equals_reordered(chmap, &entry) && + maps[i]->type == SND_CHMAP_TYPE_VAR)) + { + alsa_chmap = calloc(1, sizeof(*alsa_chmap) + + sizeof(alsa_chmap->pos[0]) * entry.num); + if (!alsa_chmap) + break; + alsa_chmap->channels = entry.num; + + // Undo if mp_chmap_reorder() was called on the result. + int reorder[MP_NUM_CHANNELS]; + mp_chmap_get_reorder(reorder, chmap, &entry); + for (int n = 0; n < entry.num; n++) + alsa_chmap->pos[n] = maps[i]->map.pos[reorder[n]]; + break; + } + } + + snd_pcm_free_chmaps(maps); + return alsa_chmap; +} + + +static int set_chmap(struct ao *ao, struct mp_chmap *dev_chmap, int num_channels) +{ + struct priv *p = ao->priv; + int err; + + snd_pcm_chmap_t *alsa_chmap = map_back_chmap(ao, dev_chmap); + if (alsa_chmap) { + char tmp[128]; + if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0) + MP_VERBOSE(ao, "trying to set ALSA channel map: %s\n", tmp); + + err = snd_pcm_set_chmap(p->alsa, alsa_chmap); + if (err == -ENXIO) { + // A device my not be able to set any channel map, even channel maps + // that were reported as supported. This is either because the ALSA + // device is broken (dmix), or because the driver has only 1 + // channel map per channel count, and setting the map is not needed. + MP_VERBOSE(ao, "device returned ENXIO when setting channel map %s\n", + mp_chmap_to_str(dev_chmap)); + } else { + CHECK_ALSA_WARN("Channel map setup failed"); + } + + free(alsa_chmap); + } + + alsa_chmap = snd_pcm_get_chmap(p->alsa); + if (alsa_chmap) { + char tmp[128]; + if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0) + MP_VERBOSE(ao, "channel map reported by ALSA: %s\n", tmp); + + struct mp_chmap chmap; + mp_chmap_from_alsa(&chmap, alsa_chmap); + + MP_VERBOSE(ao, "which we understand as: %s\n", mp_chmap_to_str(&chmap)); + + if (p->cfg_ignore_chmap) { + MP_VERBOSE(ao, "user set ignore-chmap; ignoring the channel map.\n"); + } else if (af_fmt_is_spdif(ao->format)) { + MP_VERBOSE(ao, "using spdif passthrough; ignoring the channel map.\n"); + } else if (!mp_chmap_is_valid(&chmap)) { + MP_WARN(ao, "Got unknown channel map from ALSA.\n"); + } else if (chmap.num != num_channels) { + MP_WARN(ao, "ALSA channel map conflicts with channel count!\n"); + } else { + MP_VERBOSE(ao, "using the ALSA channel map.\n"); + if (mp_chmap_equals(&chmap, &ao->channels)) + MP_VERBOSE(ao, "which is what we requested.\n"); + ao->channels = chmap; + } + + free(alsa_chmap); + } + + return 0; +} + #else /* HAVE_CHMAP_API */ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap) @@ -351,6 +470,11 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap) return false; } +static int set_chmap(struct ao *ao, struct mp_chmap *dev_chmap, int num_channels) +{ + return 0; +} + #endif /* else HAVE_CHMAP_API */ static int map_iec958_srate(int srate) @@ -371,8 +495,8 @@ static int map_iec958_srate(int srate) } // ALSA device strings can have parameters. They are usually appended to the -// device name. Since there can be various forms, and we (sometimes) want to -// append them to unknown device strings, which possibly already include params. +// device name. There can be various forms, and we (sometimes) want to append +// them to unknown device strings, which possibly already include params. static char *append_params(void *ta_parent, const char *device, const char *p) { if (!p || !p[0]) @@ -443,16 +567,14 @@ static void uninit(struct ao *ao) int err; err = snd_pcm_close(p->alsa); + p->alsa = NULL; CHECK_ALSA_ERROR("pcm close error"); } alsa_error: ; } -#define INIT_OK 0 -#define INIT_ERROR -1 -#define INIT_BRAINDEATH -2 -static int init_device(struct ao *ao, bool second_try) +static int init_device(struct ao *ao) { struct priv *p = ao->priv; int err; @@ -470,18 +592,35 @@ static int init_device(struct ao *ao, bool second_try) CHECK_ALSA_WARN("Unable to set blocking mode"); snd_pcm_hw_params_t *alsa_hwparams; - snd_pcm_sw_params_t *alsa_swparams; - snd_pcm_hw_params_alloca(&alsa_hwparams); - snd_pcm_sw_params_alloca(&alsa_swparams); err = snd_pcm_hw_params_any(p->alsa, alsa_hwparams); CHECK_ALSA_ERROR("Unable to get initial parameters"); + // Some ALSA drivers have broken delay reporting, so disable the ALSA + // resampling plugin by default. + if (!p->cfg_resample) { + err = snd_pcm_hw_params_set_rate_resample(p->alsa, alsa_hwparams, 0); + CHECK_ALSA_ERROR("Unable to disable resampling"); + } + + snd_pcm_access_t access = af_fmt_is_planar(ao->format) + ? SND_PCM_ACCESS_RW_NONINTERLEAVED + : SND_PCM_ACCESS_RW_INTERLEAVED; + err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access); + if (err < 0 && af_fmt_is_planar(ao->format)) { + ao->format = af_fmt_from_planar(ao->format); + access = SND_PCM_ACCESS_RW_INTERLEAVED; + err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access); + } + CHECK_ALSA_ERROR("Unable to set access type"); + bool found_format = false; int try_formats[AF_FORMAT_COUNT]; af_get_best_sample_formats(ao->format, try_formats); for (int n = 0; try_formats[n]; n++) { + if (af_fmt_is_planar(ao->format) != af_fmt_is_planar(try_formats[n])) + continue; // implied SND_PCM_ACCESS mismatches p->alsa_fmt = find_alsa_format(try_formats[n]); MP_VERBOSE(ao, "trying format %s\n", af_fmt_to_str(try_formats[n])); if (snd_pcm_hw_params_test_format(p->alsa, alsa_hwparams, p->alsa_fmt) >= 0) { @@ -499,17 +638,6 @@ static int init_device(struct ao *ao, bool second_try) err = snd_pcm_hw_params_set_format(p->alsa, alsa_hwparams, p->alsa_fmt); CHECK_ALSA_ERROR("Unable to set format"); - snd_pcm_access_t access = af_fmt_is_planar(ao->format) - ? SND_PCM_ACCESS_RW_NONINTERLEAVED - : SND_PCM_ACCESS_RW_INTERLEAVED; - err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access); - if (err < 0 && af_fmt_is_planar(ao->format)) { - ao->format = af_fmt_from_planar(ao->format); - access = SND_PCM_ACCESS_RW_INTERLEAVED; - err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access); - } - CHECK_ALSA_ERROR("Unable to set access type"); - struct mp_chmap dev_chmap = ao->channels; if (af_fmt_is_spdif(ao->format) || p->cfg_ignore_chmap) { dev_chmap.num = 0; // disable chmap API @@ -534,157 +662,51 @@ static int init_device(struct ao *ao, bool second_try) goto alsa_error; } - if (num_channels != ao->channels.num) { - int req = ao->channels.num; - mp_chmap_from_channels_alsa(&ao->channels, num_channels); - if (!mp_chmap_is_valid(&ao->channels)) - mp_chmap_from_channels(&ao->channels, 2); - MP_ERR(ao, "Asked for %d channels, got %d - fallback to %s.\n", req, - num_channels, mp_chmap_to_str(&ao->channels)); - } - - // Some ALSA drivers have broken delay reporting, so disable the ALSA - // resampling plugin by default. - if (!p->cfg_resample) { - err = snd_pcm_hw_params_set_rate_resample(p->alsa, alsa_hwparams, 0); - CHECK_ALSA_ERROR("Unable to disable resampling"); - } - err = snd_pcm_hw_params_set_rate_near (p->alsa, alsa_hwparams, &ao->samplerate, NULL); CHECK_ALSA_ERROR("Unable to set samplerate-2"); + snd_pcm_hw_params_t *hwparams_backup; + snd_pcm_hw_params_alloca(&hwparams_backup); + snd_pcm_hw_params_copy(hwparams_backup, alsa_hwparams); + + // Cargo-culted buffer settings; might still be useful for PulseAudio. err = snd_pcm_hw_params_set_buffer_time_near (p->alsa, alsa_hwparams, &(unsigned int){BUFFER_TIME}, NULL); CHECK_ALSA_WARN("Unable to set buffer time near"); - - err = snd_pcm_hw_params_set_periods_near - (p->alsa, alsa_hwparams, &(unsigned int){FRAGCOUNT}, NULL); - CHECK_ALSA_WARN("Unable to set periods"); + if (err >= 0) { + err = snd_pcm_hw_params_set_periods_near + (p->alsa, alsa_hwparams, &(unsigned int){FRAGCOUNT}, NULL); + CHECK_ALSA_WARN("Unable to set periods"); + } + if (err < 0) + snd_pcm_hw_params_copy(alsa_hwparams, hwparams_backup); /* finally install hardware parameters */ err = snd_pcm_hw_params(p->alsa, alsa_hwparams); CHECK_ALSA_ERROR("Unable to set hw-parameters"); - /* end setting hw-params */ - -#if HAVE_CHMAP_API - if (mp_chmap_is_valid(&dev_chmap)) { - snd_pcm_chmap_t *alsa_chmap = - calloc(1, sizeof(*alsa_chmap) + - sizeof(alsa_chmap->pos[0]) * dev_chmap.num); - if (!alsa_chmap) - goto alsa_error; - - alsa_chmap->channels = dev_chmap.num; - for (int c = 0; c < dev_chmap.num; c++) - alsa_chmap->pos[c] = find_alsa_channel(dev_chmap.speaker[c]); - - // mpv and ALSA use different conventions for mono - if (dev_chmap.num == 1 && dev_chmap.speaker[0] == MP_SP(FC)) - alsa_chmap->pos[0] = SND_CHMAP_MONO; - - char tmp[128]; - if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0) - MP_VERBOSE(ao, "trying to set ALSA channel map: %s\n", tmp); - - err = snd_pcm_set_chmap(p->alsa, alsa_chmap); - if (err == -ENXIO) { - // A device my not be able to set any channel map, even channel maps - // that were reported as supported. This is either because the ALSA - // device is broken (dmix), or because the driver has only 1 - // channel map per channel count, and setting the map is not needed. - MP_VERBOSE(ao, "device returned ENXIO when setting channel map %s\n", - mp_chmap_to_str(&dev_chmap)); - } else { - CHECK_ALSA_WARN("Channel map setup failed"); - } - - free(alsa_chmap); - } - - snd_pcm_chmap_t *alsa_chmap = snd_pcm_get_chmap(p->alsa); - if (alsa_chmap) { - char tmp[128]; - if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0) - MP_VERBOSE(ao, "channel map reported by ALSA: %s\n", tmp); - - struct mp_chmap chmap; - mp_chmap_from_alsa(&chmap, alsa_chmap); - - MP_VERBOSE(ao, "which we understand as: %s\n", mp_chmap_to_str(&chmap)); - - if (p->cfg_ignore_chmap) { - MP_VERBOSE(ao, "user set ignore-chmap; ignoring the channel map.\n"); - } else if (af_fmt_is_spdif(ao->format)) { - MP_VERBOSE(ao, "using spdif passthrough; ignoring the channel map.\n"); - } else if (mp_chmap_is_valid(&chmap)) { - // Is it one that contains NA channels? - struct mp_chmap without_na = chmap; - mp_chmap_remove_na(&without_na); - - if (mp_chmap_is_valid(&without_na) && - !mp_chmap_equals(&without_na, &chmap) && - !mp_chmap_equals(&chmap, &ao->channels) && - !second_try) - { - // Sometimes, ALSA will advertise certain chmaps, but it's not - // possible to set them. This can happen with dmix: as of - // alsa 1.0.28, dmix can do stereo only, but advertises the - // surround chmaps of the underlying device. In this case, - // e.g. setting 6 channels will succeed, but requesting 5.1 - // afterwards will fail. Then it will return something like - // "FL FR NA NA NA NA" as channel map. This means we would - // have to pad stereo output to 6 channels with silence, which - // would require lots of extra processing. You can't change - // the number of channels to 2 either, because the hw params - // are already set! So just fuck it and reopen the device with - // the chmap "cleaned out" of NA entries. - MP_VERBOSE(ao, "Working around braindead ALSA behavior.\n"); - err = snd_pcm_close(p->alsa); - p->alsa = NULL; - CHECK_ALSA_ERROR("pcm close error"); - ao->channels = without_na; - return INIT_BRAINDEATH; - } - - if (mp_chmap_equals(&chmap, &ao->channels)) { - MP_VERBOSE(ao, "which is what we requested.\n"); - } else if (chmap.num == ao->channels.num) { - MP_VERBOSE(ao, "using the ALSA channel map.\n"); - ao->channels = chmap; - |