summaryrefslogtreecommitdiff
path: root/src/modules/alsa/alsa-mixer.h
blob: b56d88113f65f64804ad532c61a1287f5b1e36b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
#ifndef fooalsamixerhfoo
#define fooalsamixerhfoo

/***
  This file is part of PulseAudio.

  Copyright 2004-2006 Lennart Poettering
  Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB

  PulseAudio 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.

  PulseAudio 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
  General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/

#include <alsa/asoundlib.h>

#include <pulse/sample.h>
#include <pulse/mainloop-api.h>
#include <pulse/channelmap.h>
#include <pulse/volume.h>

#include <pulsecore/llist.h>
#include <pulsecore/rtpoll.h>

typedef struct pa_alsa_fdlist pa_alsa_fdlist;
typedef struct pa_alsa_mixer pa_alsa_mixer;
typedef struct pa_alsa_mixer_pdata pa_alsa_mixer_pdata;
typedef struct pa_alsa_setting pa_alsa_setting;
typedef struct pa_alsa_mixer_id pa_alsa_mixer_id;
typedef struct pa_alsa_option pa_alsa_option;
typedef struct pa_alsa_element pa_alsa_element;
typedef struct pa_alsa_jack pa_alsa_jack;
typedef struct pa_alsa_path pa_alsa_path;
typedef struct pa_alsa_path_set pa_alsa_path_set;
typedef struct pa_alsa_mapping pa_alsa_mapping;
typedef struct pa_alsa_profile pa_alsa_profile;
typedef struct pa_alsa_decibel_fix pa_alsa_decibel_fix;
typedef struct pa_alsa_profile_set pa_alsa_profile_set;
typedef struct pa_alsa_port_data pa_alsa_port_data;

#include "alsa-util.h"
#include "alsa-ucm.h"

#define POSITION_MASK_CHANNELS 8

typedef enum pa_alsa_switch_use {
    PA_ALSA_SWITCH_IGNORE,
    PA_ALSA_SWITCH_MUTE,   /* make this switch follow mute status */
    PA_ALSA_SWITCH_OFF,    /* set this switch to 'off' unconditionally */
    PA_ALSA_SWITCH_ON,     /* set this switch to 'on' unconditionally */
    PA_ALSA_SWITCH_SELECT  /* allow the user to select switch status through a setting */
} pa_alsa_switch_use_t;

typedef enum pa_alsa_volume_use {
    PA_ALSA_VOLUME_IGNORE,
    PA_ALSA_VOLUME_MERGE,   /* merge this volume slider into the global volume slider */
    PA_ALSA_VOLUME_OFF,     /* set this volume to minimal unconditionally */
    PA_ALSA_VOLUME_ZERO,    /* set this volume to 0dB unconditionally */
    PA_ALSA_VOLUME_CONSTANT /* set this volume to a constant value unconditionally */
} pa_alsa_volume_use_t;

typedef enum pa_alsa_enumeration_use {
    PA_ALSA_ENUMERATION_IGNORE,
    PA_ALSA_ENUMERATION_SELECT
} pa_alsa_enumeration_use_t;

typedef enum pa_alsa_required {
    PA_ALSA_REQUIRED_IGNORE,
    PA_ALSA_REQUIRED_SWITCH,
    PA_ALSA_REQUIRED_VOLUME,
    PA_ALSA_REQUIRED_ENUMERATION,
    PA_ALSA_REQUIRED_ANY
} pa_alsa_required_t;

typedef enum pa_alsa_direction {
    PA_ALSA_DIRECTION_ANY,
    PA_ALSA_DIRECTION_OUTPUT,
    PA_ALSA_DIRECTION_INPUT
} pa_alsa_direction_t;

/* A setting combines a couple of options into a single entity that
 * may be selected. Only one setting can be active at the same
 * time. */
struct pa_alsa_setting {
    pa_alsa_path *path;
    PA_LLIST_FIELDS(pa_alsa_setting);

    pa_idxset *options;

    char *name;
    char *description;
    unsigned priority;
};

/* An entry for one ALSA mixer */
struct pa_alsa_mixer {
    struct pa_alsa_mixer *alias;
    snd_mixer_t *mixer_handle;
    pa_alsa_fdlist *fdl;
    bool used_for_probe_only:1;
};

/* ALSA mixer element identifier */
struct pa_alsa_mixer_id {
    char *name;
    int index;
};

char *pa_alsa_mixer_id_to_string(char *dst, size_t dst_len, pa_alsa_mixer_id *id);

/* An option belongs to an element and refers to one enumeration item
 * of the element is an enumeration item, or a switch status if the
 * element is a switch item. */
struct pa_alsa_option {
    pa_alsa_element *element;
    PA_LLIST_FIELDS(pa_alsa_option);

