| 1 | /* |
| 2 | * Pulseaudio common |
| 3 | * Copyright (c) 2014 Lukasz Marek |
| 4 | * Copyright (c) 2011 Luca Barbato <lu_zero@gentoo.org> |
| 5 | * |
| 6 | * This file is part of FFmpeg. |
| 7 | * |
| 8 | * FFmpeg is free software; you can redistribute it and/or |
| 9 | * modify it under the terms of the GNU Lesser General Public |
| 10 | * License as published by the Free Software Foundation; either |
| 11 | * version 2.1 of the License, or (at your option) any later version. |
| 12 | * |
| 13 | * FFmpeg is distributed in the hope that it will be useful, |
| 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 16 | * Lesser General Public License for more details. |
| 17 | * |
| 18 | * You should have received a copy of the GNU Lesser General Public |
| 19 | * License along with FFmpeg; if not, write to the Free Software |
| 20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 21 | */ |
| 22 | |
| 23 | #include "pulse_audio_common.h" |
| 24 | #include "libavutil/attributes.h" |
| 25 | #include "libavutil/avstring.h" |
| 26 | #include "libavutil/mem.h" |
| 27 | #include "libavutil/avassert.h" |
| 28 | |
| 29 | pa_sample_format_t av_cold ff_codec_id_to_pulse_format(enum AVCodecID codec_id) |
| 30 | { |
| 31 | switch (codec_id) { |
| 32 | case AV_CODEC_ID_PCM_U8: return PA_SAMPLE_U8; |
| 33 | case AV_CODEC_ID_PCM_ALAW: return PA_SAMPLE_ALAW; |
| 34 | case AV_CODEC_ID_PCM_MULAW: return PA_SAMPLE_ULAW; |
| 35 | case AV_CODEC_ID_PCM_S16LE: return PA_SAMPLE_S16LE; |
| 36 | case AV_CODEC_ID_PCM_S16BE: return PA_SAMPLE_S16BE; |
| 37 | case AV_CODEC_ID_PCM_F32LE: return PA_SAMPLE_FLOAT32LE; |
| 38 | case AV_CODEC_ID_PCM_F32BE: return PA_SAMPLE_FLOAT32BE; |
| 39 | case AV_CODEC_ID_PCM_S32LE: return PA_SAMPLE_S32LE; |
| 40 | case AV_CODEC_ID_PCM_S32BE: return PA_SAMPLE_S32BE; |
| 41 | case AV_CODEC_ID_PCM_S24LE: return PA_SAMPLE_S24LE; |
| 42 | case AV_CODEC_ID_PCM_S24BE: return PA_SAMPLE_S24BE; |
| 43 | default: return PA_SAMPLE_INVALID; |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | enum PulseAudioContextState { |
| 48 | PULSE_CONTEXT_INITIALIZING, |
| 49 | PULSE_CONTEXT_READY, |
| 50 | PULSE_CONTEXT_FINISHED |
| 51 | }; |
| 52 | |
| 53 | typedef struct PulseAudioDeviceList { |
| 54 | AVDeviceInfoList *devices; |
| 55 | int error_code; |
| 56 | int output; |
| 57 | char *default_device; |
| 58 | } PulseAudioDeviceList; |
| 59 | |
| 60 | static void pa_state_cb(pa_context *c, void *userdata) |
| 61 | { |
| 62 | enum PulseAudioContextState *context_state = userdata; |
| 63 | |
| 64 | switch (pa_context_get_state(c)) { |
| 65 | case PA_CONTEXT_FAILED: |
| 66 | case PA_CONTEXT_TERMINATED: |
| 67 | *context_state = PULSE_CONTEXT_FINISHED; |
| 68 | break; |
| 69 | case PA_CONTEXT_READY: |
| 70 | *context_state = PULSE_CONTEXT_READY; |
| 71 | break; |
| 72 | default: |
| 73 | break; |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | void ff_pulse_audio_disconnect_context(pa_mainloop **pa_ml, pa_context **pa_ctx) |
| 78 | { |
| 79 | av_assert0(pa_ml); |
| 80 | av_assert0(pa_ctx); |
| 81 | |
| 82 | if (*pa_ctx) { |
| 83 | pa_context_set_state_callback(*pa_ctx, NULL, NULL); |
| 84 | pa_context_disconnect(*pa_ctx); |
| 85 | pa_context_unref(*pa_ctx); |
| 86 | } |
| 87 | if (*pa_ml) |
| 88 | pa_mainloop_free(*pa_ml); |
| 89 | *pa_ml = NULL; |
| 90 | *pa_ctx = NULL; |
| 91 | } |
| 92 | |
| 93 | int ff_pulse_audio_connect_context(pa_mainloop **pa_ml, pa_context **pa_ctx, |
| 94 | const char *server, const char *description) |
| 95 | { |
| 96 | int ret; |
| 97 | pa_mainloop_api *pa_mlapi = NULL; |
| 98 | enum PulseAudioContextState context_state = PULSE_CONTEXT_INITIALIZING; |
| 99 | |
| 100 | av_assert0(pa_ml); |
| 101 | av_assert0(pa_ctx); |
| 102 | |
| 103 | *pa_ml = NULL; |
| 104 | *pa_ctx = NULL; |
| 105 | |
| 106 | if (!(*pa_ml = pa_mainloop_new())) |
| 107 | return AVERROR(ENOMEM); |
| 108 | if (!(pa_mlapi = pa_mainloop_get_api(*pa_ml))) { |
| 109 | ret = AVERROR_EXTERNAL; |
| 110 | goto fail; |
| 111 | } |
| 112 | if (!(*pa_ctx = pa_context_new(pa_mlapi, description))) { |
| 113 | ret = AVERROR(ENOMEM); |
| 114 | goto fail; |
| 115 | } |
| 116 | pa_context_set_state_callback(*pa_ctx, pa_state_cb, &context_state); |
| 117 | if (pa_context_connect(*pa_ctx, server, 0, NULL) < 0) { |
| 118 | ret = AVERROR_EXTERNAL; |
| 119 | goto fail; |
| 120 | } |
| 121 | |
| 122 | while (context_state == PULSE_CONTEXT_INITIALIZING) |
| 123 | pa_mainloop_iterate(*pa_ml, 1, NULL); |
| 124 | if (context_state == PULSE_CONTEXT_FINISHED) { |
| 125 | ret = AVERROR_EXTERNAL; |
| 126 | goto fail; |
| 127 | } |
| 128 | return 0; |
| 129 | |
| 130 | fail: |
| 131 | ff_pulse_audio_disconnect_context(pa_ml, pa_ctx); |
| 132 | return ret; |
| 133 | } |
| 134 | |
| 135 | static void pulse_add_detected_device(PulseAudioDeviceList *info, |
| 136 | const char *name, const char *description) |
| 137 | { |
| 138 | int ret; |
| 139 | AVDeviceInfo *new_device = NULL; |
| 140 | |
| 141 | if (info->error_code) |
| 142 | return; |
| 143 | |
| 144 | new_device = av_mallocz(sizeof(AVDeviceInfo)); |
| 145 | if (!new_device) { |
| 146 | info->error_code = AVERROR(ENOMEM); |
| 147 | return; |
| 148 | } |
| 149 | |
| 150 | new_device->device_description = av_strdup(description); |
| 151 | new_device->device_name = av_strdup(name); |
| 152 | |
| 153 | if (!new_device->device_description || !new_device->device_name) { |
| 154 | info->error_code = AVERROR(ENOMEM); |
| 155 | goto fail; |
| 156 | } |
| 157 | |
| 158 | if ((ret = av_dynarray_add_nofree(&info->devices->devices, |
| 159 | &info->devices->nb_devices, new_device)) < 0) { |
| 160 | info->error_code = ret; |
| 161 | goto fail; |
| 162 | } |
| 163 | return; |
| 164 | |
| 165 | fail: |
| 166 | av_freep(&new_device->device_description); |
| 167 | av_freep(&new_device->device_name); |
| 168 | av_free(new_device); |
| 169 | |
| 170 | } |
| 171 | |
| 172 | static void pulse_audio_source_device_cb(pa_context *c, const pa_source_info *dev, |
| 173 | int eol, void *userdata) |
| 174 | { |
| 175 | if (!eol) |
| 176 | pulse_add_detected_device(userdata, dev->name, dev->description); |
| 177 | } |
| 178 | |
| 179 | static void pulse_audio_sink_device_cb(pa_context *c, const pa_sink_info *dev, |
| 180 | int eol, void *userdata) |
| 181 | { |
| 182 | if (!eol) |
| 183 | pulse_add_detected_device(userdata, dev->name, dev->description); |
| 184 | } |
| 185 | |
| 186 | static void pulse_server_info_cb(pa_context *c, const pa_server_info *i, void *userdata) |
| 187 | { |
| 188 | PulseAudioDeviceList *info = userdata; |
| 189 | if (info->output) |
| 190 | info->default_device = av_strdup(i->default_sink_name); |
| 191 | else |
| 192 | info->default_device = av_strdup(i->default_source_name); |
| 193 | if (!info->default_device) |
| 194 | info->error_code = AVERROR(ENOMEM); |
| 195 | } |
| 196 | |
| 197 | int ff_pulse_audio_get_devices(AVDeviceInfoList *devices, const char *server, int output) |
| 198 | { |
| 199 | pa_mainloop *pa_ml = NULL; |
| 200 | pa_operation *pa_op = NULL; |
| 201 | pa_context *pa_ctx = NULL; |
| 202 | enum pa_operation_state op_state; |
| 203 | PulseAudioDeviceList dev_list = { 0 }; |
| 204 | int i; |
| 205 | |
| 206 | dev_list.output = output; |
| 207 | dev_list.devices = devices; |
| 208 | if (!devices) |
| 209 | return AVERROR(EINVAL); |
| 210 | devices->nb_devices = 0; |
| 211 | devices->devices = NULL; |
| 212 | |
| 213 | if ((dev_list.error_code = ff_pulse_audio_connect_context(&pa_ml, &pa_ctx, server, "Query devices")) < 0) |
| 214 | goto fail; |
| 215 | |
| 216 | if (output) |
| 217 | pa_op = pa_context_get_sink_info_list(pa_ctx, pulse_audio_sink_device_cb, &dev_list); |
| 218 | else |
| 219 | pa_op = pa_context_get_source_info_list(pa_ctx, pulse_audio_source_device_cb, &dev_list); |
| 220 | while ((op_state = pa_operation_get_state(pa_op)) == PA_OPERATION_RUNNING) |
| 221 | pa_mainloop_iterate(pa_ml, 1, NULL); |
| 222 | if (op_state != PA_OPERATION_DONE) |
| 223 | dev_list.error_code = AVERROR_EXTERNAL; |
| 224 | pa_operation_unref(pa_op); |
| 225 | if (dev_list.error_code < 0) |
| 226 | goto fail; |
| 227 | |
| 228 | pa_op = pa_context_get_server_info(pa_ctx, pulse_server_info_cb, &dev_list); |
| 229 | while ((op_state = pa_operation_get_state(pa_op)) == PA_OPERATION_RUNNING) |
| 230 | pa_mainloop_iterate(pa_ml, 1, NULL); |
| 231 | if (op_state != PA_OPERATION_DONE) |
| 232 | dev_list.error_code = AVERROR_EXTERNAL; |
| 233 | pa_operation_unref(pa_op); |
| 234 | if (dev_list.error_code < 0) |
| 235 | goto fail; |
| 236 | |
| 237 | devices->default_device = -1; |
| 238 | for (i = 0; i < devices->nb_devices; i++) { |
| 239 | if (!strcmp(devices->devices[i]->device_name, dev_list.default_device)) { |
| 240 | devices->default_device = i; |
| 241 | break; |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | fail: |
| 246 | av_free(dev_list.default_device); |
| 247 | ff_pulse_audio_disconnect_context(&pa_ml, &pa_ctx); |
| 248 | return dev_list.error_code; |
| 249 | } |