/* * ALSA 0.9.x-1.x audio output driver * * Copyright (C) 2004 Alex Beregszaszi * Zsolt Barat * * modified for real ALSA 0.9.0 support by Zsolt Barat * additional AC-3 passthrough support by Andy Lo A Foe * 08/22/2002 iec958-init rewritten and merged with common init, zsolt * 04/13/2004 merged with ao_alsa1.x, fixes provided by Jindrich Makovicka * 04/25/2004 printfs converted to mp_msg, Zsolt. * * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see . */ #include #include #include #include #include #include #include #include "options/options.h" #include "options/m_config.h" #include "options/m_option.h" #include "common/msg.h" #include "osdep/endian.h" #include #if defined(SND_CHMAP_API_VERSION) && SND_CHMAP_API_VERSION >= (1 << 16) #define HAVE_CHMAP_API 1 #else #define HAVE_CHMAP_API 0 #endif #include "ao.h" #include "internal.h" #include "audio/format.h" struct ao_alsa_opts { char *mixer_device; char *mixer_name; int mixer_index; bool resample; bool ni; bool ignore_chmap; int buffer_time; int frags; }; #define OPT_BASE_STRUCT struct ao_alsa_opts static const struct m_sub_options ao_alsa_conf = { .opts = (const struct m_option[]) { {"alsa-resample", OPT_BOOL(resample)}, {"alsa-mixer-device", OPT_STRING(mixer_device)}, {"alsa-mixer-name", OPT_STRING(mixer_name)}, {"alsa-mixer-index", OPT_INT(mixer_index), M_RANGE(0, 99)}, {"alsa-non-interleaved", OPT_BOOL(ni)}, {"alsa-ignore-chmap", OPT_BOOL(ignore_chmap)}, {"alsa-buffer-time", OPT_INT(buffer_time), M_RANGE(0, INT_MAX)}, {"alsa-periods", OPT_INT(frags), M_RANGE(0, INT_MAX)}, {0} }, .defaults = &(const struct ao_alsa_opts) { .mixer_device = "default", .mixer_name = "Master", .buffer_time = 100000, .frags = 4, }, .size = sizeof(struct ao_alsa_opts), }; struct priv { snd_pcm_t *alsa; bool device_lost; snd_pcm_format_t alsa_fmt; bool can_pause; snd_pcm_uframes_t buffersize; snd_pcm_uframes_t outburst; snd_output_t *output; struct ao_convert_fmt convert; struct ao_alsa_opts *opts; }; #define CHECK_ALSA_ERROR(message) \ do { \ if (err < 0) { \ MP_ERR(ao, "%s: %s\n", (message), snd_strerror(err)); \ goto alsa_error; \ } \ } while (0) #define CHECK_ALSA_WARN(message) \ do { \ if (err < 0) \ MP_WARN(ao, "%s: %s\n", (message), snd_strerror(err)); \ } while (0) static int control(struct ao *ao, enum aocontrol cmd, void *arg) { struct priv *p = ao->priv; snd_mixer_t *handle = NULL; switch (cmd) { case AOCONTROL_GET_MUTE: case AOCONTROL_SET_MUTE: case AOCONTROL_GET_VOLUME: case AOCONTROL_SET_VOLUME: { int err; snd_mixer_elem_t *elem; snd_mixer_selem_id_t *sid; long pmin, pmax; long get_vol, set_vol; float f_multi; if (!af_fmt_is_pcm(ao->format)) return CONTROL_FALSE; snd_mixer_selem_id_alloca(&sid); snd_mixer_selem_id_set_index(sid, p->opts->mixer_index); snd_mixer_selem_id_set_name(sid, p->opts->mixer_name); err = snd_mixer_open(&handle, 0); CHECK_ALSA_ERROR("Mixer open error"); err = snd_mixer_attach(handle, p->opts->mixer_device); CHECK_ALSA_ERROR("Mixer attach error"); err = snd_mixer_selem_register(handle, NULL, NULL); CHECK_ALSA_ERROR("Mixer register error"); err = snd_mixer_load(handle); CHECK_ALSA_ERROR("Mixer load error"); elem = snd_mixer_find_selem(handle, sid); if (!elem) { MP_VERBOSE(ao, "Unable to find simple control '%s',%i.\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid)); goto alsa_error; } snd_mixer_selem_get_playback_volume_range(elem, &pmin, &pmax); f_multi = (100 / (float)(pmax - pmin)); switch (cmd) { case AOCONTROL_SET_VOLUME: { float *vol = arg; set_vol = *vol / f_multi + pmin + 0.5; err = snd_mixer_selem_set_playback_volume(elem, 0, set_vol); CHECK_ALSA_ERROR("Error setting left channel"); MP_DBG(ao, "left=%li, ", set_vol); err = snd_mixer_selem_set_playback_volume(elem, 1, set_vol); CHECK_ALSA_ERROR("Error setting right channel"); MP_DBG(ao, "right=%li, pmin=%li, pmax=%li, mult=%f\n", set_vol, pmin, pmax, f_multi); break; } case AOCONTROL_GET_VOLUME: { float *vol = arg; float left, right; snd_mixer_selem_get_playback_volume(elem, 0, &get_vol); left = (get_vol - pmin) * f_multi; snd_mixer_selem_get_playback_volume(elem, 1, &get_vol); right = (get_vol - pmin) * f_multi; *vol = (left + right) / 2.0; MP_DBG(ao, "vol=%f\n", *vol); break; } case AOCONTROL_SET_MUTE: { bool *mute = arg; if (!snd_mixer_selem_has_playback_switch(elem)) goto alsa_error; if (!snd_mixer_selem_has_playback_switch_joined(elem)) { snd_mixer_selem_set_playback_switch(elem, 1, !*mute); } snd_mixer_selem_set_playback_switch(elem, 0, !*mute); break; } case AOCONTROL_GET_MUTE: { bool *mute = arg; if (!snd_mixer_selem_has_playback_switch(elem)) goto alsa_error; int tmp = 1; snd_mixer_selem_get_playback_switch(elem, 0, &tmp); *mute = !tmp; if (!snd_mixer_selem_has_playback_switch_joined(elem)) { snd_mixer_selem_get_playback_switch(elem, 1, &tmp); *mute &= !tmp; } break; } } snd_mixer_close(handle); return CONTROL_OK; } } //end switch return CONTROL_UNKNOWN; alsa_error: if (handle) snd_mixer_close(handle); return CONTROL_ERROR; } struct alsa_fmt { int mp_format; int alsa_format; int bits; // alsa format full sample size (optional) int pad_msb; // how many MSB bits are 0 (optional) }; // Entries that have the same mp_format must be: // 1. consecutive // 2. sorted by preferred format (worst comes last) static const struct alsa_fmt mp_alsa_formats[] = { {AF_FORMAT_U8, SND_PCM_FORMAT_U8}, {AF_FORMAT_S16, SND_PCM_FORMAT_S16}, {AF_FORMAT_S32, SND_PCM_FORMAT_S32}, {AF_FORMAT_S32, SND_PCM_FORMAT_S24, .bits = 32, .pad_msb = 8}, {AF_FORMAT_S32, MP_SELECT_LE_BE(SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S24_3BE), .bits = 24, .pad_msb = 0}, {AF_FORMAT_FLOAT, SND_PCM_FORMAT_FLOAT}, {AF_FORMAT_DOUBLE, SND_PCM_FORMAT_FLOAT64}, {0}, }; static const struct alsa_fmt *find_alsa_format(int mp_format) { for (int n = 0; mp_alsa_formats[n].mp_format; n++) { if (mp_alsa_formats[n].mp_format == mp_format) return &mp_alsa_formats[n]; } return NULL; } #if HAVE_CHMAP_API static const int alsa_to_mp_channels[][2] = { {SND_CHMAP_FL, MP_SP(FL)}, {SND_CHMAP_FR, MP_SP(FR)}, {SND_CHMAP_RL, MP_SP(BL)}, {SND_CHMAP_RR, MP_SP(BR)}, {SND_CHMAP_FC, MP_SP(FC)}, {SND_CHMAP_LFE, MP_SP(LFE)}, {SND_CHMAP_SL, MP_SP(SL)}, {SND_CHMAP_SR, MP_SP(SR)}, {SND_CHMAP_RC, MP_SP(BC)}, {SND_CHMAP_FLC, MP_SP(FLC)}, {SND_CHMAP_FRC, MP_SP(FRC)}, {SND_CHMAP_FLW, MP_SP(WL)}, {SND_CHMAP_FRW, MP_SP(WR)}, {SND_CHMAP_TC, MP_SP(TC)}, {SND_CHMAP_TFL, MP_SP(TFL)}, {SND_CHMAP_TFR, MP_SP(TFR)}, {SND_CHMAP_TFC, MP_SP(TFC)}, {SND_CHMAP_TRL, MP_SP(TBL)}, {SND_CHMAP_TRR, MP_SP(TBR)}, {SND_CHMAP_TRC, MP_SP(TBC)}, {SND_CHMAP_RRC, MP_SP(SDR)}, {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} }; static int find_mp_channel(int alsa_channel) { for (int i = 0; alsa_to_mp_channels[i][1] != MP_SPEAKER_ID_COUNT; i++) { if (alsa_to_mp_channels[i][0] == alsa_channel) return alsa_to_mp_channels[i][1]; } return MP_SPEAKER_ID_COUNT; } #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) { 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; } } } if (mp_chmap_is_valid(&t)) *dst = t; } 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 false; dst->num = src->channels; for (int c = 0; c < dst->num; c++) dst->speaker[c] = find_mp_channel(src->pos[c]); // 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) { struct priv *p = ao->priv; struct mp_chmap_sel chmap_sel = {.tmp = p}; snd_pcm_chmap_query_t **maps = snd_pcm_query_chmaps(p->alsa); if (!maps) { MP_VERBOSE(ao, "snd_pcm_query_chmaps() returned NULL\n"); return false; } for (int i = 0; maps[i] != NULL; i++) { char aname[128]; if (snd_pcm_chmap_print(&maps[i]->map, sizeof(aname), aname) <= 0) aname[0] = '\0'; struct mp_chmap entry; if (mp_chmap_from_alsa(&entry, &maps[i]->map)) { struct mp_chmap reorder = entry; mp_chmap_reorder_norm(&reorder); MP_DBG(ao, "got ALSA chmap: %s (%s) -> %s", aname, snd_pcm_chmap_type_name(maps[i]->type), mp_chmap_to_str(&entry)); if (!mp_chmap_equals(&entry, &reorder)) MP_DBG(ao, " -> %s", mp_chmap_to_str(&reorder)); MP_DBG(ao, "\n"); struct mp_chmap final = maps[i]->type == SND_CHMAP_TYPE_VAR ? reorder : entry; mp_chmap_sel_add_map(&chmap_sel, &final); } else { MP_VERBOSE(ao, "skipping unknown ALSA channel map: %s\n", aname); } } snd_pcm_free_chmaps(maps); return ao_chmap_sel_adjust2(ao, &chmap_sel, chmap, false); } // 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->opts->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 { if (mp_chmap_equals(&chmap, &ao->channels)) { MP_VERBOSE(ao, "which is what we requested.\n"); } else if (!mp_chmap_is_valid(dev_chmap)) { MP_VERBOSE(ao, "ignoring the ALSA channel map.\n"); } else { MP_VERBOSE(ao, "using the ALSA channel map.\n"); ao->channels = chmap; } } free(alsa_chmap); } return 0; } #else /* HAVE_CHMAP_API */ 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 void dump_hw_params(struct ao *ao, const char *msg, snd_pcm_hw_params_t *hw_params) { struct priv *p = ao->priv; int err; err = snd_pcm_hw_params_dump(hw_params, p->output); CHECK_ALSA_WARN("Dump hwparams error"); char *tmp = NULL; size_t tmp_s = snd_output_buffer_string(p->output, &tmp); if (tmp) mp_msg(ao->log, MSGL_DEBUG, "%s---\n%.*s---\n", msg, (int)tmp_s, tmp); snd_output_flush(p->output); } static int map_iec958_srate(int srate) { switch (srate) { case 44100: return IEC958_AES3_CON_FS_44100; case 48000: return IEC958_AES3_CON_FS_48000; case 32000: return IEC958_AES3_CON_FS_32000; case 22050: return IEC958_AES3_CON_FS_22050; case 24000: return IEC958_AES3_CON_FS_24000; case 88200: return IEC958_AES3_CON_FS_88200; case 768000: return IEC958_AES3_CON_FS_768000; case 96000: return IEC958_AES3_CON_FS_96000; case 176400: return IEC958_AES3_CON_FS_176400; case 192000: return IEC958_AES3_CON_FS_192000; default: return IEC958_AES3_CON_FS_NOTID; } } // ALSA device strings can have parameters. They are usually appended to the // 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]) return talloc_strdup(ta_parent, device); int len = strlen(device); char *end = strchr(device, ':'); if (!end) { /* no existing parameters: add it behind device name */ return talloc_asprintf(ta_parent, "%s:%s", device, p); } else if (end[1] == '\0') { /* ":" but no parameters */ return talloc_asprintf(ta_parent, "%s%s", device, p); } else if (end[1] == '{' && device[len - 1] == '}') { /* parameters in config syntax: add it inside the { } block */ return talloc_asprintf(ta_parent, "%.*s %s}", len - 1, device, p); } else { /* a simple list of parameters: add it at the end of the list */ return talloc_asprintf(ta_parent, "%s,%s", device, p); } MP_ASSERT_UNREACHABLE(); } static int try_open_device(struct ao *ao, const char *device, int mode) { struct priv *p = ao->priv; int err; if (af_fmt_is_spdif(ao->format)) { void *tmp = talloc_new(NULL); char *params = talloc_asprintf(tmp, "AES0=%d,AES1=%d,AES2=0,AES3=%d", IEC958_AES0_NONAUDIO | IEC958_AES0_PRO_EMPHASIS_NONE, IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER, map_iec958_srate(ao->samplerate)); const char *ac3_device = append_params(tmp, device, params); MP_VERBOSE(ao, "opening device '%s' => '%s'\n", device, ac3_device); err = snd_pcm_open(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, mode); if (err < 0) { // Some spdif-capable devices do not accept the AES0 parameter, // and instead require the iec958 pseudo-device (they will play // noise otherwise). Unfortunately, ALSA gives us no way to map // these devices, so try it for the default device only. bstr dev; bstr_split_tok(bstr0(device), ":", &dev, &(bstr){0}); if (bstr_equals0(dev, "default")) { const char *const fallbacks[] = {"hdmi", "iec958", NULL}; for (int n = 0; fallbacks[n]; n++) { char *ndev = append_params(tmp, fallbacks[n], params); MP_VERBOSE(ao, "got error '%s'; opening iec fallback " "device '%s'\n", snd_strerror(err), ndev); err = snd_pcm_open (&p->alsa, ndev, SND_PCM_STREAM_PLAYBACK, mode); if (err >= 0) break; } } } talloc_free(tmp); } else { MP_VERBOSE(ao, "opening device '%s'\n", device); err = snd_pcm_open(&p->alsa, device, SND_PCM_STREAM_PLAYBACK, mode); } return err; } static void uninit(struct ao *ao) { struct priv *p = ao->priv; if (p->output) snd_output_close(p->output); p->output = NULL; if (p->alsa) { int err; err = snd_pcm_close(p->alsa); p->alsa = NULL; CHECK_ALSA_ERROR("pcm close error"); } alsa_error: snd_config_update_free_global(); } #define INIT_DEVICE_ERR_GENERIC -1 #define INIT_DEVICE_ERR_HWPARAMS -2 static int init_device(struct ao *ao, int mode) { struct priv *p = ao->priv; struct ao_alsa_opts *opts = p->opts; int ret = INIT_DEVICE_ERR_GENERIC; char *tmp; size_t tmp_s; int err; p->alsa_fmt = SND_PCM_FORMAT_UNKNOWN; err = snd_output_buffer_open(&p->output); CHECK_ALSA_ERROR("Unable to create output buffer"); const char *device = "default"; if (ao->device) device = ao->device; err = try_open_device(ao, device, mode); CHECK_ALSA_ERROR("Playback open error"); err = snd_pcm_dump(p->alsa, p->output); CHECK_ALSA_WARN("Dump PCM error"); tmp_s = snd_output_buffer_string(p->output, &tmp); if (tmp) MP_DBG(ao, "PCM setup:\n---\n%.*s---\n", (int)tmp_s, tmp); snd_output_flush(p->output); err = snd_pcm_nonblock(p->alsa, 0); CHECK_ALSA_WARN("Unable to set blocking mode"); snd_pcm_hw_params_t *alsa_hwparams; snd_pcm_hw_params_alloca(&alsa_hwparams); err = snd_pcm_hw_params_any(p->alsa, alsa_hwparams); CHECK_ALSA_ERROR("Unable to get initial parameters"); dump_hw_params(ao, "Start HW params:\n", alsa_hwparams); // Some ALSA drivers have broken delay reporting, so disable the ALSA // resampling plugin by default. if (!p->opts->resample) { err = snd_pcm_hw_params_set_rate_resample(p->alsa, alsa_hwparams, 0); CHECK_ALSA_ERROR("Unable to disable resampling"); } dump_hw_params(ao, "HW params after rate:\n", alsa_hwparams); 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"); dump_hw_params(ao, "HW params after access:\n", alsa_hwparams); bool found_format = false; int try_formats[AF_FORMAT_COUNT + 1]; af_get_best_sample_formats(ao->format, try_formats); for (int n = 0; try_formats[n] && !found_format; n++) { int mp_format = try_formats[n]; if (af_fmt_is_planar(ao->format) != af_fmt_is_planar(mp_format)) continue; // implied SND_PCM_ACCESS mismatches int mp_pformat = af_fmt_from_planar(mp_format); if (af_fmt_is_spdif(mp_pformat)) mp_pformat = AF_FORMAT_S16; const struct alsa_fmt *fmt = find_alsa_format(mp_pformat); if (!fmt) continue; for (; fmt->mp_format == mp_pformat; fmt++) { p->alsa_fmt = fmt->alsa_format; p->convert = (struct ao_convert_fmt){ .src_fmt = mp_format, .dst_bits = fmt->bits ? fmt->bits : af_fmt_to_bytes(mp_format) * 8, .pad_msb = fmt->pad_msb, }; if (!ao_can_convert_inplace(&p->convert)) continue; MP_VERBOSE(ao, "trying format %s/%d\n", af_fmt_to_str(mp_pformat), p->alsa_fmt); if (snd_pcm_hw_params_test_format(p->alsa, alsa_hwparams, p->alsa_fmt) >= 0) { ao->format = mp_format; found_format = true; break; } } } if (!found_format) { MP_ERR(ao, "Can't find appropriate sample format.\n"); goto alsa_error; } err = snd_pcm_hw_params_set_format(p->alsa, alsa_hwparams, p->alsa_fmt); CHECK_ALSA_ERROR("Unable to set format"); dump_hw_params(ao, "HW params after format:\n", alsa_hwparams); // Stereo, or mono if input is 1 channel. struct mp_chmap reduced; mp_chmap_from_channels(&reduced, MPMIN(2, ao->channels.num)); struct mp_chmap dev_chmap = {0}; if (!af_fmt_is_spdif(ao->format) && !p->opts->ignore_chmap && !mp_chmap_equals(&ao->channels, &reduced)) { struct mp_chmap res = ao->channels; if (query_chmaps(ao, &res)) dev_chmap = res; // Whatever it is, we dumb it down to mono or stereo. Some drivers may // return things like bl-br, but the user (probably) still wants stereo. // This also handles the failure case (dev_chmap.num==0). if (dev_chmap.num <= 2) { dev_chmap.num = 0; ao->channels = reduced; } else if (dev_chmap.num) { ao->channels = dev_chmap; } } int num_channels = ao->channels.num; err = snd_pcm_hw_params_set_channels_near (p->alsa, alsa_hwparams, &num_channels); CHECK_ALSA_ERROR("Unable to set channels"); dump_hw_params(ao, "HW params after channels:\n", alsa_hwparams); if (num_channels > MP_NUM_CHANNELS) { MP_FATAL(ao, "Too many audio channels (%d).\n", num_channels); goto alsa_error; } err = snd_pcm_hw_params_set_rate_near (p->alsa, alsa_hwparams, &ao->samplerate, NULL); CHECK_ALSA_ERROR("Unable to set samplerate-2"); dump_hw_params(ao, "HW params after rate-2:\n", alsa_hwparams); 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 = 0; if (opts->buffer_time) { err = snd_pcm_hw_params_set_buffer_time_near (p->alsa, alsa_hwparams, &(unsigned int){opts->buffer_time}, NULL); CHECK_ALSA_WARN("Unable to set buffer time near"); } if (err >= 0 && opts->frags) { err = snd_pcm_hw_params_set_periods_near (p->alsa, alsa_hwparams, &(unsigned int){opts->frags}, NULL); CHECK_ALSA_WARN("Unable to set periods"); } if (err < 0) snd_pcm_hw_params_copy(alsa_hwparams, hwparams_backup); dump_hw_params(ao, "Going to set final HW params:\n", alsa_hwparams); /* finally install hardware parameters */ err = snd_pcm_hw_params(p->alsa, alsa_hwparams); ret = INIT_DEVICE_ERR_HWPARAMS; CHECK_ALSA_ERROR("Unable to set hw-parameters"); ret = INIT_DEVICE_ERR_GENERIC; dump_hw_params(ao, "Final HW params:\n", alsa_hwparams); if (set_chmap(ao, &dev_chmap, num_channels) < 0) goto alsa_error; if (num_channels != ao->channels.num) { int req = ao->channels.num; mp_chmap_from_channels(&ao->channels, MPMIN(2, num_channels)); mp_chmap_fill_na(&ao->channels, num_channels); MP_ERR(ao, "Asked for %d channels, got %d - fallback to %s.\n", req, num_channels, mp_chmap_to_str(&ao->channels)); if (num_channels != ao->channels.num) { MP_FATAL(ao, "mismatching channel counts.\n"); goto alsa_error; } } err = snd_pcm_hw_params_get_buffer_size(alsa_hwparams, &p->buffersize); CHECK_ALSA_ERROR("Unable to get buffersize"); err = snd_pcm_hw_params_get_period_size(alsa_hwparams, &p->outburst, NULL); CHECK_ALSA_ERROR("Unable to get period size"); p->can_pause = snd_pcm_hw_params_can_pause(alsa_hwparams); snd_pcm_sw_params_t *alsa_swparams; snd_pcm_sw_params_alloca(&alsa_swparams); err = snd_pcm_sw_params_current(p->alsa, alsa_swparams); CHECK_ALSA_ERROR("Unable to get sw-parameters"); snd_pcm_uframes_t boundary; err = snd_pcm_sw_params_get_boundary(alsa_swparams, &boundary); CHECK_ALSA_ERROR("Unable to get boundary"); // Manual trigger; INT_MAX as suggested by ALSA doxygen (they call it MAXINT). err = snd_pcm_sw_params_set_start_threshold(p->alsa, alsa_swparams, INT_MAX); CHECK_ALSA_ERROR("Unable to set start threshold"); /* play silence when there is an underrun */ err = snd_pcm_sw_params_set_silence_size (p->alsa, alsa_swparams, boundary); CHECK_ALSA_ERROR("Unable to set silence size"); err = snd_pcm_sw_params(p->alsa, alsa_swparams); CHECK_ALSA_ERROR("Unable to set sw-parameters"); MP_VERBOSE(ao, "hw pausing supported: %s\n", p->can_pause ? "yes" : "no"); MP_VERBOSE(ao, "buffersize: %d samples\n", (int)p->buffersize); MP_VERBOSE(ao, "period size: %d samples\n", (int)p->outburst); ao->device_buffer = p->buffersize; p->convert.channels = ao->channels.num; err = snd_pcm_prepare(p->alsa); CHECK_ALSA_ERROR("pcm prepare error"); return 0; alsa_error: uninit(ao); return ret; } static int init(struct ao *ao) { struct priv *p = ao->priv; p->opts = mp_get_config_group(ao, ao->global, &ao_alsa_conf); if (!p->opts->ni) ao->format = af_fmt_from_planar(ao->format); MP_VERBOSE(ao, "using ALSA version: %s\n", snd_asoundlib_version()); int mode = 0; int r = init_device(ao, mode); if (r == INIT_DEVICE_ERR_HWPARAMS) { // With some drivers, ALSA appears to be unable to set valid hwparams, // but they work if at least SND_PCM_NO_AUTO_FORMAT is set. Also, it // appears you can set this flag only on opening a device, thus there // is the need to retry opening the device. MP_WARN(ao, "Attempting to work around even more ALSA bugs...\n"); mode |= SND_PCM_NO_AUTO_CHANNELS | SND_PCM_NO_AUTO_FORMAT | SND_PCM_NO_AUTO_RESAMPLE; r = init_device(ao, mode); } // Sometimes, ALSA will advertise certain chmaps, but it's not possible to // set them. This can happen with dmix: as of alsa 1.0.29, 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. if (r >= 0) { struct mp_chmap without_na = ao->channels; mp_chmap_remove_na(&without_na); if (mp_chmap_is_valid(&without_na) && without_na.num <= 2 && ao->channels.num > 2) { MP_VERBOSE(ao, "Working around braindead dmix multichannel behavior.\n"); uninit(ao); ao->channels = without_na; r = init_device(ao, mode); } } return r; } // Function for dealing with playback state. This attempts to recover the ALSA // state (bring it into SND_PCM_STATE_{PREPARED,RUNNING,PAUSED,UNDERRUN}). If // state!=NULL, fill it after recovery is attempted. // Returns true if PCM is in one the expected states. static bool recover_and_get_state(struct ao *ao, struct mp_pcm_state *state) { struct priv *p = ao->priv; int err; snd_pcm_status_t *st; snd_pcm_status_alloca(&st); bool state_ok = false; snd_pcm_state_t pcmst = SND_PCM_STATE_DISCONNECTED; // Give it a number of chances to recover. This tries to deal with the fact // that the API is asynchronous, and to account for some past cargo-cult // (where things were retried in a loop). for (int n = 0; n <= 10; n++) { err = snd_pcm_status(p->alsa, st); if (err == -EPIPE) { // ALSA APIs can return -EPIPE when an XRUN happens, // we skip right to handling it by setting pcmst // manually. pcmst = SND_PCM_STATE_XRUN; } else { // Otherwise do error checking and query the PCM state properly. CHECK_ALSA_ERROR("snd_pcm_status"); pcmst = snd_pcm_status_get_state(st); } if (n == 10) pcmst = SND_PCM_STATE_DISCONNECTED; if (pcmst == SND_PCM_STATE_PREPARED || pcmst == SND_PCM_STATE_RUNNING || pcmst == SND_PCM_STATE_PAUSED) { state_ok = true; break; } MP_VERBOSE(ao, "attempt %d to recover from state '%s'...\n", n + 1, snd_pcm_state_name(pcmst)); switch (pcmst) { // Underrun; recover. (We never use draining.) case SND_PCM_STATE_XRUN: case SND_PCM_STATE_DRAINING: err = snd_pcm_prepare(p->alsa); if (err < 0) MP_ERR(ao, "pcm prepare error: %s\n", snd_strerror(err)); continue; // Hardware suspend. case SND_PCM_STATE_SUSPENDED: MP_INFO(ao, "PCM in suspend mode, trying to resume.\n"); err = snd_pcm_resume(p->alsa); if (err == -EAGAIN) { // Cargo-cult from decades ago, with a cargo cult timeout. MP_INFO(ao, "PCM resume EAGAIN - retrying.\n"); sleep(1); continue; } if (err == -ENOSYS) { // As suggested by ALSA doxygen. MP_VERBOSE(ao, "ENOSYS, retrying with snd_pcm_prepare().\n"); err = snd_pcm_prepare(p->alsa); } if (err < 0) MP_ERR(ao, "resuming from SUSPENDED: %s\n", snd_strerror(err)); continue; // Device lost. OPEN/SETUP are states we never enter after init, so // treat them like DISCONNECTED. case SND_PCM_STATE_DISCONNECTED: case SND_PCM_STATE_OPEN: case SND_PCM_STATE_SETUP: default: if (!p->device_lost) { MP_WARN(ao, "Device lost, trying to recover...\n"); ao_request_reload(ao); p->device_lost = true; } goto alsa_error; } } if (!state_ok) { MP_ERR(ao, "could not recover\n"); } alsa_error: if (state) { snd_pcm_sframes_t del = state_ok ? snd_pcm_status_get_delay(st) : 0; state->delay = MPMAX(del, 0) / (double)ao->samplerate; state->free_samples = state_ok ? snd_pcm_status_get_avail(st) : 0; state->free_samples = MPCLAMP(state->free_samples, 0, ao->device_buffer); // Align to period size. state->free_samples = state->free_samples / p->outburst * p->outburst; state->queued_samples = ao->device_buffer - state->free_samples; state->playing = pcmst == SND_PCM_STATE_RUNNING || pcmst == SND_PCM_STATE_PAUSED; } return state_ok; } static void audio_get_state(struct ao *ao, struct mp_pcm_state *state) { recover_and_get_state(ao, state); } static void audio_start(struct ao *ao) { struct priv *p = ao->priv; int err; recover_and_get_state(ao, NULL); err = snd_pcm_start(p->alsa); CHECK_ALSA_ERROR("pcm start error"); alsa_error: ; } static void audio_reset(struct ao *ao) { struct priv *p = ao->priv; int err; err = snd_pcm_drop(p->alsa); CHECK_ALSA_ERROR("pcm drop error"); err = snd_pcm_prepare(p->alsa); CHECK_ALSA_ERROR("pcm prepare error"); recover_and_get_state(ao, NULL); alsa_error: ; } static bool audio_set_paused(struct ao *ao, bool paused) { struct priv *p = ao->priv; int err; recover_and_get_state(ao, NULL); if (!p->can_pause) return false; snd_pcm_state_t pcmst = snd_pcm_state(p->alsa); if (pcmst == SND_PCM_STATE_RUNNING && paused) { err = snd_pcm_pause(p->alsa, 1); CHECK_ALSA_ERROR("pcm pause error"); } else if (pcmst == SND_PCM_STATE_PAUSED && !paused) { err = snd_pcm_pause(p->alsa, 0); CHECK_ALSA_ERROR("pcm resume error"); } return true; alsa_error: return false; } static bool audio_write(struct ao *ao, void **data, int samples) { struct priv *p = ao->priv; ao_convert_inplace(&p->convert, data, samples); if (!recover_and_get_state(ao, NULL)) return false; snd_pcm_sframes_t err = 0; if (af_fmt_is_planar(ao->format)) { err = snd_pcm_writen(p->alsa, data, samples); } else { err = snd_pcm_writei(p->alsa, data[0], samples); } CHECK_ALSA_ERROR("pcm write error"); if (err >= 0 && err != samples) { MP_ERR(ao, "unexpected partial write (%d of %d frames), dropping audio\n", (int)err, samples); } return true; alsa_error: return false; } static bool is_useless_device(char *name) { char *crap[] = {"rear", "center_lfe", "side", "pulse", "null", "dsnoop", "hw"}; for (int i = 0; i < MP_ARRAY_SIZE(crap); i++) { int l = strlen(crap[i]); if (name && strncmp(name, crap[i], l) == 0 && (!name[l] || name[l] == ':')) return true; } // The standard default entry will achieve exactly the same. if (name && strcmp(name, "default") == 0) return true; return false; } static void list_devs(struct ao *ao, struct ao_device_list *list) { void **hints; if (snd_device_name_hint(-1, "pcm", &hints) < 0) return; ao_device_list_add(list, ao, &(struct ao_device_desc){"", ""}); for (int n = 0; hints[n]; n++) { char *name = snd_device_name_get_hint(hints[n], "NAME"); char *desc = snd_device_name_get_hint(hints[n], "DESC"); char *io = snd_device_name_get_hint(hints[n], "IOID"); if (!is_useless_device(name) && (!io || strcmp(io, "Output") == 0)) { char desc2[1024]; snprintf(desc2, sizeof(desc2), "%s", desc ? desc : ""); for (int i = 0; desc2[i]; i++) { if (desc2[i] == '\n') desc2[i] = '/'; } ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc2}); } free(name); free(desc); free(io); } snd_device_name_free_hint(hints); } const struct ao_driver audio_out_alsa = { .description = "ALSA audio output", .name = "alsa", .init = init, .uninit = uninit, .control = control, .get_state = audio_get_state, .write = audio_write, .start = audio_start, .set_pause = audio_set_paused, .reset = audio_reset, .list_devs = list_devs, .priv_size = sizeof(struct priv), .global_opts = &ao_alsa_conf, };