    char *alsa_name;
    int alsa_idx;

    char *name;
    char *description;
    unsigned priority;

    pa_alsa_required_t required;
    pa_alsa_required_t required_any;
    pa_alsa_required_t required_absent;
};

/* An element wraps one specific ALSA element. A series of elements
 * make up a path (see below). If the element is an enumeration or switch
 * element it may include a list of options. */
struct pa_alsa_element {
    pa_alsa_path *path;
    PA_LLIST_FIELDS(pa_alsa_element);

    struct pa_alsa_mixer_id alsa_id;
    pa_alsa_direction_t direction;

    pa_alsa_switch_use_t switch_use;
    pa_alsa_volume_use_t volume_use;
    pa_alsa_enumeration_use_t enumeration_use;

    pa_alsa_required_t required;
    pa_alsa_required_t required_any;
    pa_alsa_required_t required_absent;

    long constant_volume;

    unsigned int override_map;
    bool direction_try_other:1;

    bool has_dB:1;
    long min_volume, max_volume;
    long volume_limit; /* -1 for no configured limit */
    double min_dB, max_dB;

    pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][POSITION_MASK_CHANNELS];
    unsigned n_channels;

    pa_channel_position_mask_t merged_mask;

    PA_LLIST_HEAD(pa_alsa_option, options);

    pa_alsa_decibel_fix *db_fix;
};

struct pa_alsa_jack {
    pa_alsa_path *path;
    PA_LLIST_FIELDS(pa_alsa_jack);

    snd_mixer_t *mixer_handle;
    char *mixer_device_name;

    struct pa_alsa_mixer_id alsa_id;
    char *name; /* E g "Headphone" */
    bool has_control; /* is the jack itself present? */
    bool plugged_in; /* is this jack currently plugged in? */
    snd_mixer_elem_t *melem; /* Jack detection handle */
    pa_available_t state_unplugged, state_plugged;

    pa_alsa_required_t required;
    pa_alsa_required_t required_any;
    pa_alsa_required_t required_absent;

    pa_dynarray *ucm_devices; /* pa_alsa_ucm_device */
    pa_dynarray *ucm_hw_mute_devices; /* pa_alsa_ucm_device */

    bool append_pcm_to_name;
};

pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name, int index);
void pa_alsa_jack_free(pa_alsa_jack *jack);
void pa_alsa_jack_set_has_control(pa_alsa_jack *jack, bool has_control);
void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in);
void pa_alsa_jack_add_ucm_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device);
void pa_alsa_jack_add_ucm_hw_mute_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device);

/* A path wraps a series of elements into a single entity which can be
 * used to control it as if it had a single volume slider, a single
 * mute switch and a single list of selectable options. */
struct pa_alsa_path {
    pa_alsa_direction_t direction;
    pa_device_port* port;

    char *name;
    char *description_key;
    char *description;
    char *availability_group;
    pa_device_port_type_t device_port_type;
    unsigned priority;
    bool autodetect_eld_device;
    pa_alsa_mixer *eld_mixer_handle;
    int eld_device;
    pa_proplist *proplist;

    bool probed:1;
    bool supported:1;
    bool has_mute:1;
    bool has_volume:1;
    bool has_dB:1;
    bool mute_during_activation:1;
    /* These two are used during probing only */
    bool has_req_any:1;
    bool req_any_present:1;

    long min_volume, max_volume;
    double min_dB, max_dB;

    /* This is used during parsing only, as a shortcut so that we
     * don't have to iterate the list all the time */
    pa_alsa_element *last_element;
    pa_alsa_option *last_option;
    pa_alsa_setting *last_setting;
    pa_alsa_jack *last_jack;

    PA_LLIST_HEAD(pa_alsa_element, elements);
    PA_LLIST_HEAD(pa_alsa_setting, settings);
    PA_LLIST_HEAD(pa_alsa_jack, jacks);
};

/* A path set is simply a set of paths that are applicable to a
 * device */
struct pa_alsa_path_set {
    pa_hashmap *paths;
    pa_alsa_direction_t direction;
};

void pa_alsa_setting_dump(pa_alsa_setting *s);

void pa_alsa_option_dump(pa_alsa_option *o);
void pa_alsa_jack_dump(pa_alsa_jack *j);
void pa_alsa_element_dump(pa_alsa_element *e);

pa_alsa_path *pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction);
pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction);
pa_alsa_element *pa_alsa_element_get(pa_alsa_path *p, const char *section, bool prefixed);
int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m, bool ignore_dB);
void pa_alsa_path_dump(pa_alsa_path *p);
int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v);
int pa_alsa_path_get_mute(pa_alsa_path *path, snd_mixer_t *m, bool *muted);
int pa_alsa_path_set_volume(pa_alsa_path *path, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, bool deferred_volume, bool write_to_hw);
int pa_alsa_path_set_mute(pa_alsa_path *path, snd_mixer_t *m, bool muted);
int pa_alsa_path_select(pa_alsa_path *p, pa_alsa_setting *s, snd_mixer_t *m, bool device_is_muted);
void pa_alsa_path_set_callback(pa_alsa_path *p, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata);
void pa_alsa_path_free(pa_alsa_path *p);

pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction, const char *paths_dir);
void pa_alsa_path_set_dump(pa_alsa_path_set *s);
void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata);
void pa_alsa_path_set_free(pa_alsa_path_set *s);
int pa_alsa_path_set_is_empty(pa_alsa_path_set *s);

struct pa_alsa_mapping {
    pa_alsa_profile_set *profile_set;

    char *name;
    char *description;
    char *description_key;
    unsigned priority;
    pa_alsa_direction_t direction;
    /* These are copied over to the resultant sink/source */
    pa_proplist *proplist;

    pa_sample_spec sample_spec;
    pa_channel_map channel_map;

    char **device_strings;

    char **input_path_names;
    char **output_path_names;
    char **input_element; /* list of fallbacks */
    char **output_element;
    pa_alsa_path_set *input_path_set;
    pa_alsa_path_set *output_path_set;

    unsigned supported;
    bool exact_channels:1;
    bool fallback:1;

    /* The "y" in "hw:x,y". This is set to -1 before the device index has been
     * queried, or if the query failed. */
    int hw_device_index;

    /* Temporarily used during probing */
    snd_pcm_t *input_pcm;
    snd_pcm_t *output_pcm;

    pa_sink *sink;
    pa_source *source;

    /* ucm device context */
    pa_alsa_ucm_mapping_context ucm_context;
};

struct pa_alsa_profile {
    pa_alsa_profile_set *profile_set;

    char *name;
    char *description;
    char *description_key;
    unsigned priority;

    char *input_name;
    char *output_name;

    bool supported:1;
    bool fallback_input:1;
    bool fallback_output:1;

    char **input_mapping_names;
    char **output_mapping_names;

    pa_idxset *input_mappings;
    pa_idxset *output_mappings;

    /* ucm device context */
    pa_alsa_ucm_profile_context ucm_context;
};

struct pa_alsa_decibel_fix {
    char *key;

    pa_alsa_profile_set *profile_set;

    char *name; /* Alsa volume element name. */
    int index;  /* Alsa volume element index. */
    long min_step;
    long max_step;

    /* An array that maps alsa volume element steps to decibels. The steps can
     * be used as indices to this array, after subtracting min_step from the
     * real value.
     *
     * The values are actually stored as integers representing millibels,
     * because that's the format the alsa API uses. */
    long *db_values;
};

struct pa_alsa_profile_set {
    pa_hashmap *mappings;
    pa_hashmap *profiles;
    pa_hashmap *decibel_fixes;
    pa_hashmap *input_paths;
    pa_hashmap *output_paths;

    bool auto_profiles;
    bool ignore_dB:1;
    bool probed:1;
};

void pa_alsa_mapping_dump(pa_alsa_mapping *m);
void pa_alsa_profile_dump(pa_alsa_profile *p);
void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix);
pa_alsa_mapping *pa_alsa_mapping_get(pa_alsa_profile_set *ps, const char *name);

pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus);
void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, pa_hashmap *mixers, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec);
void pa_alsa_profile_set_free(pa_alsa_profile_set *s);
void pa_alsa_profile_set_dump(pa_alsa_profile_set *s);
void pa_alsa_profile_set_drop_unsupported(pa_alsa_profile_set *s);

pa_alsa_fdlist *pa_alsa_fdlist_new(void);
void pa_alsa_fdlist_free(pa_alsa_fdlist *fdl);
int pa_alsa_fdlist_set_handle(pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, snd_hctl_t *hctl_handle, pa_mainloop_api* m);

/* Alternative for handling alsa mixer events in io-thread. */

pa_alsa_mixer_pdata *pa_alsa_mixer_pdata_new(void);
void pa_alsa_mixer_pdata_free(pa_alsa_mixer_pdata *pd);
int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer, pa_rtpoll *rtp);

/* Data structure for inclusion in pa_device_port for alsa
 * sinks/sources. This contains nothing that needs to be freed
 * individually */
struct pa_alsa_port_data {
    pa_alsa_path *path;
    pa_alsa_setting *setting;
    bool suspend_when_unavailable;
};

void pa_alsa_add_ports(void *sink_or_source_new_data, pa_alsa_path_set *ps, pa_card *card);
void pa_alsa_path_set_add_ports(pa_alsa_path_set *ps, pa_card_profile *cp, pa_hashmap *ports, pa_hashmap *extra, pa_core *core);

#endif