mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
alsa-acp: Add libacp based card device
libacp is a port and wrapper around the pulseaudio card profile code. It uses a set of templates for construct a card profile and mixer port settings. It also has support for UCM when available for the hardware.
This commit is contained in:
parent
5c6247daef
commit
1612f5e4d2
31 changed files with 15304 additions and 11 deletions
|
|
@ -453,3 +453,6 @@
|
|||
#mesondefine PIPEWIRE_VERSION_MAJOR
|
||||
#mesondefine PIPEWIRE_VERSION_MINOR
|
||||
#mesondefine PIPEWIRE_VERSION_MICRO
|
||||
|
||||
#mesondefine PA_ALSA_PATHS_DIR
|
||||
#mesondefine PA_ALSA_PROFILE_SETS_DIR
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ else
|
|||
endif
|
||||
|
||||
spa_plugindir = join_paths(pipewire_libdir, spa_name)
|
||||
alsadatadir = join_paths('/usr', 'share', 'pulseaudio', 'alsa-mixer')
|
||||
|
||||
pipewire_headers_dir = join_paths(pipewire_name, 'pipewire')
|
||||
|
||||
|
|
@ -179,6 +180,8 @@ cdata.set('VERSION', '"@0@"'.format(pipewire_version))
|
|||
cdata.set('PLUGINDIR', '"@0@"'.format(spa_plugindir))
|
||||
# FIXME: --with-memory-alignment],[8,N,malloc,pagesize (default is 32)]) option
|
||||
cdata.set('MEMORY_ALIGNMENT_MALLOC', 1)
|
||||
cdata.set_quoted('PA_ALSA_PATHS_DIR', join_paths(alsadatadir, 'paths'))
|
||||
cdata.set_quoted('PA_ALSA_PROFILE_SETS_DIR', join_paths(alsadatadir, 'profile-sets'))
|
||||
|
||||
|
||||
check_headers = [['dlfcn.h','HAVE_DLFCN_H'],
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ extern "C" {
|
|||
static inline int spa_debug_dict(int indent, const struct spa_dict *dict)
|
||||
{
|
||||
const struct spa_dict_item *item;
|
||||
spa_debug("%*s flags:%08x n_items:%d", indent, "", dict->flags, dict->n_items);
|
||||
spa_debug("%*sflags:%08x n_items:%d", indent, "", dict->flags, dict->n_items);
|
||||
spa_dict_for_each(item, dict) {
|
||||
spa_debug("%*s%s = \"%s\"", indent, "", item->key, item->value);
|
||||
spa_debug("%*s %s = \"%s\"", indent, "", item->key, item->value);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ extern "C" {
|
|||
* playback of midi */
|
||||
#define SPA_NAME_API_ALSA_SEQ_BRIDGE "api.alsa.seq.bridge" /**< an alsa Node interface for
|
||||
* bridging midi ports */
|
||||
#define SPA_NAME_API_ALSA_ACP_DEVICE "api.alsa.acp.device" /**< an alsa ACP Device interface */
|
||||
|
||||
/** keys for bluez5 factory names */
|
||||
#define SPA_NAME_API_BLUEZ5_ENUM_DBUS "api.bluez5.enum.dbus" /**< a dbus Device interface */
|
||||
|
|
|
|||
1352
spa/plugins/alsa/acp/acp.c
Normal file
1352
spa/plugins/alsa/acp/acp.c
Normal file
File diff suppressed because it is too large
Load diff
239
spa/plugins/alsa/acp/acp.h
Normal file
239
spa/plugins/alsa/acp/acp.h
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
/* ALSA Card Profile
|
||||
*
|
||||
* Copyright © 2020 Wim Taymans
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef ACP_H
|
||||
#define ACP_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <poll.h>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define ACP_PRINTF_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1)))
|
||||
#else
|
||||
#define ACP_PRINTF_FUNC(fmt, arg1)
|
||||
#endif
|
||||
|
||||
struct acp_dict_item {
|
||||
const char *key;
|
||||
const char *value;
|
||||
};
|
||||
#define ACP_DICT_ITEM_INIT(key,value) (struct acp_dict_item) { key, value }
|
||||
|
||||
struct acp_dict {
|
||||
uint32_t flags;
|
||||
uint32_t n_items;
|
||||
const struct acp_dict_item *items;
|
||||
};
|
||||
|
||||
struct acp_format {
|
||||
uint32_t flags;
|
||||
uint32_t format_mask;
|
||||
uint32_t rate_mask;
|
||||
uint32_t channels;
|
||||
uint32_t map[64];
|
||||
};
|
||||
|
||||
#define ACP_DICT_INIT(items,n_items) (struct acp_dict) { 0, n_items, items }
|
||||
#define ACP_DICT_INIT_ARRAY(items) (struct acp_dict) { 0, sizeof(items)/sizeof((items)[0]), items }
|
||||
|
||||
#define acp_dict_for_each(item, dict) \
|
||||
for ((item) = (dict)->items; \
|
||||
(item) < &(dict)->items[(dict)->n_items]; \
|
||||
(item)++)
|
||||
|
||||
enum acp_direction {
|
||||
ACP_DIRECTION_PLAYBACK = 1,
|
||||
ACP_DIRECTION_CAPTURE = 2
|
||||
};
|
||||
|
||||
enum acp_available {
|
||||
ACP_AVAILABLE_UNKNOWN = 0,
|
||||
ACP_AVAILABLE_NO = 1,
|
||||
ACP_AVAILABLE_YES = 2
|
||||
};
|
||||
|
||||
enum acp_port_type {
|
||||
ACP_PORT_TYPE_UNKNOWN = 0,
|
||||
ACP_PORT_TYPE_AUX = 1,
|
||||
ACP_PORT_TYPE_SPEAKER = 2,
|
||||
ACP_PORT_TYPE_HEADPHONES = 3,
|
||||
ACP_PORT_TYPE_LINE = 4,
|
||||
ACP_PORT_TYPE_MIC = 5,
|
||||
ACP_PORT_TYPE_HEADSET = 6,
|
||||
ACP_PORT_TYPE_HANDSET = 7,
|
||||
ACP_PORT_TYPE_EARPIECE = 8,
|
||||
ACP_PORT_TYPE_SPDIF = 9,
|
||||
ACP_PORT_TYPE_HDMI = 10,
|
||||
ACP_PORT_TYPE_TV = 11,
|
||||
ACP_PORT_TYPE_RADIO = 12,
|
||||
ACP_PORT_TYPE_VIDEO = 13,
|
||||
ACP_PORT_TYPE_USB = 14,
|
||||
ACP_PORT_TYPE_BLUETOOTH = 15,
|
||||
ACP_PORT_TYPE_PORTABLE = 16,
|
||||
ACP_PORT_TYPE_HANDSFREE = 17,
|
||||
ACP_PORT_TYPE_CAR = 18,
|
||||
ACP_PORT_TYPE_HIFI = 19,
|
||||
ACP_PORT_TYPE_PHONE = 20,
|
||||
ACP_PORT_TYPE_NETWORK = 21,
|
||||
ACP_PORT_TYPE_ANALOG = 22,
|
||||
};
|
||||
|
||||
struct acp_device;
|
||||
|
||||
struct acp_card_events {
|
||||
#define ACP_VERSION_CARD_EVENTS 0
|
||||
uint32_t version;
|
||||
|
||||
void (*destroy) (void *data);
|
||||
|
||||
void (*props_changed) (void *data);
|
||||
|
||||
void (*profile_changed) (void *data, uint32_t old_index, uint32_t new_index);
|
||||
|
||||
void (*profile_available) (void *data, uint32_t index,
|
||||
enum acp_available old, enum acp_available available);
|
||||
|
||||
void (*port_available) (void *data, uint32_t index,
|
||||
enum acp_available old, enum acp_available available);
|
||||
|
||||
void (*volume_changed) (void *data, struct acp_device *dev);
|
||||
void (*mute_changed) (void *data, struct acp_device *dev);
|
||||
};
|
||||
|
||||
struct acp_port {
|
||||
uint32_t index;
|
||||
#define ACP_PORT_ACTIVE (1<<0)
|
||||
uint32_t flags;
|
||||
|
||||
char *name;
|
||||
char *description;
|
||||
uint32_t priority;
|
||||
enum acp_direction direction;
|
||||
enum acp_available available;
|
||||
char *available_group; /* a string indentifier which determine the group of devices
|
||||
* handling the available state simulteneously */
|
||||
enum acp_port_type type;
|
||||
|
||||
struct acp_dict props;
|
||||
|
||||
uint32_t n_profiles;
|
||||
struct acp_card_profile **profiles;
|
||||
};
|
||||
|
||||
struct acp_device {
|
||||
uint32_t index;
|
||||
#define ACP_DEVICE_ACTIVE (1<<0)
|
||||
#define ACP_DEVICE_HW_VOLUME (1<<1)
|
||||
#define ACP_DEVICE_HW_MUTE (1<<2)
|
||||
uint32_t flags;
|
||||
|
||||
char *name;
|
||||
char *description;
|
||||
uint32_t priority;
|
||||
enum acp_direction direction;
|
||||
struct acp_dict props;
|
||||
|
||||
char **device_strings;
|
||||
struct acp_format format;
|
||||
|
||||
struct acp_port **ports;
|
||||
uint32_t n_ports;
|
||||
};
|
||||
|
||||
struct acp_card_profile {
|
||||
uint32_t index;
|
||||
#define ACP_PROFILE_ACTIVE (1<<0)
|
||||
uint32_t flags;
|
||||
|
||||
char *name;
|
||||
char *description;
|
||||
uint32_t priority;
|
||||
enum acp_available available;
|
||||
struct acp_dict props;
|
||||
|
||||
uint32_t n_devices;
|
||||
struct acp_device **devices;
|
||||
};
|
||||
|
||||
struct acp_card {
|
||||
uint32_t index;
|
||||
uint32_t flags;
|
||||
|
||||
struct acp_dict props;
|
||||
|
||||
uint32_t n_profiles;
|
||||
struct acp_card_profile **profiles;
|
||||
uint32_t active_profile_index;
|
||||
|
||||
uint32_t n_devices;
|
||||
struct acp_device **devices;
|
||||
|
||||
uint32_t n_ports;
|
||||
struct acp_port **ports;
|
||||
uint32_t preferred_input_port_index;
|
||||
uint32_t preferred_output_port_index;
|
||||
};
|
||||
|
||||
struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props);
|
||||
|
||||
void acp_card_add_listener(struct acp_card *card,
|
||||
const struct acp_card_events *events, void *user_data);
|
||||
|
||||
void acp_card_destroy(struct acp_card *card);
|
||||
|
||||
int acp_card_poll_descriptors_count(struct acp_card *card);
|
||||
int acp_card_poll_descriptors(struct acp_card *card, struct pollfd *pfds, unsigned int space);
|
||||
int acp_card_poll_descriptors_revents(struct acp_card *card, struct pollfd *pfds,
|
||||
unsigned int nfds, unsigned short *revents);
|
||||
int acp_card_handle_events(struct acp_card *card);
|
||||
|
||||
int acp_card_set_profile(struct acp_card *card, uint32_t profile_index);
|
||||
|
||||
int acp_device_set_port(struct acp_device *dev, uint32_t port_index);
|
||||
|
||||
int acp_device_set_volume(struct acp_device *dev, const float *volume, uint32_t n_volume);
|
||||
int acp_device_get_volume(struct acp_device *dev, float *volume, uint32_t n_volume);
|
||||
int acp_device_set_mute(struct acp_device *dev, bool mute);
|
||||
int acp_device_get_mute(struct acp_device *dev, bool *mute);
|
||||
|
||||
typedef void (*acp_log_func) (void *data,
|
||||
int level, const char *file, int line, const char *func,
|
||||
const char *fmt, va_list arg) ACP_PRINTF_FUNC(6,0);
|
||||
|
||||
void acp_set_log_func(acp_log_func, void *data);
|
||||
void acp_set_log_level(int level);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ACP_H */
|
||||
4919
spa/plugins/alsa/acp/alsa-mixer.c
Normal file
4919
spa/plugins/alsa/acp/alsa-mixer.c
Normal file
File diff suppressed because it is too large
Load diff
432
spa/plugins/alsa/acp/alsa-mixer.h
Normal file
432
spa/plugins/alsa/acp/alsa-mixer.h
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
#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>
|
||||
|
||||
typedef struct pa_alsa_mixer pa_alsa_mixer;
|
||||
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;
|
||||
typedef struct pa_alsa_profile pa_alsa_profile;
|
||||
typedef struct pa_alsa_device pa_alsa_device;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
#include "acp.h"
|
||||
#include "device-port.h"
|
||||
#include "alsa-util.h"
|
||||
#include "alsa-ucm.h"
|
||||
#include "card.h"
|
||||
|
||||
/* 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 {
|
||||
snd_mixer_t *mixer_handle;
|
||||
int card_index;
|
||||
bool used_for_probe_only:1;
|
||||
bool used_for_poll:1;
|
||||
};
|
||||
|
||||
/* ALSA mixer element identifier */
|
||||
struct pa_alsa_mixer_id {
|
||||
char *name;
|
||||
int index;
|
||||
};
|
||||
|
||||
/* 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;
|
||||
|
||||
bool override_map:1;
|
||||
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][2];
|
||||
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;
|
||||
|
||||
char *name; /* E g "Headphone" */
|
||||
char *alsa_name; /* E g "Headphone Jack" */
|
||||
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);
|
||||
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 *available_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_device {
|
||||
struct acp_device device;
|
||||
|
||||
pa_card *card;
|
||||
|
||||
pa_alsa_direction_t direction;
|
||||
pa_proplist *proplist;
|
||||
|
||||
pa_alsa_mapping *mapping;
|
||||
pa_alsa_ucm_mapping_context *ucm_context;
|
||||
|
||||
pa_hashmap *ports;
|
||||
pa_dynarray port_array;
|
||||
pa_device_port *active_port;
|
||||
|
||||
snd_mixer_t *mixer_handle;
|
||||
pa_alsa_path_set *mixer_path_set;
|
||||
pa_alsa_path *mixer_path;
|
||||
snd_pcm_t *pcm_handle;
|
||||
|
||||
unsigned muted:1;
|
||||
unsigned decibel_volume:1;
|
||||
pa_cvolume real_volume;
|
||||
|
||||
pa_volume_t base_volume;
|
||||
unsigned n_volume_steps;
|
||||
|
||||
int (*read_volume)(pa_alsa_device *dev);
|
||||
int (*read_mute)(pa_alsa_device *dev);
|
||||
|
||||
void (*set_volume)(pa_alsa_device *dev, const pa_cvolume *v);
|
||||
void (*set_mute)(pa_alsa_device *dev, bool m);
|
||||
};
|
||||
|
||||
struct pa_alsa_mapping {
|
||||
pa_alsa_profile_set *profile_set;
|
||||
|
||||
char *name;
|
||||
char *description;
|
||||
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_proplist *input_proplist;
|
||||
pa_proplist *output_proplist;
|
||||
|
||||
pa_alsa_device output;
|
||||
pa_alsa_device input;
|
||||
|
||||
/* ucm device context*/
|
||||
pa_alsa_ucm_mapping_context ucm_context;
|
||||
};
|
||||
|
||||
struct pa_alsa_profile {
|
||||
struct acp_card_profile profile;
|
||||
|
||||
pa_alsa_profile_set *profile_set;
|
||||
|
||||
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;
|
||||
|
||||
struct {
|
||||
pa_dynarray devices;
|
||||
} out;
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
void pa_alsa_mixer_use_for_poll(pa_hashmap *mixers, snd_mixer_t *mixer_handle);
|
||||
|
||||
/* 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(pa_hashmap *ports, pa_alsa_path_set *ps, pa_card *card);
|
||||
void pa_alsa_path_set_add_ports(pa_alsa_path_set *ps, pa_alsa_profile *cp, pa_hashmap *ports, pa_hashmap *extra, pa_core *core);
|
||||
|
||||
#endif
|
||||
2367
spa/plugins/alsa/acp/alsa-ucm.c
Normal file
2367
spa/plugins/alsa/acp/alsa-ucm.c
Normal file
File diff suppressed because it is too large
Load diff
300
spa/plugins/alsa/acp/alsa-ucm.h
Normal file
300
spa/plugins/alsa/acp/alsa-ucm.h
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
#ifndef fooalsaucmhfoo
|
||||
#define fooalsaucmhfoo
|
||||
|
||||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2011 Wolfson Microelectronics PLC
|
||||
Author Margarita Olaya <magi@slimlogic.co.uk>
|
||||
Copyright 2012 Feng Wei <wei.feng@freescale.com>, Freescale Ltd.
|
||||
|
||||
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/>.
|
||||
***/
|
||||
|
||||
#ifdef HAVE_ALSA_UCM
|
||||
#include <alsa/use-case.h>
|
||||
#else
|
||||
typedef void snd_use_case_mgr_t;
|
||||
#endif
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
#include "alsa-mixer.h"
|
||||
|
||||
/** For devices: List of verbs, devices or modifiers available */
|
||||
#define PA_ALSA_PROP_UCM_NAME "alsa.ucm.name"
|
||||
|
||||
/** For devices: List of supported devices per verb*/
|
||||
#define PA_ALSA_PROP_UCM_DESCRIPTION "alsa.ucm.description"
|
||||
|
||||
/** For devices: Playback device name e.g PlaybackPCM */
|
||||
#define PA_ALSA_PROP_UCM_SINK "alsa.ucm.sink"
|
||||
|
||||
/** For devices: Capture device name e.g CapturePCM*/
|
||||
#define PA_ALSA_PROP_UCM_SOURCE "alsa.ucm.source"
|
||||
|
||||
/** For devices: Playback roles */
|
||||
#define PA_ALSA_PROP_UCM_PLAYBACK_ROLES "alsa.ucm.playback.roles"
|
||||
|
||||
/** For devices: Playback control device name */
|
||||
#define PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE "alsa.ucm.playback.ctldev"
|
||||
|
||||
/** For devices: Playback control volume ID string. e.g PlaybackVolume */
|
||||
#define PA_ALSA_PROP_UCM_PLAYBACK_VOLUME "alsa.ucm.playback.volume"
|
||||
|
||||
/** For devices: Playback switch e.g PlaybackSwitch */
|
||||
#define PA_ALSA_PROP_UCM_PLAYBACK_SWITCH "alsa.ucm.playback.switch"
|
||||
|
||||
/** For devices: Playback mixer device name */
|
||||
#define PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE "alsa.ucm.playback.mixer.device"
|
||||
|
||||
/** For devices: Playback mixer identifier */
|
||||
#define PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM "alsa.ucm.playback.mixer.element"
|
||||
|
||||
/** For devices: Playback mixer master identifier */
|
||||
#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM "alsa.ucm.playback.master.element"
|
||||
|
||||
/** For devices: Playback mixer master type */
|
||||
#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE "alsa.ucm.playback.master.type"
|
||||
|
||||
/** For devices: Playback mixer master identifier */
|
||||
#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ID "alsa.ucm.playback.master.id"
|
||||
|
||||
/** For devices: Playback mixer master type */
|
||||
#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE "alsa.ucm.playback.master.type"
|
||||
|
||||
/** For devices: Playback priority */
|
||||
#define PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY "alsa.ucm.playback.priority"
|
||||
|
||||
/** For devices: Playback rate */
|
||||
#define PA_ALSA_PROP_UCM_PLAYBACK_RATE "alsa.ucm.playback.rate"
|
||||
|
||||
/** For devices: Playback channels */
|
||||
#define PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS "alsa.ucm.playback.channels"
|
||||
|
||||
/** For devices: Capture roles */
|
||||
#define PA_ALSA_PROP_UCM_CAPTURE_ROLES "alsa.ucm.capture.roles"
|
||||
|
||||
/** For devices: Capture control device name */
|
||||
#define PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE "alsa.ucm.capture.ctldev"
|
||||
|
||||
/** For devices: Capture controls volume ID string. e.g CaptureVolume */
|
||||
#define PA_ALSA_PROP_UCM_CAPTURE_VOLUME "alsa.ucm.capture.volume"
|
||||
|
||||
/** For devices: Capture switch e.g CaptureSwitch */
|
||||
#define PA_ALSA_PROP_UCM_CAPTURE_SWITCH "alsa.ucm.capture.switch"
|
||||
|
||||
/** For devices: Capture mixer device name */
|
||||
#define PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE "alsa.ucm.capture.mixer.device"
|
||||
|
||||
/** For devices: Capture mixer identifier */
|
||||
#define PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM "alsa.ucm.capture.mixer.element"
|
||||
|
||||
/** For devices: Capture mixer identifier */
|
||||
#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM "alsa.ucm.capture.master.element"
|
||||
|
||||
/** For devices: Capture mixer identifier */
|
||||
#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE "alsa.ucm.capture.master.type"
|
||||
|
||||
/** For devices: Capture mixer identifier */
|
||||
#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_ID "alsa.ucm.capture.master.id"
|
||||
|
||||
/** For devices: Capture mixer identifier */
|
||||
#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE "alsa.ucm.capture.master.type"
|
||||
|
||||
/** For devices: Capture priority */
|
||||
#define PA_ALSA_PROP_UCM_CAPTURE_PRIORITY "alsa.ucm.capture.priority"
|
||||
|
||||
/** For devices: Capture rate */
|
||||
#define PA_ALSA_PROP_UCM_CAPTURE_RATE "alsa.ucm.capture.rate"
|
||||
|
||||
/** For devices: Capture channels */
|
||||
#define PA_ALSA_PROP_UCM_CAPTURE_CHANNELS "alsa.ucm.capture.channels"
|
||||
|
||||
/** For devices: Quality of Service */
|
||||
#define PA_ALSA_PROP_UCM_QOS "alsa.ucm.qos"
|
||||
|
||||
/** For devices: The modifier (if any) that this device corresponds to */
|
||||
#define PA_ALSA_PROP_UCM_MODIFIER "alsa.ucm.modifier"
|
||||
|
||||
/* Corresponds to the "JackCTL" UCM value. */
|
||||
#define PA_ALSA_PROP_UCM_JACK_DEVICE "alsa.ucm.jack_device"
|
||||
|
||||
/* Corresponds to the "JackControl" UCM value. */
|
||||
#define PA_ALSA_PROP_UCM_JACK_CONTROL "alsa.ucm.jack_control"
|
||||
|
||||
/* Corresponds to the "JackHWMute" UCM value. */
|
||||
#define PA_ALSA_PROP_UCM_JACK_HW_MUTE "alsa.ucm.jack_hw_mute"
|
||||
|
||||
typedef struct pa_alsa_ucm_verb pa_alsa_ucm_verb;
|
||||
typedef struct pa_alsa_ucm_modifier pa_alsa_ucm_modifier;
|
||||
typedef struct pa_alsa_ucm_device pa_alsa_ucm_device;
|
||||
typedef struct pa_alsa_ucm_config pa_alsa_ucm_config;
|
||||
typedef struct pa_alsa_ucm_mapping_context pa_alsa_ucm_mapping_context;
|
||||
typedef struct pa_alsa_ucm_port_data pa_alsa_ucm_port_data;
|
||||
typedef struct pa_alsa_ucm_volume pa_alsa_ucm_volume;
|
||||
|
||||
int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index);
|
||||
pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map);
|
||||
int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile);
|
||||
|
||||
int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb);
|
||||
|
||||
void pa_alsa_ucm_add_ports(
|
||||
pa_hashmap **hash,
|
||||
pa_proplist *proplist,
|
||||
pa_alsa_ucm_mapping_context *context,
|
||||
bool is_sink,
|
||||
pa_card *card,
|
||||
snd_pcm_t *pcm_handle,
|
||||
bool ignore_dB);
|
||||
void pa_alsa_ucm_add_ports_combination(
|
||||
pa_hashmap *hash,
|
||||
pa_alsa_ucm_mapping_context *context,
|
||||
bool is_sink,
|
||||
pa_hashmap *ports,
|
||||
pa_alsa_profile *cp,
|
||||
pa_core *core);
|
||||
int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink);
|
||||
|
||||
void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm);
|
||||
void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context);
|
||||
|
||||
void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir);
|
||||
void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir);
|
||||
|
||||
/* UCM - Use Case Manager is available on some audio cards */
|
||||
|
||||
struct pa_alsa_ucm_device {
|
||||
PA_LLIST_FIELDS(pa_alsa_ucm_device);
|
||||
|
||||
pa_proplist *proplist;
|
||||
|
||||
pa_device_port_type_t type;
|
||||
|
||||
unsigned playback_priority;
|
||||
unsigned capture_priority;
|
||||
|
||||
unsigned playback_rate;
|
||||
unsigned capture_rate;
|
||||
|
||||
unsigned playback_channels;
|
||||
unsigned capture_channels;
|
||||
|
||||
/* These may be different per verb, so we store this as a hashmap of verb -> volume_control. We might eventually want to
|
||||
* make this a hashmap of verb -> per-verb-device-properties-struct. */
|
||||
pa_hashmap *playback_volumes;
|
||||
pa_hashmap *capture_volumes;
|
||||
|
||||
pa_alsa_mapping *playback_mapping;
|
||||
pa_alsa_mapping *capture_mapping;
|
||||
|
||||
pa_idxset *conflicting_devices;
|
||||
pa_idxset *supported_devices;
|
||||
|
||||
/* One device may be part of multiple ports, since each device has
|
||||
* a dedicated port, and in addition to that we sometimes generate ports
|
||||
* that represent combinations of devices. */
|
||||
pa_dynarray *ucm_ports; /* struct ucm_port */
|
||||
|
||||
pa_alsa_jack *jack;
|
||||
pa_dynarray *hw_mute_jacks; /* pa_alsa_jack */
|
||||
pa_available_t available;
|
||||
|
||||
char *eld_mixer_device_name;
|
||||
int eld_device;
|
||||
};
|
||||
|
||||
void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device);
|
||||
|
||||
struct pa_alsa_ucm_modifier {
|
||||
PA_LLIST_FIELDS(pa_alsa_ucm_modifier);
|
||||
|
||||
pa_proplist *proplist;
|
||||
|
||||
int n_confdev;
|
||||
int n_suppdev;
|
||||
|
||||
const char **conflicting_devices;
|
||||
const char **supported_devices;
|
||||
|
||||
pa_direction_t action_direction;
|
||||
|
||||
char *media_role;
|
||||
|
||||
/* Non-NULL if the modifier has its own PlaybackPCM/CapturePCM */
|
||||
pa_alsa_mapping *playback_mapping;
|
||||
pa_alsa_mapping *capture_mapping;
|
||||
|
||||
/* Count how many role matched streams are running */
|
||||
int enabled_counter;
|
||||
};
|
||||
|
||||
struct pa_alsa_ucm_verb {
|
||||
PA_LLIST_FIELDS(pa_alsa_ucm_verb);
|
||||
|
||||
pa_proplist *proplist;
|
||||
unsigned priority;
|
||||
|
||||
PA_LLIST_HEAD(pa_alsa_ucm_device, devices);
|
||||
PA_LLIST_HEAD(pa_alsa_ucm_modifier, modifiers);
|
||||
};
|
||||
|
||||
struct pa_alsa_ucm_config {
|
||||
pa_sample_spec default_sample_spec;
|
||||
pa_channel_map default_channel_map;
|
||||
unsigned default_fragment_size_msec;
|
||||
unsigned default_n_fragments;
|
||||
|
||||
snd_use_case_mgr_t *ucm_mgr;
|
||||
pa_alsa_ucm_verb *active_verb;
|
||||
|
||||
pa_hashmap *mixers;
|
||||
PA_LLIST_HEAD(pa_alsa_ucm_verb, verbs);
|
||||
PA_LLIST_HEAD(pa_alsa_jack, jacks);
|
||||
};
|
||||
|
||||
struct pa_alsa_ucm_mapping_context {
|
||||
pa_alsa_ucm_config *ucm;
|
||||
pa_direction_t direction;
|
||||
|
||||
pa_idxset *ucm_devices;
|
||||
pa_idxset *ucm_modifiers;
|
||||
};
|
||||
|
||||
struct pa_alsa_ucm_port_data {
|
||||
pa_alsa_ucm_config *ucm;
|
||||
pa_device_port *core_port;
|
||||
|
||||
/* A single port will be associated with multiple devices if it represents
|
||||
* a combination of devices. */
|
||||
pa_dynarray *devices; /* pa_alsa_ucm_device */
|
||||
|
||||
/* profile name -> pa_alsa_path for volume control */
|
||||
pa_hashmap *paths;
|
||||
/* Current path, set when activating profile */
|
||||
pa_alsa_path *path;
|
||||
|
||||
/* ELD info */
|
||||
char *eld_mixer_device_name;
|
||||
int eld_device; /* PCM device number */
|
||||
};
|
||||
|
||||
struct pa_alsa_ucm_volume {
|
||||
char *mixer_elem; /* mixer element identifier */
|
||||
char *master_elem; /* master mixer element identifier */
|
||||
char *master_type;
|
||||
};
|
||||
|
||||
#endif
|
||||
1564
spa/plugins/alsa/acp/alsa-util.c
Normal file
1564
spa/plugins/alsa/acp/alsa-util.c
Normal file
File diff suppressed because it is too large
Load diff
156
spa/plugins/alsa/acp/alsa-util.h
Normal file
156
spa/plugins/alsa/acp/alsa-util.h
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
#ifndef fooalsautilhfoo
|
||||
#define fooalsautilhfoo
|
||||
|
||||
/***
|
||||
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 <stdbool.h>
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
#include "alsa-mixer.h"
|
||||
|
||||
int pa_alsa_set_hw_params(
|
||||
snd_pcm_t *pcm_handle,
|
||||
pa_sample_spec *ss, /* modified at return */
|
||||
snd_pcm_uframes_t *period_size, /* modified at return */
|
||||
snd_pcm_uframes_t *buffer_size, /* modified at return */
|
||||
snd_pcm_uframes_t tsched_size,
|
||||
bool *use_mmap, /* modified at return */
|
||||
bool *use_tsched, /* modified at return */
|
||||
bool require_exact_channel_number);
|
||||
|
||||
int pa_alsa_set_sw_params(
|
||||
snd_pcm_t *pcm,
|
||||
snd_pcm_uframes_t avail_min,
|
||||
bool period_event);
|
||||
|
||||
#if 0
|
||||
/* Picks a working mapping from the profile set based on the specified ss/map */
|
||||
snd_pcm_t *pa_alsa_open_by_device_id_auto(
|
||||
const char *dev_id,
|
||||
char **dev, /* modified at return */
|
||||
pa_sample_spec *ss, /* modified at return */
|
||||
pa_channel_map* map, /* modified at return */
|
||||
int mode,
|
||||
snd_pcm_uframes_t *period_size, /* modified at return */
|
||||
snd_pcm_uframes_t *buffer_size, /* modified at return */
|
||||
snd_pcm_uframes_t tsched_size,
|
||||
bool *use_mmap, /* modified at return */
|
||||
bool *use_tsched, /* modified at return */
|
||||
pa_alsa_profile_set *ps,
|
||||
pa_alsa_mapping **mapping); /* modified at return */
|
||||
#endif
|
||||
|
||||
/* Uses the specified mapping */
|
||||
snd_pcm_t *pa_alsa_open_by_device_id_mapping(
|
||||
const char *dev_id,
|
||||
char **dev, /* modified at return */
|
||||
pa_sample_spec *ss, /* modified at return */
|
||||
pa_channel_map* map, /* modified at return */
|
||||
int mode,
|
||||
snd_pcm_uframes_t *period_size, /* modified at return */
|
||||
snd_pcm_uframes_t *buffer_size, /* modified at return */
|
||||
snd_pcm_uframes_t tsched_size,
|
||||
bool *use_mmap, /* modified at return */
|
||||
bool *use_tsched, /* modified at return */
|
||||
pa_alsa_mapping *mapping);
|
||||
|
||||
/* Opens the explicit ALSA device */
|
||||
snd_pcm_t *pa_alsa_open_by_device_string(
|
||||
const char *dir,
|
||||
char **dev, /* modified at return */
|
||||
pa_sample_spec *ss, /* modified at return */
|
||||
pa_channel_map* map, /* modified at return */
|
||||
int mode,
|
||||
snd_pcm_uframes_t *period_size, /* modified at return */
|
||||
snd_pcm_uframes_t *buffer_size, /* modified at return */
|
||||
snd_pcm_uframes_t tsched_size,
|
||||
bool *use_mmap, /* modified at return */
|
||||
bool *use_tsched, /* modified at return */
|
||||
bool require_exact_channel_number);
|
||||
|
||||
/* Opens the explicit ALSA device with a fallback list */
|
||||
snd_pcm_t *pa_alsa_open_by_template(
|
||||
char **template,
|
||||
const char *dev_id,
|
||||
char **dev, /* modified at return */
|
||||
pa_sample_spec *ss, /* modified at return */
|
||||
pa_channel_map* map, /* modified at return */
|
||||
int mode,
|
||||
snd_pcm_uframes_t *period_size, /* modified at return */
|
||||
snd_pcm_uframes_t *buffer_size, /* modified at return */
|
||||
snd_pcm_uframes_t tsched_size,
|
||||
bool *use_mmap, /* modified at return */
|
||||
bool *use_tsched, /* modified at return */
|
||||
bool require_exact_channel_number);
|
||||
|
||||
#if 0
|
||||
void pa_alsa_dump(pa_log_level_t level, snd_pcm_t *pcm);
|
||||
void pa_alsa_dump_status(snd_pcm_t *pcm);
|
||||
#endif
|
||||
|
||||
void pa_alsa_refcnt_inc(void);
|
||||
void pa_alsa_refcnt_dec(void);
|
||||
|
||||
void pa_alsa_init_proplist_pcm_info(pa_core *c, pa_proplist *p, snd_pcm_info_t *pcm_info);
|
||||
void pa_alsa_init_proplist_card(pa_core *c, pa_proplist *p, int card);
|
||||
void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm);
|
||||
#if 0
|
||||
void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name);
|
||||
#endif
|
||||
bool pa_alsa_init_description(pa_proplist *p, pa_card *card);
|
||||
|
||||
char *pa_alsa_get_driver_name(int card);
|
||||
char *pa_alsa_get_driver_name_by_pcm(snd_pcm_t *pcm);
|
||||
|
||||
char *pa_alsa_get_reserve_name(const char *device);
|
||||
|
||||
unsigned int *pa_alsa_get_supported_rates(snd_pcm_t *pcm, unsigned int fallback_rate);
|
||||
pa_sample_format_t *pa_alsa_get_supported_formats(snd_pcm_t *pcm, pa_sample_format_t fallback_format);
|
||||
|
||||
bool pa_alsa_pcm_is_hw(snd_pcm_t *pcm);
|
||||
bool pa_alsa_pcm_is_modem(snd_pcm_t *pcm);
|
||||
|
||||
const char* pa_alsa_strerror(int errnum);
|
||||
|
||||
#if 0
|
||||
bool pa_alsa_may_tsched(bool want);
|
||||
#endif
|
||||
|
||||
snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device);
|
||||
snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device);
|
||||
|
||||
snd_mixer_t *pa_alsa_open_mixer(pa_hashmap *mixers, int alsa_card_index, bool probe);
|
||||
snd_mixer_t *pa_alsa_open_mixer_by_name(pa_hashmap *mixers, const char *dev, bool probe);
|
||||
snd_mixer_t *pa_alsa_open_mixer_for_pcm(pa_hashmap *mixers, snd_pcm_t *pcm, bool probe);
|
||||
|
||||
void pa_alsa_mixer_free(pa_alsa_mixer *mixer);
|
||||
|
||||
typedef struct pa_hdmi_eld pa_hdmi_eld;
|
||||
struct pa_hdmi_eld {
|
||||
char monitor_name[17];
|
||||
};
|
||||
|
||||
int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld);
|
||||
|
||||
#endif
|
||||
150
spa/plugins/alsa/acp/array.h
Normal file
150
spa/plugins/alsa/acp/array.h
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/* ALSA Card Profile
|
||||
*
|
||||
* Copyright © 2018 Wim Taymans
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef PA_ARRAY_H
|
||||
#define PA_ARRAY_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct pa_array {
|
||||
void *data; /**< pointer to array data */
|
||||
size_t size; /**< length of array in bytes */
|
||||
size_t alloc; /**< number of allocated memory in \a data */
|
||||
size_t extend; /**< number of bytes to extend with */
|
||||
} pa_array;
|
||||
|
||||
#define PW_ARRAY_INIT(extend) (struct pa_array) { NULL, 0, 0, extend }
|
||||
|
||||
#define pa_array_get_len_s(a,s) ((a)->size / (s))
|
||||
#define pa_array_get_unchecked_s(a,idx,s,t) (t*)((uint8_t*)(a)->data + (int)((idx)*(s)))
|
||||
#define pa_array_check_index_s(a,idx,s) ((idx) < pa_array_get_len_s(a,s))
|
||||
|
||||
#define pa_array_get_len(a,t) pa_array_get_len_s(a,sizeof(t))
|
||||
#define pa_array_get_unchecked(a,idx,t) pa_array_get_unchecked_s(a,idx,sizeof(t),t)
|
||||
#define pa_array_check_index(a,idx,t) pa_array_check_index_s(a,idx,sizeof(t))
|
||||
|
||||
#define pa_array_first(a) ((a)->data)
|
||||
#define pa_array_end(a) (void*)((uint8_t*)(a)->data + (int)(a)->size)
|
||||
#define pa_array_check(a,p) ((void*)((uint8_t*)p + (int)sizeof(*p)) <= pa_array_end(a))
|
||||
|
||||
#define pa_array_for_each(pos, array) \
|
||||
for (pos = (__typeof__(pos)) pa_array_first(array); \
|
||||
pa_array_check(array, pos); \
|
||||
(pos)++)
|
||||
|
||||
#define pa_array_consume(pos, array) \
|
||||
while (pos = (__typeof__(pos)) pa_array_first(array) && \
|
||||
pa_array_check(array, pos)
|
||||
|
||||
#define pa_array_remove(a,p) \
|
||||
({ \
|
||||
(a)->size -= sizeof(*(p)); \
|
||||
memmove(p, ((uint8_t*)(p) + (int)sizeof(*(p))), \
|
||||
(uint8_t*)pa_array_end(a) - (uint8_t*)(p)); \
|
||||
})
|
||||
|
||||
static inline void pa_array_init(pa_array *arr, size_t extend)
|
||||
{
|
||||
arr->data = NULL;
|
||||
arr->size = arr->alloc = 0;
|
||||
arr->extend = extend;
|
||||
}
|
||||
|
||||
static inline void pa_array_clear(pa_array *arr)
|
||||
{
|
||||
free(arr->data);
|
||||
}
|
||||
|
||||
static inline void pa_array_reset(pa_array *arr)
|
||||
{
|
||||
arr->size = 0;
|
||||
}
|
||||
|
||||
static inline int pa_array_ensure_size(pa_array *arr, size_t size)
|
||||
{
|
||||
size_t alloc, need;
|
||||
|
||||
alloc = arr->alloc;
|
||||
need = arr->size + size;
|
||||
|
||||
if (alloc < need) {
|
||||
void *data;
|
||||
alloc = alloc > arr->extend ? alloc : arr->extend;
|
||||
while (alloc < need)
|
||||
alloc *= 2;
|
||||
if ((data = realloc(arr->data, alloc)) == NULL)
|
||||
return -errno;
|
||||
arr->data = data;
|
||||
arr->alloc = alloc;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void *pa_array_add(pa_array *arr, size_t size)
|
||||
{
|
||||
void *p;
|
||||
|
||||
if (pa_array_ensure_size(arr, size) < 0)
|
||||
return NULL;
|
||||
|
||||
p = (void*)((uint8_t*)arr->data + (int)arr->size);
|
||||
arr->size += size;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static inline void *pa_array_add_fixed(pa_array *arr, size_t size)
|
||||
{
|
||||
void *p;
|
||||
if (arr->alloc < arr->size + size) {
|
||||
errno = ENOSPC;
|
||||
return NULL;
|
||||
}
|
||||
p = ((uint8_t*)arr->data + (int)arr->size);
|
||||
arr->size += size;
|
||||
return p;
|
||||
}
|
||||
|
||||
#define pa_array_add_ptr(a,p) \
|
||||
*((void**) pa_array_add(a, sizeof(void*))) = (p)
|
||||
|
||||
static inline int pa_array_add_data(pa_array *arr, const void *data, size_t size)
|
||||
{
|
||||
void *d;
|
||||
if ((d = pa_array_add(arr, size)) == NULL)
|
||||
return -1;
|
||||
memcpy(d, data, size);
|
||||
return size;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* PA_ARRAY_H */
|
||||
70
spa/plugins/alsa/acp/card.h
Normal file
70
spa/plugins/alsa/acp/card.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/***
|
||||
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/>.
|
||||
***/
|
||||
|
||||
|
||||
#ifndef PULSE_CARD_H
|
||||
#define PULSE_CARD_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
typedef struct pa_card pa_card;
|
||||
|
||||
struct pa_card {
|
||||
struct acp_card card;
|
||||
|
||||
pa_core *core;
|
||||
|
||||
char *name;
|
||||
char *driver;
|
||||
|
||||
pa_proplist *proplist;
|
||||
|
||||
bool use_ucm;
|
||||
|
||||
pa_alsa_ucm_config ucm;
|
||||
pa_alsa_profile_set *profile_set;
|
||||
|
||||
pa_hashmap *ports;
|
||||
pa_hashmap *profiles;
|
||||
pa_hashmap *jacks;
|
||||
|
||||
struct {
|
||||
pa_dynarray ports;
|
||||
pa_dynarray profiles;
|
||||
pa_dynarray devices;
|
||||
} out;
|
||||
|
||||
const struct acp_card_events *events;
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
bool pa_alsa_device_init_description(pa_proplist *p, pa_card *card);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PULSE_CARD_H */
|
||||
474
spa/plugins/alsa/acp/channelmap.h
Normal file
474
spa/plugins/alsa/acp/channelmap.h
Normal file
|
|
@ -0,0 +1,474 @@
|
|||
/***
|
||||
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/>.
|
||||
***/
|
||||
|
||||
#ifndef PULSE_CHANNELMAP_H
|
||||
#define PULSE_CHANNELMAP_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PA_CHANNELS_MAX 64
|
||||
|
||||
#define PA_CHANNEL_MAP_SNPRINT_MAX 336
|
||||
|
||||
typedef enum pa_channel_map_def {
|
||||
PA_CHANNEL_MAP_AIFF,
|
||||
PA_CHANNEL_MAP_ALSA,
|
||||
PA_CHANNEL_MAP_AUX,
|
||||
PA_CHANNEL_MAP_WAVEEX,
|
||||
PA_CHANNEL_MAP_OSS,
|
||||
PA_CHANNEL_MAP_DEF_MAX,
|
||||
PA_CHANNEL_MAP_DEFAULT = PA_CHANNEL_MAP_AIFF
|
||||
} pa_channel_map_def_t;
|
||||
|
||||
typedef enum pa_channel_position {
|
||||
PA_CHANNEL_POSITION_INVALID = -1,
|
||||
PA_CHANNEL_POSITION_MONO = 0,
|
||||
|
||||
PA_CHANNEL_POSITION_FRONT_LEFT, /**< Apple, Dolby call this 'Left' */
|
||||
PA_CHANNEL_POSITION_FRONT_RIGHT, /**< Apple, Dolby call this 'Right' */
|
||||
PA_CHANNEL_POSITION_FRONT_CENTER, /**< Apple, Dolby call this 'Center' */
|
||||
|
||||
/** \cond fulldocs */
|
||||
PA_CHANNEL_POSITION_LEFT = PA_CHANNEL_POSITION_FRONT_LEFT,
|
||||
PA_CHANNEL_POSITION_RIGHT = PA_CHANNEL_POSITION_FRONT_RIGHT,
|
||||
PA_CHANNEL_POSITION_CENTER = PA_CHANNEL_POSITION_FRONT_CENTER,
|
||||
/** \endcond */
|
||||
|
||||
PA_CHANNEL_POSITION_REAR_CENTER, /**< Microsoft calls this 'Back Center', Apple calls this 'Center Surround', Dolby calls this 'Surround Rear Center' */
|
||||
PA_CHANNEL_POSITION_REAR_LEFT, /**< Microsoft calls this 'Back Left', Apple calls this 'Left Surround' (!), Dolby calls this 'Surround Rear Left' */
|
||||
PA_CHANNEL_POSITION_REAR_RIGHT, /**< Microsoft calls this 'Back Right', Apple calls this 'Right Surround' (!), Dolby calls this 'Surround Rear Right' */
|
||||
|
||||
PA_CHANNEL_POSITION_LFE, /**< Microsoft calls this 'Low Frequency', Apple calls this 'LFEScreen' */
|
||||
/** \cond fulldocs */
|
||||
PA_CHANNEL_POSITION_SUBWOOFER = PA_CHANNEL_POSITION_LFE,
|
||||
/** \endcond */
|
||||
|
||||
PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, /**< Apple, Dolby call this 'Left Center' */
|
||||
PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, /**< Apple, Dolby call this 'Right Center */
|
||||
|
||||
PA_CHANNEL_POSITION_SIDE_LEFT, /**< Apple calls this 'Left Surround Direct', Dolby calls this 'Surround Left' (!) */
|
||||
PA_CHANNEL_POSITION_SIDE_RIGHT, /**< Apple calls this 'Right Surround Direct', Dolby calls this 'Surround Right' (!) */
|
||||
PA_CHANNEL_POSITION_AUX0,
|
||||
PA_CHANNEL_POSITION_AUX1,
|
||||
PA_CHANNEL_POSITION_AUX2,
|
||||
PA_CHANNEL_POSITION_AUX3,
|
||||
PA_CHANNEL_POSITION_AUX4,
|
||||
PA_CHANNEL_POSITION_AUX5,
|
||||
PA_CHANNEL_POSITION_AUX6,
|
||||
PA_CHANNEL_POSITION_AUX7,
|
||||
PA_CHANNEL_POSITION_AUX8,
|
||||
PA_CHANNEL_POSITION_AUX9,
|
||||
PA_CHANNEL_POSITION_AUX10,
|
||||
PA_CHANNEL_POSITION_AUX11,
|
||||
PA_CHANNEL_POSITION_AUX12,
|
||||
PA_CHANNEL_POSITION_AUX13,
|
||||
PA_CHANNEL_POSITION_AUX14,
|
||||
PA_CHANNEL_POSITION_AUX15,
|
||||
PA_CHANNEL_POSITION_AUX16,
|
||||
PA_CHANNEL_POSITION_AUX17,
|
||||
PA_CHANNEL_POSITION_AUX18,
|
||||
PA_CHANNEL_POSITION_AUX19,
|
||||
PA_CHANNEL_POSITION_AUX20,
|
||||
PA_CHANNEL_POSITION_AUX21,
|
||||
PA_CHANNEL_POSITION_AUX22,
|
||||
PA_CHANNEL_POSITION_AUX23,
|
||||
PA_CHANNEL_POSITION_AUX24,
|
||||
PA_CHANNEL_POSITION_AUX25,
|
||||
PA_CHANNEL_POSITION_AUX26,
|
||||
PA_CHANNEL_POSITION_AUX27,
|
||||
PA_CHANNEL_POSITION_AUX28,
|
||||
PA_CHANNEL_POSITION_AUX29,
|
||||
PA_CHANNEL_POSITION_AUX30,
|
||||
PA_CHANNEL_POSITION_AUX31,
|
||||
|
||||
PA_CHANNEL_POSITION_TOP_CENTER, /**< Apple calls this 'Top Center Surround' */
|
||||
|
||||
PA_CHANNEL_POSITION_TOP_FRONT_LEFT, /**< Apple calls this 'Vertical Height Left' */
|
||||
PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, /**< Apple calls this 'Vertical Height Right' */
|
||||
PA_CHANNEL_POSITION_TOP_FRONT_CENTER, /**< Apple calls this 'Vertical Height Center' */
|
||||
|
||||
PA_CHANNEL_POSITION_TOP_REAR_LEFT, /**< Microsoft and Apple call this 'Top Back Left' */
|
||||
PA_CHANNEL_POSITION_TOP_REAR_RIGHT, /**< Microsoft and Apple call this 'Top Back Right' */
|
||||
PA_CHANNEL_POSITION_TOP_REAR_CENTER, /**< Microsoft and Apple call this 'Top Back Center' */
|
||||
|
||||
PA_CHANNEL_POSITION_MAX
|
||||
} pa_channel_position_t;
|
||||
|
||||
typedef struct pa_channel_map {
|
||||
uint8_t channels;
|
||||
pa_channel_position_t map[PA_CHANNELS_MAX];
|
||||
} pa_channel_map;
|
||||
|
||||
static inline int pa_channels_valid(uint8_t channels)
|
||||
{
|
||||
return channels > 0 && channels <= PA_CHANNELS_MAX;
|
||||
}
|
||||
|
||||
static inline int pa_channel_map_valid(const pa_channel_map *map)
|
||||
{
|
||||
unsigned c;
|
||||
if (!pa_channels_valid(map->channels))
|
||||
return 0;
|
||||
for (c = 0; c < map->channels; c++)
|
||||
if (map->map[c] < 0 || map->map[c] >= PA_CHANNEL_POSITION_MAX)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
static inline pa_channel_map* pa_channel_map_init(pa_channel_map *m)
|
||||
{
|
||||
unsigned c;
|
||||
m->channels = 0;
|
||||
for (c = 0; c < PA_CHANNELS_MAX; c++)
|
||||
m->map[c] = PA_CHANNEL_POSITION_INVALID;
|
||||
return m;
|
||||
}
|
||||
|
||||
static inline pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def)
|
||||
{
|
||||
pa_assert(m);
|
||||
pa_assert(pa_channels_valid(channels));
|
||||
pa_assert(def < PA_CHANNEL_MAP_DEF_MAX);
|
||||
|
||||
pa_channel_map_init(m);
|
||||
|
||||
m->channels = (uint8_t) channels;
|
||||
|
||||
switch (def) {
|
||||
case PA_CHANNEL_MAP_ALSA:
|
||||
switch (channels) {
|
||||
case 1:
|
||||
m->map[0] = PA_CHANNEL_POSITION_MONO;
|
||||
return m;
|
||||
case 8:
|
||||
m->map[6] = PA_CHANNEL_POSITION_SIDE_LEFT;
|
||||
m->map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT;
|
||||
/* Fall through */
|
||||
case 6:
|
||||
m->map[5] = PA_CHANNEL_POSITION_LFE;
|
||||
/* Fall through */
|
||||
case 5:
|
||||
m->map[4] = PA_CHANNEL_POSITION_FRONT_CENTER;
|
||||
/* Fall through */
|
||||
case 4:
|
||||
m->map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
|
||||
m->map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
|
||||
/* Fall through */
|
||||
case 2:
|
||||
m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
|
||||
m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
|
||||
return m;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline pa_channel_map* pa_channel_map_init_extend(pa_channel_map *m,
|
||||
unsigned channels, pa_channel_map_def_t def)
|
||||
{
|
||||
unsigned i, c;
|
||||
pa_channel_map_init(m);
|
||||
for (c = channels; c > 0; c--) {
|
||||
if (pa_channel_map_init_auto(m, c, def) == NULL)
|
||||
continue;
|
||||
for (i = 0; c < channels; c++, i++)
|
||||
m->map[c] = PA_CHANNEL_POSITION_AUX0 + i;
|
||||
m->channels = (uint8_t) channels;
|
||||
return m;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef uint64_t pa_channel_position_mask_t;
|
||||
|
||||
#define PA_CHANNEL_POSITION_MASK(f) ((pa_channel_position_mask_t) (1ULL << (f)))
|
||||
|
||||
#define PA_CHANNEL_POSITION_MASK_LEFT \
|
||||
(PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_LEFT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_LEFT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT)) \
|
||||
|
||||
#define PA_CHANNEL_POSITION_MASK_RIGHT \
|
||||
(PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_RIGHT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_RIGHT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT))
|
||||
|
||||
#define PA_CHANNEL_POSITION_MASK_CENTER \
|
||||
(PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_CENTER) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_CENTER) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER))
|
||||
|
||||
#define PA_CHANNEL_POSITION_MASK_FRONT \
|
||||
(PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_CENTER) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER))
|
||||
|
||||
#define PA_CHANNEL_POSITION_MASK_REAR \
|
||||
(PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_LEFT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_RIGHT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_CENTER) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER))
|
||||
|
||||
#define PA_CHANNEL_POSITION_MASK_LFE \
|
||||
PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_LFE)
|
||||
|
||||
#define PA_CHANNEL_POSITION_MASK_HFE \
|
||||
(PA_CHANNEL_POSITION_MASK_REAR | PA_CHANNEL_POSITION_MASK_FRONT \
|
||||
| PA_CHANNEL_POSITION_MASK_LEFT | PA_CHANNEL_POSITION_MASK_RIGHT \
|
||||
| PA_CHANNEL_POSITION_MASK_CENTER)
|
||||
|
||||
#define PA_CHANNEL_POSITION_MASK_SIDE_OR_TOP_CENTER \
|
||||
(PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_LEFT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_RIGHT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER))
|
||||
|
||||
#define PA_CHANNEL_POSITION_MASK_TOP \
|
||||
(PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT) \
|
||||
| PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER))
|
||||
|
||||
#define PA_CHANNEL_POSITION_MASK_ALL \
|
||||
((pa_channel_position_mask_t) (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_MAX)-1))
|
||||
|
||||
static const char *const pa_position_table[PA_CHANNEL_POSITION_MAX] = {
|
||||
[PA_CHANNEL_POSITION_MONO] = "mono",
|
||||
[PA_CHANNEL_POSITION_FRONT_CENTER] = "front-center",
|
||||
[PA_CHANNEL_POSITION_FRONT_LEFT] = "front-left",
|
||||
[PA_CHANNEL_POSITION_FRONT_RIGHT] = "front-right",
|
||||
[PA_CHANNEL_POSITION_REAR_CENTER] = "rear-center",
|
||||
[PA_CHANNEL_POSITION_REAR_LEFT] = "rear-left",
|
||||
[PA_CHANNEL_POSITION_REAR_RIGHT] = "rear-right",
|
||||
[PA_CHANNEL_POSITION_LFE] = "lfe",
|
||||
[PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = "front-left-of-center",
|
||||
[PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = "front-right-of-center",
|
||||
[PA_CHANNEL_POSITION_SIDE_LEFT] = "side-left",
|
||||
[PA_CHANNEL_POSITION_SIDE_RIGHT] = "side-right",
|
||||
[PA_CHANNEL_POSITION_AUX0] = "aux0",
|
||||
[PA_CHANNEL_POSITION_AUX1] = "aux1",
|
||||
[PA_CHANNEL_POSITION_AUX2] = "aux2",
|
||||
[PA_CHANNEL_POSITION_AUX3] = "aux3",
|
||||
[PA_CHANNEL_POSITION_AUX4] = "aux4",
|
||||
[PA_CHANNEL_POSITION_AUX5] = "aux5",
|
||||
[PA_CHANNEL_POSITION_AUX6] = "aux6",
|
||||
[PA_CHANNEL_POSITION_AUX7] = "aux7",
|
||||
[PA_CHANNEL_POSITION_AUX8] = "aux8",
|
||||
[PA_CHANNEL_POSITION_AUX9] = "aux9",
|
||||
[PA_CHANNEL_POSITION_AUX10] = "aux10",
|
||||
[PA_CHANNEL_POSITION_AUX11] = "aux11",
|
||||
[PA_CHANNEL_POSITION_AUX12] = "aux12",
|
||||
[PA_CHANNEL_POSITION_AUX13] = "aux13",
|
||||
[PA_CHANNEL_POSITION_AUX14] = "aux14",
|
||||
[PA_CHANNEL_POSITION_AUX15] = "aux15",
|
||||
[PA_CHANNEL_POSITION_AUX16] = "aux16",
|
||||
[PA_CHANNEL_POSITION_AUX17] = "aux17",
|
||||
[PA_CHANNEL_POSITION_AUX18] = "aux18",
|
||||
[PA_CHANNEL_POSITION_AUX19] = "aux19",
|
||||
[PA_CHANNEL_POSITION_AUX20] = "aux20",
|
||||
[PA_CHANNEL_POSITION_AUX21] = "aux21",
|
||||
[PA_CHANNEL_POSITION_AUX22] = "aux22",
|
||||
[PA_CHANNEL_POSITION_AUX23] = "aux23",
|
||||
[PA_CHANNEL_POSITION_AUX24] = "aux24",
|
||||
[PA_CHANNEL_POSITION_AUX25] = "aux25",
|
||||
[PA_CHANNEL_POSITION_AUX26] = "aux26",
|
||||
[PA_CHANNEL_POSITION_AUX27] = "aux27",
|
||||
[PA_CHANNEL_POSITION_AUX28] = "aux28",
|
||||
[PA_CHANNEL_POSITION_AUX29] = "aux29",
|
||||
[PA_CHANNEL_POSITION_AUX30] = "aux30",
|
||||
[PA_CHANNEL_POSITION_AUX31] = "aux31",
|
||||
[PA_CHANNEL_POSITION_TOP_CENTER] = "top-center",
|
||||
[PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = "top-front-center",
|
||||
[PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = "top-front-left",
|
||||
[PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = "top-front-right",
|
||||
[PA_CHANNEL_POSITION_TOP_REAR_CENTER] = "top-rear-center",
|
||||
[PA_CHANNEL_POSITION_TOP_REAR_LEFT] = "top-rear-left",
|
||||
[PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = "top-rear-right"
|
||||
};
|
||||
|
||||
static inline pa_channel_position_t pa_channel_position_from_string(const char *p)
|
||||
{
|
||||
pa_channel_position_t i;
|
||||
/* Some special aliases */
|
||||
if (pa_streq(p, "left"))
|
||||
return PA_CHANNEL_POSITION_LEFT;
|
||||
else if (pa_streq(p, "right"))
|
||||
return PA_CHANNEL_POSITION_RIGHT;
|
||||
else if (pa_streq(p, "center"))
|
||||
return PA_CHANNEL_POSITION_CENTER;
|
||||
else if (pa_streq(p, "subwoofer"))
|
||||
return PA_CHANNEL_POSITION_SUBWOOFER;
|
||||
for (i = 0; i < PA_CHANNEL_POSITION_MAX; i++)
|
||||
if (pa_streq(p, pa_position_table[i]))
|
||||
return i;
|
||||
return PA_CHANNEL_POSITION_INVALID;
|
||||
}
|
||||
|
||||
static inline pa_channel_map *pa_channel_map_parse(pa_channel_map *rmap, const char *s)
|
||||
{
|
||||
const char *state;
|
||||
pa_channel_map map;
|
||||
char *p;
|
||||
pa_channel_map_init(&map);
|
||||
if (pa_streq(s, "stereo")) {
|
||||
map.channels = 2;
|
||||
map.map[0] = PA_CHANNEL_POSITION_LEFT;
|
||||
map.map[1] = PA_CHANNEL_POSITION_RIGHT;
|
||||
goto finish;
|
||||
} else if (pa_streq(s, "surround-21")) {
|
||||
map.channels = 3;
|
||||
map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
|
||||
map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
|
||||
map.map[2] = PA_CHANNEL_POSITION_LFE;
|
||||
goto finish;
|
||||
} else if (pa_streq(s, "surround-40")) {
|
||||
map.channels = 4;
|
||||
map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
|
||||
map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
|
||||
map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
|
||||
map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
|
||||
goto finish;
|
||||
} else if (pa_streq(s, "surround-41")) {
|
||||
map.channels = 5;
|
||||
map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
|
||||
map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
|
||||
map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
|
||||
map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
|
||||
map.map[4] = PA_CHANNEL_POSITION_LFE;
|
||||
goto finish;
|
||||
} else if (pa_streq(s, "surround-50")) {
|
||||
map.channels = 5;
|
||||
map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
|
||||
map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
|
||||
map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
|
||||
map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
|
||||
map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER;
|
||||
goto finish;
|
||||
} else if (pa_streq(s, "surround-51")) {
|
||||
map.channels = 6;
|
||||
map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
|
||||
map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
|
||||
map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
|
||||
map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
|
||||
map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER;
|
||||
map.map[5] = PA_CHANNEL_POSITION_LFE;
|
||||
goto finish;
|
||||
} else if (pa_streq(s, "surround-71")) {
|
||||
map.channels = 8;
|
||||
map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
|
||||
map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
|
||||
map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
|
||||
map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
|
||||
map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER;
|
||||
map.map[5] = PA_CHANNEL_POSITION_LFE;
|
||||
map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT;
|
||||
map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT;
|
||||
goto finish;
|
||||
}
|
||||
state = NULL;
|
||||
map.channels = 0;
|
||||
while ((p = pa_split(s, ",", &state))) {
|
||||
pa_channel_position_t f;
|
||||
|
||||
if (map.channels >= PA_CHANNELS_MAX) {
|
||||
pa_xfree(p);
|
||||
return NULL;
|
||||
}
|
||||
if ((f = pa_channel_position_from_string(p)) == PA_CHANNEL_POSITION_INVALID) {
|
||||
pa_xfree(p);
|
||||
return NULL;
|
||||
}
|
||||
map.map[map.channels++] = f;
|
||||
pa_xfree(p);
|
||||
}
|
||||
finish:
|
||||
if (!pa_channel_map_valid(&map))
|
||||
return NULL;
|
||||
*rmap = map;
|
||||
return rmap;
|
||||
}
|
||||
|
||||
static inline const char* pa_channel_position_to_string(pa_channel_position_t pos) {
|
||||
|
||||
if (pos < 0 || pos >= PA_CHANNEL_POSITION_MAX)
|
||||
return NULL;
|
||||
return pa_position_table[pos];
|
||||
}
|
||||
|
||||
static inline int pa_channel_map_equal(const pa_channel_map *a, const pa_channel_map *b)
|
||||
{
|
||||
unsigned c;
|
||||
if (PA_UNLIKELY(a == b))
|
||||
return 1;
|
||||
if (a->channels != b->channels)
|
||||
return 0;
|
||||
for (c = 0; c < a->channels; c++)
|
||||
if (a->map[c] != b->map[c])
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map) {
|
||||
unsigned channel;
|
||||
bool first = true;
|
||||
char *e;
|
||||
if (!pa_channel_map_valid(map)) {
|
||||
pa_snprintf(s, l, _("(invalid)"));
|
||||
return s;
|
||||
}
|
||||
*(e = s) = 0;
|
||||
for (channel = 0; channel < map->channels && l > 1; channel++) {
|
||||
l -= pa_snprintf(e, l, "%s%s",
|
||||
first ? "" : ",",
|
||||
pa_channel_position_to_string(map->map[channel]));
|
||||
e = strchr(e, 0);
|
||||
first = false;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PULSE_CHANNELMAP_H */
|
||||
159
spa/plugins/alsa/acp/compat.c
Normal file
159
spa/plugins/alsa/acp/compat.c
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2004-2009 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 "compat.h"
|
||||
#include "device-port.h"
|
||||
#include "alsa-mixer.h"
|
||||
|
||||
pa_device_port_new_data *pa_device_port_new_data_init(pa_device_port_new_data *data)
|
||||
{
|
||||
pa_assert(data);
|
||||
pa_zero(*data);
|
||||
data->type = PA_DEVICE_PORT_TYPE_UNKNOWN;
|
||||
data->available = PA_AVAILABLE_UNKNOWN;
|
||||
return data;
|
||||
}
|
||||
|
||||
void pa_device_port_new_data_set_name(pa_device_port_new_data *data, const char *name)
|
||||
{
|
||||
pa_assert(data);
|
||||
pa_xfree(data->name);
|
||||
data->name = pa_xstrdup(name);
|
||||
}
|
||||
|
||||
void pa_device_port_new_data_set_description(pa_device_port_new_data *data, const char *description)
|
||||
{
|
||||
pa_assert(data);
|
||||
pa_xfree(data->description);
|
||||
data->description = pa_xstrdup(description);
|
||||
}
|
||||
|
||||
void pa_device_port_new_data_set_available(pa_device_port_new_data *data, pa_available_t available)
|
||||
{
|
||||
pa_assert(data);
|
||||
data->available = available;
|
||||
}
|
||||
|
||||
void pa_device_port_new_data_set_available_group(pa_device_port_new_data *data, const char *group)
|
||||
{
|
||||
pa_assert(data);
|
||||
pa_xfree(data->available_group);
|
||||
data->available_group = pa_xstrdup(group);
|
||||
}
|
||||
|
||||
void pa_device_port_new_data_set_direction(pa_device_port_new_data *data, pa_direction_t direction)
|
||||
{
|
||||
pa_assert(data);
|
||||
data->direction = direction;
|
||||
}
|
||||
|
||||
void pa_device_port_new_data_set_type(pa_device_port_new_data *data, pa_device_port_type_t type)
|
||||
{
|
||||
pa_assert(data);
|
||||
data->type = type;
|
||||
}
|
||||
|
||||
void pa_device_port_new_data_done(pa_device_port_new_data *data)
|
||||
{
|
||||
pa_assert(data);
|
||||
pa_xfree(data->name);
|
||||
pa_xfree(data->description);
|
||||
pa_xfree(data->available_group);
|
||||
}
|
||||
|
||||
pa_device_port *pa_device_port_new(pa_core *c, pa_device_port_new_data *data, size_t extra)
|
||||
{
|
||||
pa_device_port *p;
|
||||
|
||||
pa_assert(data);
|
||||
pa_assert(data->name);
|
||||
pa_assert(data->description);
|
||||
pa_assert(data->direction == PA_DIRECTION_OUTPUT || data->direction == PA_DIRECTION_INPUT);
|
||||
|
||||
p = calloc(1, sizeof(pa_device_port) + extra);
|
||||
|
||||
p->port.name = data->name;
|
||||
data->name = NULL;
|
||||
p->port.description = data->description;
|
||||
data->description = NULL;
|
||||
p->port.priority = 0;
|
||||
p->port.available = (enum acp_available) data->available;
|
||||
p->port.available_group = data->available_group;
|
||||
data->available_group = NULL;
|
||||
p->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
|
||||
p->port.direction = data->direction == PA_DIRECTION_OUTPUT ?
|
||||
ACP_DIRECTION_PLAYBACK : ACP_DIRECTION_CAPTURE;
|
||||
p->port.type = (enum acp_port_type) data->type;
|
||||
|
||||
p->proplist = pa_proplist_new();
|
||||
p->user_data = (void*)((uint8_t*)p + sizeof(pa_device_port));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void pa_device_port_set_available(pa_device_port *p, pa_available_t status)
|
||||
{
|
||||
enum acp_available old = p->port.available;
|
||||
|
||||
if (old == (enum acp_available) status)
|
||||
return;
|
||||
p->port.available = (enum acp_available) status;
|
||||
|
||||
if (p->card && p->card->events && p->card->events->port_available)
|
||||
p->card->events->port_available(p->card->user_data, p->port.index,
|
||||
old, (enum acp_available) status);
|
||||
}
|
||||
|
||||
bool pa_alsa_device_init_description(pa_proplist *p, pa_card *card) {
|
||||
const char *s, *d = NULL, *k;
|
||||
pa_assert(p);
|
||||
|
||||
if (pa_proplist_contains(p, PA_PROP_DEVICE_DESCRIPTION))
|
||||
return true;
|
||||
|
||||
if (card)
|
||||
if ((s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_DESCRIPTION)))
|
||||
d = s;
|
||||
|
||||
if (!d)
|
||||
if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR)))
|
||||
if (pa_streq(s, "internal"))
|
||||
d = _("Built-in Audio");
|
||||
|
||||
if (!d)
|
||||
if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS)))
|
||||
if (pa_streq(s, "modem"))
|
||||
d = _("Modem");
|
||||
|
||||
if (!d)
|
||||
d = pa_proplist_gets(p, PA_PROP_DEVICE_PRODUCT_NAME);
|
||||
|
||||
if (!d)
|
||||
return false;
|
||||
|
||||
k = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_DESCRIPTION);
|
||||
|
||||
if (d && k)
|
||||
pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, "%s %s", d, k);
|
||||
else if (d)
|
||||
pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, d);
|
||||
|
||||
return true;
|
||||
}
|
||||
598
spa/plugins/alsa/acp/compat.h
Normal file
598
spa/plugins/alsa/acp/compat.h
Normal file
|
|
@ -0,0 +1,598 @@
|
|||
/***
|
||||
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/>.
|
||||
***/
|
||||
|
||||
|
||||
#ifndef PULSE_COMPAT_H
|
||||
#define PULSE_COMPAT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct pa_core pa_core;
|
||||
|
||||
typedef void *(*pa_copy_func_t)(const void *p);
|
||||
typedef void (*pa_free_cb_t)(void *p);
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define PA_LIKELY(x) (__builtin_expect(!!(x),1))
|
||||
#define PA_UNLIKELY(x) (__builtin_expect(!!(x),0))
|
||||
#define PA_PRINTF_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1)))
|
||||
#else
|
||||
#define PA_LIKELY(x) (x)
|
||||
#define PA_UNLIKELY(x) (x)
|
||||
#define PA_PRINTF_FUNC(fmt, arg1)
|
||||
#endif
|
||||
|
||||
#define PA_MIN(a,b) \
|
||||
({ \
|
||||
__typeof__(a) _a = (a); \
|
||||
__typeof__(b) _b = (b); \
|
||||
PA_LIKELY(_a < _b) ? _a : _b; \
|
||||
})
|
||||
#define PA_MAX(a,b) \
|
||||
({ \
|
||||
__typeof__(a) _a = (a); \
|
||||
__typeof__(b) _b = (b); \
|
||||
PA_LIKELY(_a > _b) ? _a : _b; \
|
||||
})
|
||||
#define PA_CLAMP_UNLIKELY(v,low,high) \
|
||||
({ \
|
||||
__typeof__(v) _v = (v); \
|
||||
__typeof__(low) _low = (low); \
|
||||
__typeof__(high) _high = (high); \
|
||||
PA_MIN(PA_MAX(_v, _low), _high); \
|
||||
})
|
||||
|
||||
#define PA_PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p)))
|
||||
|
||||
#include "array.h"
|
||||
#include "llist.h"
|
||||
#include "hashmap.h"
|
||||
#include "dynarray.h"
|
||||
#include "idxset.h"
|
||||
#include "proplist.h"
|
||||
|
||||
typedef enum pa_direction {
|
||||
PA_DIRECTION_OUTPUT = 0x0001U, /**< Output direction */
|
||||
PA_DIRECTION_INPUT = 0x0002U /**< Input direction */
|
||||
} pa_direction_t;
|
||||
|
||||
/* This enum replaces pa_port_available_t (defined in pulse/def.h) for
|
||||
* internal use, so make sure both enum types stay in sync. */
|
||||
typedef enum pa_available {
|
||||
PA_AVAILABLE_UNKNOWN = 0,
|
||||
PA_AVAILABLE_NO = 1,
|
||||
PA_AVAILABLE_YES = 2,
|
||||
} pa_available_t;
|
||||
|
||||
#define PA_RATE_MAX (48000U*8U)
|
||||
|
||||
typedef enum pa_sample_format {
|
||||
PA_SAMPLE_U8, /**< Unsigned 8 Bit PCM */
|
||||
PA_SAMPLE_ALAW, /**< 8 Bit a-Law */
|
||||
PA_SAMPLE_ULAW, /**< 8 Bit mu-Law */
|
||||
PA_SAMPLE_S16LE, /**< Signed 16 Bit PCM, little endian (PC) */
|
||||
PA_SAMPLE_S16BE, /**< Signed 16 Bit PCM, big endian */
|
||||
PA_SAMPLE_FLOAT32LE, /**< 32 Bit IEEE floating point, little endian (PC), range -1.0 to 1.0 */
|
||||
PA_SAMPLE_FLOAT32BE, /**< 32 Bit IEEE floating point, big endian, range -1.0 to 1.0 */
|
||||
PA_SAMPLE_S32LE, /**< Signed 32 Bit PCM, little endian (PC) */
|
||||
PA_SAMPLE_S32BE, /**< Signed 32 Bit PCM, big endian */
|
||||
PA_SAMPLE_S24LE, /**< Signed 24 Bit PCM packed, little endian (PC). \since 0.9.15 */
|
||||
PA_SAMPLE_S24BE, /**< Signed 24 Bit PCM packed, big endian. \since 0.9.15 */
|
||||
PA_SAMPLE_S24_32LE, /**< Signed 24 Bit PCM in LSB of 32 Bit words, little endian (PC). \since 0.9.15 */
|
||||
PA_SAMPLE_S24_32BE, /**< Signed 24 Bit PCM in LSB of 32 Bit words, big endian. \since 0.9.15 */
|
||||
PA_SAMPLE_MAX, /**< Upper limit of valid sample types */
|
||||
PA_SAMPLE_INVALID = -1 /**< An invalid value */
|
||||
} pa_sample_format_t;
|
||||
|
||||
static inline int pa_sample_format_valid(unsigned format)
|
||||
{
|
||||
return format < PA_SAMPLE_MAX;
|
||||
}
|
||||
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
#define PA_SAMPLE_S16NE PA_SAMPLE_S16BE
|
||||
#define PA_SAMPLE_FLOAT32NE PA_SAMPLE_FLOAT32BE
|
||||
#define PA_SAMPLE_S32NE PA_SAMPLE_S32BE
|
||||
#define PA_SAMPLE_S24NE PA_SAMPLE_S24BE
|
||||
#define PA_SAMPLE_S24_32NE PA_SAMPLE_S24_32BE
|
||||
#define PA_SAMPLE_S16RE PA_SAMPLE_S16LE
|
||||
#define PA_SAMPLE_FLOAT32RE PA_SAMPLE_FLOAT32LE
|
||||
#define PA_SAMPLE_S32RE PA_SAMPLE_S32LE
|
||||
#define PA_SAMPLE_S24RE PA_SAMPLE_S24LE
|
||||
#define PA_SAMPLE_S24_32RE PA_SAMPLE_S24_32LE
|
||||
#else
|
||||
#define PA_SAMPLE_S16NE PA_SAMPLE_S16LE
|
||||
#define PA_SAMPLE_FLOAT32NE PA_SAMPLE_FLOAT32LE
|
||||
#define PA_SAMPLE_S32NE PA_SAMPLE_S32LE
|
||||
#define PA_SAMPLE_S24NE PA_SAMPLE_S24LE
|
||||
#define PA_SAMPLE_S24_32NE PA_SAMPLE_S24_32LE
|
||||
#define PA_SAMPLE_S16RE PA_SAMPLE_S16BE
|
||||
#define PA_SAMPLE_FLOAT32RE PA_SAMPLE_FLOAT32BE
|
||||
#define PA_SAMPLE_S32RE PA_SAMPLE_S32BE
|
||||
#define PA_SAMPLE_S24RE PA_SAMPLE_S24BE
|
||||
#define PA_SAMPLE_S24_32RE PA_SAMPLE_S24_32BE
|
||||
#endif
|
||||
|
||||
static const size_t pa_sample_size_table[] = {
|
||||
[PA_SAMPLE_U8] = 1,
|
||||
[PA_SAMPLE_ULAW] = 1,
|
||||
[PA_SAMPLE_ALAW] = 1,
|
||||
[PA_SAMPLE_S16LE] = 2,
|
||||
[PA_SAMPLE_S16BE] = 2,
|
||||
[PA_SAMPLE_FLOAT32LE] = 4,
|
||||
[PA_SAMPLE_FLOAT32BE] = 4,
|
||||
[PA_SAMPLE_S32LE] = 4,
|
||||
[PA_SAMPLE_S32BE] = 4,
|
||||
[PA_SAMPLE_S24LE] = 3,
|
||||
[PA_SAMPLE_S24BE] = 3,
|
||||
[PA_SAMPLE_S24_32LE] = 4,
|
||||
[PA_SAMPLE_S24_32BE] = 4
|
||||
};
|
||||
|
||||
static inline const char *pa_sample_format_to_string(pa_sample_format_t f)
|
||||
{
|
||||
static const char* const table[]= {
|
||||
[PA_SAMPLE_U8] = "u8",
|
||||
[PA_SAMPLE_ALAW] = "aLaw",
|
||||
[PA_SAMPLE_ULAW] = "uLaw",
|
||||
[PA_SAMPLE_S16LE] = "s16le",
|
||||
[PA_SAMPLE_S16BE] = "s16be",
|
||||
[PA_SAMPLE_FLOAT32LE] = "float32le",
|
||||
[PA_SAMPLE_FLOAT32BE] = "float32be",
|
||||
[PA_SAMPLE_S32LE] = "s32le",
|
||||
[PA_SAMPLE_S32BE] = "s32be",
|
||||
[PA_SAMPLE_S24LE] = "s24le",
|
||||
[PA_SAMPLE_S24BE] = "s24be",
|
||||
[PA_SAMPLE_S24_32LE] = "s24-32le",
|
||||
[PA_SAMPLE_S24_32BE] = "s24-32be",
|
||||
};
|
||||
|
||||
if (!pa_sample_format_valid(f))
|
||||
return NULL;
|
||||
return table[f];
|
||||
}
|
||||
|
||||
typedef struct pa_sample_spec {
|
||||
pa_sample_format_t format;
|
||||
uint32_t rate;
|
||||
uint8_t channels;
|
||||
} pa_sample_spec;
|
||||
|
||||
typedef uint64_t pa_usec_t;
|
||||
#define PA_MSEC_PER_SEC ((pa_usec_t) 1000ULL)
|
||||
#define PA_USEC_PER_SEC ((pa_usec_t) 1000000ULL)
|
||||
#define PA_USEC_PER_MSEC ((pa_usec_t) 1000ULL)
|
||||
|
||||
static inline size_t pa_usec_to_bytes(pa_usec_t t, const pa_sample_spec *spec) {
|
||||
return (size_t) (((t * spec->rate) / PA_USEC_PER_SEC)) *
|
||||
(pa_sample_size_table[spec->format] * spec->channels);
|
||||
}
|
||||
|
||||
static inline int pa_sample_rate_valid(uint32_t rate) {
|
||||
return rate > 0 && rate <= PA_RATE_MAX * 101 / 100;
|
||||
}
|
||||
|
||||
static inline size_t pa_frame_size(const pa_sample_spec *spec) {
|
||||
return pa_sample_size_table[spec->format] * spec->channels;
|
||||
}
|
||||
|
||||
typedef enum pa_log_level {
|
||||
PA_LOG_ERROR = 0, /* Error messages */
|
||||
PA_LOG_WARN = 1, /* Warning messages */
|
||||
PA_LOG_NOTICE = 2, /* Notice messages */
|
||||
PA_LOG_INFO = 3, /* Info messages */
|
||||
PA_LOG_DEBUG = 4, /* Debug messages */
|
||||
PA_LOG_LEVEL_MAX
|
||||
} pa_log_level_t;
|
||||
|
||||
extern int _acp_log_level;
|
||||
extern acp_log_func _acp_log_func;
|
||||
extern void * _acp_log_data;
|
||||
|
||||
#define pa_log_level_enabled(lev) (_acp_log_level >= (int)(lev))
|
||||
|
||||
#define pa_log_levelv_meta(lev,f,l,func,fmt,ap) \
|
||||
({ \
|
||||
if (pa_log_level_enabled (lev) && _acp_log_func) \
|
||||
_acp_log_func(_acp_log_data,lev,f,l,func,fmt,ap); \
|
||||
})
|
||||
|
||||
static inline PA_PRINTF_FUNC(5, 6) void pa_log_level_meta(enum pa_log_level level,
|
||||
const char *file, int line, const char *func,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args,fmt);
|
||||
pa_log_levelv_meta(level,file,line,func,fmt,args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
#define pa_logl(lev,fmt,...) pa_log_level_meta(lev,__FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__)
|
||||
#define pa_log_error(fmt,...) pa_logl(PA_LOG_ERROR, fmt, ##__VA_ARGS__)
|
||||
#define pa_log_warn(fmt,...) pa_logl(PA_LOG_WARN, fmt, ##__VA_ARGS__)
|
||||
#define pa_log_notice(fmt,...) pa_logl(PA_LOG_NOTICE, fmt, ##__VA_ARGS__)
|
||||
#define pa_log_info(fmt,...) pa_logl(PA_LOG_INFO, fmt, ##__VA_ARGS__)
|
||||
#define pa_log_debug(fmt,...) pa_logl(PA_LOG_DEBUG, fmt, ##__VA_ARGS__)
|
||||
#define pa_log pa_log_error
|
||||
|
||||
#define pa_assert_se(expr) \
|
||||
do { \
|
||||
if (PA_UNLIKELY(!(expr))) { \
|
||||
fprintf(stderr, "'%s' failed at %s:%u %s()\n", \
|
||||
#expr , __FILE__, __LINE__, __func__); \
|
||||
abort(); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define pa_assert(expr) \
|
||||
do { \
|
||||
if (PA_UNLIKELY(!(expr))) { \
|
||||
fprintf(stderr, "'%s' failed at %s:%u %s()\n", \
|
||||
#expr , __FILE__, __LINE__, __func__); \
|
||||
abort(); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define pa_assert_not_reached() \
|
||||
do { \
|
||||
fprintf(stderr, "Code should not be reached at %s:%u %s()\n", \
|
||||
__FILE__, __LINE__, __func__); \
|
||||
abort(); \
|
||||
} while (false)
|
||||
|
||||
|
||||
#define pa_memzero(x,l) (memset((x), 0, (l)))
|
||||
#define pa_zero(x) (pa_memzero(&(x), sizeof(x)))
|
||||
|
||||
#define PA_ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0]))
|
||||
|
||||
#define pa_streq(a,b) (!strcmp((a),(b)))
|
||||
#define pa_strneq(a,b,n) (!strncmp((a),(b),(n)))
|
||||
#define pa_strnull(s) ((s) ? (s) : "null")
|
||||
#define pa_startswith(s,pfx) (strstr(s, pfx) == s)
|
||||
|
||||
#define pa_snprintf snprintf
|
||||
|
||||
#define pa_xstrdup(s) ((s) != NULL ? strdup(s) : NULL)
|
||||
#define pa_xstrndup(s,n) ((s) != NULL ? strndup(s,n) : NULL)
|
||||
#define pa_xfree free
|
||||
#define pa_xmalloc malloc
|
||||
#define pa_xnew0(t,n) calloc(n, sizeof(t))
|
||||
#define pa_xnew(t,n) pa_xnew0(t,n)
|
||||
#define pa_xrealloc realloc
|
||||
#define pa_xrenew(t,p,n) ((t*) realloc(p, (n)*sizeof(t)))
|
||||
|
||||
static inline void* pa_xmemdup(const void *p, size_t l) {
|
||||
return memcpy(malloc(l), p, l);
|
||||
|
||||
}
|
||||
#define pa_xnewdup(t,p,n) ((t*) pa_xmemdup((p), (n)*sizeof(t)))
|
||||
|
||||
static inline void pa_xfreev(void**a)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; a && a[i]; i++)
|
||||
free(a[i]);
|
||||
free(a);
|
||||
}
|
||||
static inline void pa_xstrfreev(char **a) {
|
||||
pa_xfreev((void**)a);
|
||||
}
|
||||
|
||||
|
||||
#define pa_cstrerror strerror
|
||||
|
||||
#define PA_PATH_SEP "/"
|
||||
#define PA_PATH_SEP_CHAR '/'
|
||||
|
||||
#define PA_WHITESPACE "\n\r \t"
|
||||
|
||||
static PA_PRINTF_FUNC(1,2) inline char *pa_sprintf_malloc(const char *fmt, ...)
|
||||
{
|
||||
char *res;
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if (vasprintf(&res, fmt, args) < 0)
|
||||
res = NULL;
|
||||
va_end(args);
|
||||
return res;
|
||||
}
|
||||
|
||||
#define pa_fopen_cloexec(f,m) fopen(f,m"e")
|
||||
|
||||
static inline char *pa_path_get_filename(const char *p)
|
||||
{
|
||||
char *fn;
|
||||
if (!p)
|
||||
return NULL;
|
||||
if ((fn = strrchr(p, PA_PATH_SEP_CHAR)))
|
||||
return fn+1;
|
||||
return (char*) p;
|
||||
}
|
||||
|
||||
static inline bool pa_is_path_absolute(const char *fn)
|
||||
{
|
||||
return *fn == PA_PATH_SEP_CHAR;
|
||||
}
|
||||
|
||||
static inline char* pa_maybe_prefix_path(const char *path, const char *prefix)
|
||||
{
|
||||
if (pa_is_path_absolute(path))
|
||||
return pa_xstrdup(path);
|
||||
return pa_sprintf_malloc("%s" PA_PATH_SEP "%s", prefix, path);
|
||||
}
|
||||
|
||||
static inline bool pa_endswith(const char *s, const char *sfx)
|
||||
{
|
||||
size_t l1, l2;
|
||||
l1 = strlen(s);
|
||||
l2 = strlen(sfx);
|
||||
return l1 >= l2 && pa_streq(s + l1 - l2, sfx);
|
||||
}
|
||||
|
||||
static inline char *pa_replace(const char*s, const char*a, const char *b)
|
||||
{
|
||||
struct pa_array res;
|
||||
size_t an, bn;
|
||||
|
||||
an = strlen(a);
|
||||
bn = strlen(b);
|
||||
pa_array_init(&res, an);
|
||||
|
||||
for (;;) {
|
||||
const char *p;
|
||||
|
||||
if (!(p = strstr(s, a)))
|
||||
break;
|
||||
|
||||
pa_array_add_data(&res, s, p-s);
|
||||
pa_array_add_data(&res, b, bn);
|
||||
s = p + an;
|
||||
}
|
||||
pa_array_add_data(&res, s, strlen(s) + 1);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
static inline char *pa_split(const char *c, const char *delimiter, const char**state)
|
||||
{
|
||||
const char *current = *state ? *state : c;
|
||||
size_t l;
|
||||
if (!*current)
|
||||
return NULL;
|
||||
l = strcspn(current, delimiter);
|
||||
*state = current+l;
|
||||
if (**state)
|
||||
(*state)++;
|
||||
return pa_xstrndup(current, l);
|
||||
}
|
||||
|
||||
static inline char *pa_split_spaces(const char *c, const char **state)
|
||||
{
|
||||
const char *current = *state ? *state : c;
|
||||
size_t l;
|
||||
if (!*current || *c == 0)
|
||||
return NULL;
|
||||
current += strspn(current, PA_WHITESPACE);
|
||||
l = strcspn(current, PA_WHITESPACE);
|
||||
*state = current+l;
|
||||
return pa_xstrndup(current, l);
|
||||
}
|
||||
|
||||
static inline char **pa_split_spaces_strv(const char *s)
|
||||
{
|
||||
char **t, *e;
|
||||
unsigned i = 0, n = 8;
|
||||
const char *state = NULL;
|
||||
|
||||
t = pa_xnew(char*, n);
|
||||
while ((e = pa_split_spaces(s, &state))) {
|
||||
t[i++] = e;
|
||||
if (i >= n) {
|
||||
n *= 2;
|
||||
t = pa_xrenew(char*, t, n);
|
||||
}
|
||||
}
|
||||
if (i <= 0) {
|
||||
pa_xfree(t);
|
||||
return NULL;
|
||||
}
|
||||
t[i] = NULL;
|
||||
return t;
|
||||
}
|
||||
|
||||
static inline char* pa_str_strip_suffix(const char *str, const char *suffix)
|
||||
{
|
||||
size_t str_l, suf_l, prefix;
|
||||
char *ret;
|
||||
|
||||
str_l = strlen(str);
|
||||
suf_l = strlen(suffix);
|
||||
|
||||
if (str_l < suf_l)
|
||||
return NULL;
|
||||
prefix = str_l - suf_l;
|
||||
if (!pa_streq(&str[prefix], suffix))
|
||||
return NULL;
|
||||
ret = pa_xmalloc(prefix + 1);
|
||||
memcpy(ret, str, prefix);
|
||||
ret[prefix] = '\0';
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline const char *pa_split_in_place(const char *c, const char *delimiter, size_t *n, const char**state)
|
||||
{
|
||||
const char *current = *state ? *state : c;
|
||||
size_t l;
|
||||
if (!*current)
|
||||
return NULL;
|
||||
l = strcspn(current, delimiter);
|
||||
*state = current+l;
|
||||
if (**state)
|
||||
(*state)++;
|
||||
*n = l;
|
||||
return current;
|
||||
}
|
||||
|
||||
static inline const char *pa_split_spaces_in_place(const char *c, size_t *n, const char **state)
|
||||
{
|
||||
const char *current = *state ? *state : c;
|
||||
size_t l;
|
||||
if (!*current || *c == 0)
|
||||
return NULL;
|
||||
current += strspn(current, PA_WHITESPACE);
|
||||
l = strcspn(current, PA_WHITESPACE);
|
||||
*state = current+l;
|
||||
*n = l;
|
||||
return current;
|
||||
}
|
||||
|
||||
static inline bool pa_str_in_list_spaces(const char *haystack, const char *needle)
|
||||
{
|
||||
const char *s;
|
||||
size_t n;
|
||||
const char *state = NULL;
|
||||
|
||||
if (!haystack || !needle)
|
||||
return false;
|
||||
|
||||
while ((s = pa_split_spaces_in_place(haystack, &n, &state))) {
|
||||
if (pa_strneq(needle, s, n))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline char *pa_strip(char *s)
|
||||
{
|
||||
char *e, *l = NULL;
|
||||
s += strspn(s, PA_WHITESPACE);
|
||||
for (e = s; *e; e++)
|
||||
if (!strchr(PA_WHITESPACE, *e))
|
||||
l = e;
|
||||
if (l)
|
||||
*(l+1) = 0;
|
||||
else
|
||||
*s = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
static inline int pa_atod(const char *s, double *ret_d)
|
||||
{
|
||||
char *x;
|
||||
*ret_d = strtod(s, &x);
|
||||
return 0;
|
||||
}
|
||||
static inline int pa_atoi(const char *s, int32_t *ret_i)
|
||||
{
|
||||
*ret_i = (int32_t) atoi(s);
|
||||
return 0;
|
||||
}
|
||||
static inline int pa_atou(const char *s, uint32_t *ret_u)
|
||||
{
|
||||
*ret_u = (uint32_t) atoi(s);
|
||||
return 0;
|
||||
}
|
||||
static inline int pa_atol(const char *s, long *ret_l)
|
||||
{
|
||||
char *x;
|
||||
*ret_l = strtol(s, &x, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int pa_parse_boolean(const char *v)
|
||||
{
|
||||
if (pa_streq(v, "1") || !strcasecmp(v, "y") || !strcasecmp(v, "t")
|
||||
|| !strcasecmp(v, "yes") || !strcasecmp(v, "true") || !strcasecmp(v, "on"))
|
||||
return 1;
|
||||
else if (pa_streq(v, "0") || !strcasecmp(v, "n") || !strcasecmp(v, "f")
|
||||
|| !strcasecmp(v, "no") || !strcasecmp(v, "false") || !strcasecmp(v, "off"))
|
||||
return 0;
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline const char *pa_yes_no(bool b) {
|
||||
return b ? "yes" : "no";
|
||||
}
|
||||
|
||||
static inline const char *pa_strna(const char *x) {
|
||||
return x ? x : "n/a";
|
||||
}
|
||||
|
||||
static inline pa_sample_spec* pa_sample_spec_init(pa_sample_spec *spec)
|
||||
{
|
||||
spec->format = PA_SAMPLE_INVALID;
|
||||
spec->rate = 0;
|
||||
spec->channels = 0;
|
||||
return spec;
|
||||
}
|
||||
|
||||
static inline char *pa_readlink(const char *p) {
|
||||
#ifdef HAVE_READLINK
|
||||
size_t l = 100;
|
||||
|
||||
for (;;) {
|
||||
char *c;
|
||||
ssize_t n;
|
||||
|
||||
c = pa_xmalloc(l);
|
||||
|
||||
if ((n = readlink(p, c, l-1)) < 0) {
|
||||
pa_xfree(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((size_t) n < l-1) {
|
||||
c[n] = 0;
|
||||
return c;
|
||||
}
|
||||
|
||||
pa_xfree(c);
|
||||
l *= 2;
|
||||
}
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
#define _(...) (__VA_ARGS__)
|
||||
#define N_(...) (__VA_ARGS__)
|
||||
|
||||
#include "channelmap.h"
|
||||
#include "volume.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PULSE_COMPAT_H */
|
||||
389
spa/plugins/alsa/acp/conf-parser.c
Normal file
389
spa/plugins/alsa/acp/conf-parser.c
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2004-2006 Lennart Poettering
|
||||
|
||||
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/>.
|
||||
***/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
#include "conf-parser.h"
|
||||
|
||||
#define WHITESPACE " \t\n"
|
||||
#define COMMENTS "#;\n"
|
||||
|
||||
/* Run the user supplied parser for an assignment */
|
||||
static int normal_assignment(pa_config_parser_state *state) {
|
||||
const pa_config_item *item;
|
||||
|
||||
pa_assert(state);
|
||||
|
||||
for (item = state->item_table; item->parse; item++) {
|
||||
|
||||
if (item->lvalue && !pa_streq(state->lvalue, item->lvalue))
|
||||
continue;
|
||||
|
||||
if (item->section && !state->section)
|
||||
continue;
|
||||
|
||||
if (item->section && !pa_streq(state->section, item->section))
|
||||
continue;
|
||||
|
||||
state->data = item->data;
|
||||
|
||||
return item->parse(state);
|
||||
}
|
||||
|
||||
pa_log("[%s:%u] Unknown lvalue '%s' in section '%s'.", state->filename, state->lineno, state->lvalue, pa_strna(state->section));
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Parse a proplist entry. */
|
||||
static int proplist_assignment(pa_config_parser_state *state) {
|
||||
pa_assert(state);
|
||||
pa_assert(state->proplist);
|
||||
|
||||
if (pa_proplist_sets(state->proplist, state->lvalue, state->rvalue) < 0) {
|
||||
pa_log("[%s:%u] Failed to parse a proplist entry: %s = %s", state->filename, state->lineno, state->lvalue, state->rvalue);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse a variable assignment line */
|
||||
static int parse_line(pa_config_parser_state *state) {
|
||||
char *c;
|
||||
|
||||
state->lvalue = state->buf + strspn(state->buf, WHITESPACE);
|
||||
|
||||
if ((c = strpbrk(state->lvalue, COMMENTS)))
|
||||
*c = 0;
|
||||
|
||||
if (!*state->lvalue)
|
||||
return 0;
|
||||
|
||||
if (pa_startswith(state->lvalue, ".include ")) {
|
||||
char *path = NULL, *fn;
|
||||
int r;
|
||||
|
||||
fn = pa_strip(state->lvalue + 9);
|
||||
if (!pa_is_path_absolute(fn)) {
|
||||
const char *k;
|
||||
if ((k = strrchr(state->filename, '/'))) {
|
||||
char *dir = pa_xstrndup(state->filename, k - state->filename);
|
||||
fn = path = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", dir, fn);
|
||||
pa_xfree(dir);
|
||||
}
|
||||
}
|
||||
|
||||
r = pa_config_parse(fn, NULL, state->item_table, state->proplist, false, state->userdata);
|
||||
pa_xfree(path);
|
||||
return r;
|
||||
}
|
||||
|
||||
if (*state->lvalue == '[') {
|
||||
size_t k;
|
||||
|
||||
k = strlen(state->lvalue);
|
||||
pa_assert(k > 0);
|
||||
|
||||
if (state->lvalue[k-1] != ']') {
|
||||
pa_log("[%s:%u] Invalid section header.", state->filename, state->lineno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pa_xfree(state->section);
|
||||
state->section = pa_xstrndup(state->lvalue + 1, k-2);
|
||||
|
||||
if (pa_streq(state->section, "Properties")) {
|
||||
if (!state->proplist) {
|
||||
pa_log("[%s:%u] \"Properties\" section is not allowed in this file.", state->filename, state->lineno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
state->in_proplist = true;
|
||||
} else
|
||||
state->in_proplist = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(state->rvalue = strchr(state->lvalue, '='))) {
|
||||
pa_log("[%s:%u] Missing '='.", state->filename, state->lineno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*state->rvalue = 0;
|
||||
state->rvalue++;
|
||||
|
||||
state->lvalue = pa_strip(state->lvalue);
|
||||
state->rvalue = pa_strip(state->rvalue);
|
||||
|
||||
if (state->in_proplist)
|
||||
return proplist_assignment(state);
|
||||
else
|
||||
return normal_assignment(state);
|
||||
}
|
||||
|
||||
#ifndef OS_IS_WIN32
|
||||
static int conf_filter(const struct dirent *entry) {
|
||||
return pa_endswith(entry->d_name, ".conf");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Go through the file and parse each line */
|
||||
int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, pa_proplist *proplist, bool use_dot_d,
|
||||
void *userdata) {
|
||||
int r = -1;
|
||||
bool do_close = !f;
|
||||
pa_config_parser_state state;
|
||||
|
||||
pa_assert(filename);
|
||||
pa_assert(t);
|
||||
|
||||
pa_zero(state);
|
||||
|
||||
if (!f && !(f = pa_fopen_cloexec(filename, "r"))) {
|
||||
if (errno == ENOENT) {
|
||||
pa_log_debug("Failed to open configuration file '%s': %s", filename, pa_cstrerror(errno));
|
||||
r = 0;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
pa_log_warn("Failed to open configuration file '%s': %s", filename, pa_cstrerror(errno));
|
||||
goto finish;
|
||||
}
|
||||
pa_log_debug("Parsing configuration file '%s'", filename);
|
||||
|
||||
state.filename = filename;
|
||||
state.item_table = t;
|
||||
state.userdata = userdata;
|
||||
|
||||
if (proplist)
|
||||
state.proplist = pa_proplist_new();
|
||||
|
||||
while (!feof(f)) {
|
||||
if (!fgets(state.buf, sizeof(state.buf), f)) {
|
||||
if (feof(f))
|
||||
break;
|
||||
|
||||
pa_log_warn("Failed to read configuration file '%s': %s", filename, pa_cstrerror(errno));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
state.lineno++;
|
||||
|
||||
if (parse_line(&state) < 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (proplist)
|
||||
pa_proplist_update(proplist, PA_UPDATE_REPLACE, state.proplist);
|
||||
|
||||
r = 0;
|
||||
|
||||
finish:
|
||||
if (state.proplist)
|
||||
pa_proplist_free(state.proplist);
|
||||
|
||||
pa_xfree(state.section);
|
||||
|
||||
if (do_close && f)
|
||||
fclose(f);
|
||||
|
||||
if (use_dot_d) {
|
||||
#ifdef OS_IS_WIN32
|
||||
char *dir_name = pa_sprintf_malloc("%s.d", filename);
|
||||
char *pattern = pa_sprintf_malloc("%s\\*.conf", dir_name);
|
||||
HANDLE fh;
|
||||
WIN32_FIND_DATA wfd;
|
||||
|
||||
fh = FindFirstFile(pattern, &wfd);
|
||||
if (fh != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
char *filename2 = pa_sprintf_malloc("%s\\%s", dir_name, wfd.cFileName);
|
||||
pa_config_parse(filename2, NULL, t, proplist, false, userdata);
|
||||
pa_xfree(filename2);
|
||||
}
|
||||
} while (FindNextFile(fh, &wfd));
|
||||
FindClose(fh);
|
||||
} else {
|
||||
DWORD err = GetLastError();
|
||||
|
||||
if (err == ERROR_PATH_NOT_FOUND) {
|
||||
pa_log_debug("Pattern %s did not match any files, ignoring.", pattern);
|
||||
} else {
|
||||
LPVOID msgbuf;
|
||||
DWORD fret = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&msgbuf, 0, NULL);
|
||||
|
||||
if (fret != 0) {
|
||||
pa_log_warn("FindFirstFile(%s) failed with error %ld (%s), ignoring.", pattern, err, (char*)msgbuf);
|
||||
LocalFree(msgbuf);
|
||||
} else {
|
||||
pa_log_warn("FindFirstFile(%s) failed with error %ld, ignoring.", pattern, err);
|
||||
pa_log_warn("FormatMessage failed with error %ld", GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pa_xfree(pattern);
|
||||
pa_xfree(dir_name);
|
||||
#else
|
||||
char *dir_name;
|
||||
int n;
|
||||
struct dirent **entries = NULL;
|
||||
|
||||
dir_name = pa_sprintf_malloc("%s.d", filename);
|
||||
|
||||
n = scandir(dir_name, &entries, conf_filter, alphasort);
|
||||
if (n >= 0) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
char *filename2;
|
||||
|
||||
filename2 = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", dir_name, entries[i]->d_name);
|
||||
pa_config_parse(filename2, NULL, t, proplist, false, userdata);
|
||||
pa_xfree(filename2);
|
||||
|
||||
free(entries[i]);
|
||||
}
|
||||
|
||||
free(entries);
|
||||
} else {
|
||||
if (errno == ENOENT)
|
||||
pa_log_debug("%s does not exist, ignoring.", dir_name);
|
||||
else
|
||||
pa_log_warn("scandir(\"%s\") failed: %s", dir_name, pa_cstrerror(errno));
|
||||
}
|
||||
|
||||
pa_xfree(dir_name);
|
||||
#endif
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int pa_config_parse_int(pa_config_parser_state *state) {
|
||||
int *i;
|
||||
int32_t k;
|
||||
|
||||
pa_assert(state);
|
||||
|
||||
i = state->data;
|
||||
|
||||
if (pa_atoi(state->rvalue, &k) < 0) {
|
||||
pa_log("[%s:%u] Failed to parse numeric value: %s", state->filename, state->lineno, state->rvalue);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*i = (int) k;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_config_parse_unsigned(pa_config_parser_state *state) {
|
||||
unsigned *u;
|
||||
uint32_t k;
|
||||
|
||||
pa_assert(state);
|
||||
|
||||
u = state->data;
|
||||
|
||||
if (pa_atou(state->rvalue, &k) < 0) {
|
||||
pa_log("[%s:%u] Failed to parse numeric value: %s", state->filename, state->lineno, state->rvalue);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*u = (unsigned) k;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_config_parse_size(pa_config_parser_state *state) {
|
||||
size_t *i;
|
||||
uint32_t k;
|
||||
|
||||
pa_assert(state);
|
||||
|
||||
i = state->data;
|
||||
|
||||
if (pa_atou(state->rvalue, &k) < 0) {
|
||||
pa_log("[%s:%u] Failed to parse numeric value: %s", state->filename, state->lineno, state->rvalue);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*i = (size_t) k;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_config_parse_bool(pa_config_parser_state *state) {
|
||||
int k;
|
||||
bool *b;
|
||||
|
||||
pa_assert(state);
|
||||
|
||||
b = state->data;
|
||||
|
||||
if ((k = pa_parse_boolean(state->rvalue)) < 0) {
|
||||
pa_log("[%s:%u] Failed to parse boolean value: %s", state->filename, state->lineno, state->rvalue);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*b = !!k;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_config_parse_not_bool(pa_config_parser_state *state) {
|
||||
int k;
|
||||
bool *b;
|
||||
|
||||
pa_assert(state);
|
||||
|
||||
b = state->data;
|
||||
|
||||
if ((k = pa_parse_boolean(state->rvalue)) < 0) {
|
||||
pa_log("[%s:%u] Failed to parse boolean value: %s", state->filename, state->lineno, state->rvalue);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*b = !k;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_config_parse_string(pa_config_parser_state *state) {
|
||||
char **s;
|
||||
|
||||
pa_assert(state);
|
||||
|
||||
s = state->data;
|
||||
|
||||
pa_xfree(*s);
|
||||
*s = *state->rvalue ? pa_xstrdup(state->rvalue) : NULL;
|
||||
return 0;
|
||||
}
|
||||
88
spa/plugins/alsa/acp/conf-parser.h
Normal file
88
spa/plugins/alsa/acp/conf-parser.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#ifndef fooconfparserhfoo
|
||||
#define fooconfparserhfoo
|
||||
|
||||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2004-2006 Lennart Poettering
|
||||
|
||||
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 <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
/* An abstract parser for simple, line based, shallow configuration
|
||||
* files consisting of variable assignments only. */
|
||||
|
||||
typedef struct pa_config_parser_state pa_config_parser_state;
|
||||
|
||||
typedef int (*pa_config_parser_cb_t)(pa_config_parser_state *state);
|
||||
|
||||
/* Wraps info for parsing a specific configuration variable */
|
||||
typedef struct pa_config_item {
|
||||
const char *lvalue; /* name of the variable */
|
||||
pa_config_parser_cb_t parse; /* Function that is called to parse the variable's value */
|
||||
void *data; /* Where to store the variable's data */
|
||||
const char *section;
|
||||
} pa_config_item;
|
||||
|
||||
struct pa_config_parser_state {
|
||||
const char *filename;
|
||||
unsigned lineno;
|
||||
char *section;
|
||||
char *lvalue;
|
||||
char *rvalue;
|
||||
void *data; /* The data pointer of the current pa_config_item. */
|
||||
void *userdata; /* The pointer that was given to pa_config_parse(). */
|
||||
|
||||
/* Private data to be used only by conf-parser.c. */
|
||||
const pa_config_item *item_table;
|
||||
char buf[4096];
|
||||
pa_proplist *proplist;
|
||||
bool in_proplist;
|
||||
};
|
||||
|
||||
/* The configuration file parsing routine. Expects a table of
|
||||
* pa_config_items in *t that is terminated by an item where lvalue is
|
||||
* NULL.
|
||||
*
|
||||
* If use_dot_d is true, then after parsing the file named by the filename
|
||||
* argument, the function will parse all files ending with ".conf" in
|
||||
* alphabetical order from a directory whose name is filename + ".d", if such
|
||||
* directory exists.
|
||||
*
|
||||
* Some configuration files may contain a Properties section, which
|
||||
* is a bit special. Normally all accepted lvalues must be predefined
|
||||
* in the pa_config_item table, but in the Properties section the
|
||||
* pa_config_item table is ignored, and all lvalues are accepted (as
|
||||
* long as they are valid proplist keys). If the proplist pointer is
|
||||
* non-NULL, the parser will parse any section named "Properties" as
|
||||
* properties, and those properties will be merged into the given
|
||||
* proplist. If proplist is NULL, then sections named "Properties"
|
||||
* are not allowed at all in the configuration file. */
|
||||
int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, pa_proplist *proplist, bool use_dot_d,
|
||||
void *userdata);
|
||||
|
||||
/* Generic parsers for integers, size_t, booleans and strings */
|
||||
int pa_config_parse_int(pa_config_parser_state *state);
|
||||
int pa_config_parse_unsigned(pa_config_parser_state *state);
|
||||
int pa_config_parse_size(pa_config_parser_state *state);
|
||||
int pa_config_parse_bool(pa_config_parser_state *state);
|
||||
int pa_config_parse_not_bool(pa_config_parser_state *state);
|
||||
int pa_config_parse_string(pa_config_parser_state *state);
|
||||
|
||||
#endif
|
||||
104
spa/plugins/alsa/acp/device-port.h
Normal file
104
spa/plugins/alsa/acp/device-port.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/***
|
||||
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/>.
|
||||
***/
|
||||
|
||||
|
||||
#ifndef PULSE_DEVICE_PORT_H
|
||||
#define PULSE_DEVICE_PORT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
typedef struct pa_card pa_card;
|
||||
typedef struct pa_device_port pa_device_port;
|
||||
|
||||
/** Port type. \since 14.0 */
|
||||
typedef enum pa_device_port_type {
|
||||
PA_DEVICE_PORT_TYPE_UNKNOWN = 0,
|
||||
PA_DEVICE_PORT_TYPE_AUX = 1,
|
||||
PA_DEVICE_PORT_TYPE_SPEAKER = 2,
|
||||
PA_DEVICE_PORT_TYPE_HEADPHONES = 3,
|
||||
PA_DEVICE_PORT_TYPE_LINE = 4,
|
||||
PA_DEVICE_PORT_TYPE_MIC = 5,
|
||||
PA_DEVICE_PORT_TYPE_HEADSET = 6,
|
||||
PA_DEVICE_PORT_TYPE_HANDSET = 7,
|
||||
PA_DEVICE_PORT_TYPE_EARPIECE = 8,
|
||||
PA_DEVICE_PORT_TYPE_SPDIF = 9,
|
||||
PA_DEVICE_PORT_TYPE_HDMI = 10,
|
||||
PA_DEVICE_PORT_TYPE_TV = 11,
|
||||
PA_DEVICE_PORT_TYPE_RADIO = 12,
|
||||
PA_DEVICE_PORT_TYPE_VIDEO = 13,
|
||||
PA_DEVICE_PORT_TYPE_USB = 14,
|
||||
PA_DEVICE_PORT_TYPE_BLUETOOTH = 15,
|
||||
PA_DEVICE_PORT_TYPE_PORTABLE = 16,
|
||||
PA_DEVICE_PORT_TYPE_HANDSFREE = 17,
|
||||
PA_DEVICE_PORT_TYPE_CAR = 18,
|
||||
PA_DEVICE_PORT_TYPE_HIFI = 19,
|
||||
PA_DEVICE_PORT_TYPE_PHONE = 20,
|
||||
PA_DEVICE_PORT_TYPE_NETWORK = 21,
|
||||
PA_DEVICE_PORT_TYPE_ANALOG = 22,
|
||||
} pa_device_port_type_t;
|
||||
|
||||
struct pa_device_port {
|
||||
struct acp_port port;
|
||||
|
||||
pa_card *card;
|
||||
|
||||
pa_proplist *proplist;
|
||||
pa_hashmap *profiles;
|
||||
pa_dynarray prof;
|
||||
|
||||
void (*impl_free)(struct pa_device_port *port);
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
#define PA_DEVICE_PORT_DATA(p) (p->user_data);
|
||||
|
||||
typedef struct pa_device_port_new_data {
|
||||
char *name;
|
||||
char *description;
|
||||
pa_available_t available;
|
||||
char *available_group;
|
||||
pa_direction_t direction;
|
||||
pa_device_port_type_t type;
|
||||
} pa_device_port_new_data;
|
||||
|
||||
pa_device_port_new_data *pa_device_port_new_data_init(pa_device_port_new_data *data);
|
||||
void pa_device_port_new_data_set_name(pa_device_port_new_data *data, const char *name);
|
||||
void pa_device_port_new_data_set_description(pa_device_port_new_data *data, const char *description);
|
||||
void pa_device_port_new_data_set_available(pa_device_port_new_data *data, pa_available_t available);
|
||||
void pa_device_port_new_data_set_available_group(pa_device_port_new_data *data, const char *group);
|
||||
void pa_device_port_new_data_set_direction(pa_device_port_new_data *data, pa_direction_t direction);
|
||||
void pa_device_port_new_data_set_type(pa_device_port_new_data *data, pa_device_port_type_t type);
|
||||
void pa_device_port_new_data_done(pa_device_port_new_data *data);
|
||||
|
||||
pa_device_port *pa_device_port_new(pa_core *c, pa_device_port_new_data *data, size_t extra);
|
||||
|
||||
void pa_device_port_set_available(pa_device_port *p, pa_available_t status);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PULSE_DEVICE_PORT_H */
|
||||
159
spa/plugins/alsa/acp/dynarray.h
Normal file
159
spa/plugins/alsa/acp/dynarray.h
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/* ALSA Card Profile
|
||||
*
|
||||
* Copyright © 2020 Wim Taymans
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef PA_DYNARRAY_H
|
||||
#define PA_DYNARRAY_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "array.h"
|
||||
|
||||
typedef struct pa_dynarray_item {
|
||||
void *ptr;
|
||||
} pa_dynarray_item;
|
||||
|
||||
typedef struct pa_dynarray {
|
||||
pa_array array;
|
||||
pa_free_cb_t free_cb;
|
||||
} pa_dynarray;
|
||||
|
||||
static inline void pa_dynarray_init(pa_dynarray *array, pa_free_cb_t free_cb)
|
||||
{
|
||||
pa_array_init(&array->array, 16);
|
||||
array->free_cb = free_cb;
|
||||
}
|
||||
|
||||
static inline void pa_dynarray_item_free(pa_dynarray *array, pa_dynarray_item *item)
|
||||
{
|
||||
if (array->free_cb)
|
||||
array->free_cb(item->ptr);
|
||||
}
|
||||
|
||||
static inline void pa_dynarray_clear(pa_dynarray *array)
|
||||
{
|
||||
pa_dynarray_item *item;
|
||||
pa_array_for_each(item, &array->array)
|
||||
pa_dynarray_item_free(array, item);
|
||||
pa_array_clear(&array->array);
|
||||
}
|
||||
|
||||
static inline pa_dynarray* pa_dynarray_new(pa_free_cb_t free_cb)
|
||||
{
|
||||
pa_dynarray *d = calloc(1, sizeof(*d));
|
||||
pa_dynarray_init(d, free_cb);
|
||||
return d;
|
||||
}
|
||||
|
||||
static inline void pa_dynarray_free(pa_dynarray *array)
|
||||
{
|
||||
pa_dynarray_clear(array);
|
||||
free(array);
|
||||
}
|
||||
|
||||
static inline void pa_dynarray_append(pa_dynarray *array, void *p)
|
||||
{
|
||||
pa_dynarray_item *item = pa_array_add(&array->array, sizeof(*item));
|
||||
item->ptr = p;
|
||||
}
|
||||
|
||||
static inline pa_dynarray_item *pa_dynarray_find_item(pa_dynarray *array, void *p)
|
||||
{
|
||||
pa_dynarray_item *item;
|
||||
pa_array_for_each(item, &array->array) {
|
||||
if (item->ptr == p)
|
||||
return item;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline pa_dynarray_item *pa_dynarray_get_item(pa_dynarray *array, unsigned i)
|
||||
{
|
||||
if (!pa_array_check_index(&array->array, i, pa_dynarray_item))
|
||||
return NULL;
|
||||
return pa_array_get_unchecked(&array->array, i, pa_dynarray_item);
|
||||
}
|
||||
|
||||
static inline void *pa_dynarray_get(pa_dynarray *array, unsigned i)
|
||||
{
|
||||
pa_dynarray_item *item = pa_dynarray_get_item(array, i);
|
||||
if (item == NULL)
|
||||
return NULL;
|
||||
return item->ptr;
|
||||
}
|
||||
|
||||
static inline int pa_dynarray_insert_by_index(pa_dynarray *array, void *p, unsigned i)
|
||||
{
|
||||
unsigned j, len;
|
||||
pa_dynarray_item *item;
|
||||
|
||||
len = pa_array_get_len(&array->array, pa_dynarray_item);
|
||||
|
||||
if (i > len)
|
||||
return -EINVAL;
|
||||
|
||||
item = pa_array_add(&array->array, sizeof(*item));
|
||||
for (j = len; j > i; j--) {
|
||||
item--;
|
||||
item[1].ptr = item[0].ptr;
|
||||
}
|
||||
item->ptr = p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int pa_dynarray_remove_by_index(pa_dynarray *array, unsigned i)
|
||||
{
|
||||
pa_dynarray_item *item = pa_dynarray_get_item(array, i);
|
||||
if (item == NULL)
|
||||
return -ENOENT;
|
||||
pa_dynarray_item_free(array, item);
|
||||
pa_array_remove(&array->array, item);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int pa_dynarray_remove_by_data(pa_dynarray *array, void *p)
|
||||
{
|
||||
pa_dynarray_item *item = pa_dynarray_find_item(array, p);
|
||||
if (item == NULL)
|
||||
return -ENOENT;
|
||||
pa_dynarray_item_free(array, item);
|
||||
pa_array_remove(&array->array, item);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline unsigned pa_dynarray_size(pa_dynarray *array)
|
||||
{
|
||||
return pa_array_get_len(&array->array, pa_dynarray_item);
|
||||
}
|
||||
|
||||
#define PA_DYNARRAY_FOREACH(elem, array, idx) \
|
||||
for ((idx) = 0; ((elem) = pa_dynarray_get(array, idx)); (idx)++)
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* PA_DYNARRAY_H */
|
||||
219
spa/plugins/alsa/acp/hashmap.h
Normal file
219
spa/plugins/alsa/acp/hashmap.h
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
/* ALSA Card Profile
|
||||
*
|
||||
* Copyright © 2020 Wim Taymans
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef PA_HASHMAP_H
|
||||
#define PA_HASHMAP_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "array.h"
|
||||
|
||||
typedef unsigned (*pa_hash_func_t)(const void *p);
|
||||
typedef int (*pa_compare_func_t)(const void *a, const void *b);
|
||||
|
||||
typedef struct pa_hashmap_item {
|
||||
void *key;
|
||||
void *value;
|
||||
} pa_hashmap_item;
|
||||
|
||||
typedef struct pa_hashmap {
|
||||
pa_array array;
|
||||
pa_hash_func_t hash_func;
|
||||
pa_compare_func_t compare_func;
|
||||
pa_free_cb_t key_free_func;
|
||||
pa_free_cb_t value_free_func;
|
||||
} pa_hashmap;
|
||||
|
||||
static inline pa_hashmap *pa_hashmap_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func)
|
||||
{
|
||||
pa_hashmap *m = calloc(1, sizeof(pa_hashmap));
|
||||
pa_array_init(&m->array, 16);
|
||||
m->hash_func = hash_func;
|
||||
m->compare_func = compare_func;
|
||||
return m;
|
||||
}
|
||||
|
||||
static inline pa_hashmap *pa_hashmap_new_full(pa_hash_func_t hash_func, pa_compare_func_t compare_func,
|
||||
pa_free_cb_t key_free_func, pa_free_cb_t value_free_func)
|
||||
{
|
||||
pa_hashmap *m = pa_hashmap_new(hash_func, compare_func);
|
||||
m->key_free_func = key_free_func;
|
||||
m->value_free_func = value_free_func;
|
||||
return m;
|
||||
}
|
||||
|
||||
static inline void pa_hashmap_item_free(pa_hashmap *h, pa_hashmap_item *item)
|
||||
{
|
||||
if (h->key_free_func)
|
||||
h->key_free_func(item->key);
|
||||
if (h->value_free_func)
|
||||
h->value_free_func(item->value);
|
||||
}
|
||||
|
||||
static inline void pa_hashmap_remove_all(pa_hashmap *h)
|
||||
{
|
||||
pa_hashmap_item *item;
|
||||
pa_array_for_each(item, &h->array)
|
||||
pa_hashmap_item_free(h, item);
|
||||
pa_array_reset(&h->array);
|
||||
}
|
||||
|
||||
static inline void pa_hashmap_free(pa_hashmap *h)
|
||||
{
|
||||
pa_hashmap_remove_all(h);
|
||||
pa_array_clear(&h->array);
|
||||
free(h);
|
||||
}
|
||||
|
||||
static inline pa_hashmap_item* pa_hashmap_find_free(pa_hashmap *h)
|
||||
{
|
||||
pa_hashmap_item *item;
|
||||
pa_array_for_each(item, &h->array) {
|
||||
if (item->key == NULL)
|
||||
return item;
|
||||
}
|
||||
return pa_array_add(&h->array, sizeof(*item));
|
||||
}
|
||||
|
||||
static inline pa_hashmap_item* pa_hashmap_find(const pa_hashmap *h, const void *key)
|
||||
{
|
||||
pa_hashmap_item *item = NULL;
|
||||
pa_array_for_each(item, &h->array) {
|
||||
if (item->key != NULL && h->compare_func(item->key, key) == 0)
|
||||
return item;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void* pa_hashmap_get(const pa_hashmap *h, const void *key)
|
||||
{
|
||||
const pa_hashmap_item *item = pa_hashmap_find(h, key);
|
||||
if (item == NULL)
|
||||
return NULL;
|
||||
return item->value;
|
||||
}
|
||||
|
||||
static inline int pa_hashmap_put(pa_hashmap *h, void *key, void *value)
|
||||
{
|
||||
pa_hashmap_item *item = pa_hashmap_find(h, key);
|
||||
if (item != NULL)
|
||||
return -1;
|
||||
item = pa_hashmap_find_free(h);
|
||||
item->key = key;
|
||||
item->value = value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void* pa_hashmap_remove(pa_hashmap *h, const void *key)
|
||||
{
|
||||
pa_hashmap_item *item = pa_hashmap_find(h, key);
|
||||
void *value;
|
||||
if (item == NULL)
|
||||
return NULL;
|
||||
value = item->value;
|
||||
if (h->key_free_func)
|
||||
h->key_free_func(item->key);
|
||||
item->key = NULL;
|
||||
item->value = NULL;
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline int pa_hashmap_remove_and_free(pa_hashmap *h, const void *key)
|
||||
{
|
||||
void *val = pa_hashmap_remove(h, key);
|
||||
if (val && h->value_free_func)
|
||||
h->value_free_func(val);
|
||||
return val ? 0 : -1;
|
||||
}
|
||||
|
||||
static inline void *pa_hashmap_first(const pa_hashmap *h)
|
||||
{
|
||||
pa_hashmap_item *item;
|
||||
pa_array_for_each(item, &h->array) {
|
||||
if (item->key != NULL)
|
||||
return item->value;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void *pa_hashmap_iterate(const pa_hashmap *h, void **state, const void **key)
|
||||
{
|
||||
pa_hashmap_item *it = *state;
|
||||
if (it == NULL)
|
||||
*state = pa_array_first(&h->array);
|
||||
do {
|
||||
it = *state;
|
||||
if (!pa_array_check(&h->array, it))
|
||||
return NULL;
|
||||
*state = it + 1;
|
||||
} while (it->key == NULL);
|
||||
if (key)
|
||||
*key = it->key;
|
||||
return it->value;
|
||||
}
|
||||
|
||||
static inline bool pa_hashmap_isempty(const pa_hashmap *h)
|
||||
{
|
||||
pa_hashmap_item *item;
|
||||
pa_array_for_each(item, &h->array)
|
||||
if (item->key != NULL)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline unsigned pa_hashmap_size(const pa_hashmap *h)
|
||||
{
|
||||
unsigned count = 0;
|
||||
pa_hashmap_item *item;
|
||||
pa_array_for_each(item, &h->array)
|
||||
if (item->key != NULL)
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
static inline void pa_hashmap_sort(pa_hashmap *h,
|
||||
int (*compar)(const void *, const void *))
|
||||
{
|
||||
qsort((void*)h->array.data,
|
||||
pa_array_get_len(&h->array, pa_hashmap_item),
|
||||
sizeof(pa_hashmap_item), compar);
|
||||
}
|
||||
|
||||
#define PA_HASHMAP_FOREACH(e, h, state) \
|
||||
for ((state) = NULL, (e) = pa_hashmap_iterate((h), &(state), NULL); \
|
||||
(e); (e) = pa_hashmap_iterate((h), &(state), NULL))
|
||||
|
||||
/* A macro to ease itration through all key, value pairs */
|
||||
#define PA_HASHMAP_FOREACH_KV(k, e, h, state) \
|
||||
for ((state) = NULL, (e) = pa_hashmap_iterate((h), &(state), (const void **) &(k)); \
|
||||
(e); (e) = pa_hashmap_iterate((h), &(state), (const void **) &(k)))
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* PA_HASHMAP_H */
|
||||
192
spa/plugins/alsa/acp/idxset.h
Normal file
192
spa/plugins/alsa/acp/idxset.h
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
/* ALSA Card Profile
|
||||
*
|
||||
* Copyright © 2020 Wim Taymans
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef PA_IDXSET_H
|
||||
#define PA_IDXSET_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "array.h"
|
||||
|
||||
#define PA_IDXSET_INVALID ((uint32_t) -1)
|
||||
|
||||
typedef unsigned (*pa_hash_func_t)(const void *p);
|
||||
typedef int (*pa_compare_func_t)(const void *a, const void *b);
|
||||
|
||||
typedef struct pa_idxset_item {
|
||||
void *ptr;
|
||||
} pa_idxset_item;
|
||||
|
||||
typedef struct pa_idxset {
|
||||
pa_array array;
|
||||
pa_hash_func_t hash_func;
|
||||
pa_compare_func_t compare_func;
|
||||
} pa_idxset;
|
||||
|
||||
static inline unsigned pa_idxset_trivial_hash_func(const void *p)
|
||||
{
|
||||
return PA_PTR_TO_UINT(p);
|
||||
}
|
||||
|
||||
static inline int pa_idxset_trivial_compare_func(const void *a, const void *b)
|
||||
{
|
||||
return a < b ? -1 : (a > b ? 1 : 0);
|
||||
}
|
||||
|
||||
static inline unsigned pa_idxset_string_hash_func(const void *p)
|
||||
{
|
||||
unsigned hash = 0;
|
||||
const char *c;
|
||||
for (c = p; *c; c++)
|
||||
hash = 31 * hash + (unsigned) *c;
|
||||
return hash;
|
||||
}
|
||||
|
||||
static inline int pa_idxset_string_compare_func(const void *a, const void *b)
|
||||
{
|
||||
return strcmp(a, b);
|
||||
}
|
||||
|
||||
static inline pa_idxset *pa_idxset_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func)
|
||||
{
|
||||
pa_idxset *s = calloc(1, sizeof(pa_idxset));
|
||||
pa_array_init(&s->array, 16);
|
||||
s->hash_func = hash_func;
|
||||
s->compare_func = compare_func;
|
||||
return s;
|
||||
}
|
||||
|
||||
static inline void pa_idxset_free(pa_idxset *s, pa_free_cb_t free_cb)
|
||||
{
|
||||
if (free_cb) {
|
||||
pa_idxset_item *item;
|
||||
pa_array_for_each(item, &s->array)
|
||||
free_cb(item->ptr);
|
||||
}
|
||||
pa_array_clear(&s->array);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static inline pa_idxset_item* pa_idxset_find(const pa_idxset *s, const void *ptr)
|
||||
{
|
||||
pa_idxset_item *item;
|
||||
pa_array_for_each(item, &s->array) {
|
||||
if (item->ptr == ptr)
|
||||
return item;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline int pa_idxset_put(pa_idxset*s, void *p, uint32_t *idx)
|
||||
{
|
||||
pa_idxset_item *item = pa_idxset_find(s, p);
|
||||
int res = item ? -1 : 0;
|
||||
if (item == NULL) {
|
||||
item = pa_idxset_find(s, NULL);
|
||||
if (item == NULL)
|
||||
item = pa_array_add(&s->array, sizeof(*item));
|
||||
item->ptr = p;
|
||||
}
|
||||
if (idx)
|
||||
*idx = item - (pa_idxset_item*)s->array.data;
|
||||
return res;
|
||||
}
|
||||
|
||||
static inline pa_idxset *pa_idxset_copy(pa_idxset *s, pa_copy_func_t copy_func)
|
||||
{
|
||||
pa_idxset_item *item;
|
||||
pa_idxset *copy = pa_idxset_new(s->hash_func, s->compare_func);
|
||||
pa_array_for_each(item, &s->array) {
|
||||
if (item->ptr)
|
||||
pa_idxset_put(copy, copy_func ? copy_func(item->ptr) : item->ptr, NULL);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
static inline bool pa_idxset_isempty(const pa_idxset *s)
|
||||
{
|
||||
pa_idxset_item *item;
|
||||
pa_array_for_each(item, &s->array)
|
||||
if (item->ptr != NULL)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
static inline unsigned pa_idxset_size(pa_idxset*s)
|
||||
{
|
||||
unsigned count = 0;
|
||||
pa_idxset_item *item;
|
||||
pa_array_for_each(item, &s->array)
|
||||
if (item->ptr != NULL)
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
static inline void *pa_idxset_search(pa_idxset *s, uint32_t *idx)
|
||||
{
|
||||
pa_idxset_item *item;
|
||||
for (item = pa_array_get_unchecked(&s->array, *idx, pa_idxset_item);
|
||||
pa_array_check(&s->array, item); item++, (*idx)++) {
|
||||
if (item->ptr != NULL)
|
||||
return item->ptr;
|
||||
}
|
||||
*idx = PA_IDXSET_INVALID;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void *pa_idxset_next(pa_idxset *s, uint32_t *idx)
|
||||
{
|
||||
(*idx)++;;
|
||||
return pa_idxset_search(s, idx);
|
||||
}
|
||||
|
||||
static inline void* pa_idxset_first(pa_idxset *s, uint32_t *idx)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
void *ptr = pa_idxset_search(s, &i);
|
||||
if (idx)
|
||||
*idx = i;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static inline void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx)
|
||||
{
|
||||
pa_idxset_item *item = pa_idxset_find(s, p);
|
||||
if (item == NULL)
|
||||
return NULL;
|
||||
if (idx)
|
||||
*idx = item - (pa_idxset_item*)s->array.data;
|
||||
return item->ptr;
|
||||
}
|
||||
|
||||
|
||||
#define PA_IDXSET_FOREACH(e, s, idx) \
|
||||
for ((e) = pa_idxset_first((s), &(idx)); (e); (e) = pa_idxset_next((s), &(idx)))
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* PA_IDXSET_H */
|
||||
109
spa/plugins/alsa/acp/llist.h
Normal file
109
spa/plugins/alsa/acp/llist.h
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#ifndef foollistfoo
|
||||
#define foollistfoo
|
||||
|
||||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2004-2006 Lennart Poettering
|
||||
|
||||
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/>.
|
||||
***/
|
||||
|
||||
/* Some macros for maintaining doubly linked lists */
|
||||
|
||||
/* The head of the linked list. Use this in the structure that shall
|
||||
* contain the head of the linked list */
|
||||
#define PA_LLIST_HEAD(t,name) \
|
||||
t *name
|
||||
|
||||
/* The pointers in the linked list's items. Use this in the item structure */
|
||||
#define PA_LLIST_FIELDS(t) \
|
||||
t *next, *prev
|
||||
|
||||
/* Initialize the list's head */
|
||||
#define PA_LLIST_HEAD_INIT(t,item) \
|
||||
do { \
|
||||
(item) = (t*) NULL; } \
|
||||
while(0)
|
||||
|
||||
/* Initialize a list item */
|
||||
#define PA_LLIST_INIT(t,item) \
|
||||
do { \
|
||||
t *_item = (item); \
|
||||
pa_assert(_item); \
|
||||
_item->prev = _item->next = NULL; \
|
||||
} while(0)
|
||||
|
||||
/* Prepend an item to the list */
|
||||
#define PA_LLIST_PREPEND(t,head,item) \
|
||||
do { \
|
||||
t **_head = &(head), *_item = (item); \
|
||||
pa_assert(_item); \
|
||||
if ((_item->next = *_head)) \
|
||||
_item->next->prev = _item; \
|
||||
_item->prev = NULL; \
|
||||
*_head = _item; \
|
||||
} while (0)
|
||||
|
||||
/* Remove an item from the list */
|
||||
#define PA_LLIST_REMOVE(t,head,item) \
|
||||
do { \
|
||||
t **_head = &(head), *_item = (item); \
|
||||
pa_assert(_item); \
|
||||
if (_item->next) \
|
||||
_item->next->prev = _item->prev; \
|
||||
if (_item->prev) \
|
||||
_item->prev->next = _item->next; \
|
||||
else { \
|
||||
pa_assert(*_head == _item); \
|
||||
*_head = _item->next; \
|
||||
} \
|
||||
_item->next = _item->prev = NULL; \
|
||||
} while(0)
|
||||
|
||||
/* Find the head of the list */
|
||||
#define PA_LLIST_FIND_HEAD(t,item,head) \
|
||||
do { \
|
||||
t **_head = (head), *_item = (item); \
|
||||
*_head = _item; \
|
||||
pa_assert(_head); \
|
||||
while ((*_head)->prev) \
|
||||
*_head = (*_head)->prev; \
|
||||
} while (0)
|
||||
|
||||
/* Insert an item after another one (a = where, b = what) */
|
||||
#define PA_LLIST_INSERT_AFTER(t,head,a,b) \
|
||||
do { \
|
||||
t **_head = &(head), *_a = (a), *_b = (b); \
|
||||
pa_assert(_b); \
|
||||
if (!_a) { \
|
||||
if ((_b->next = *_head)) \
|
||||
_b->next->prev = _b; \
|
||||
_b->prev = NULL; \
|
||||
*_head = _b; \
|
||||
} else { \
|
||||
if ((_b->next = _a->next)) \
|
||||
_b->next->prev = _b; \
|
||||
_b->prev = _a; \
|
||||
_a->next = _b; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define PA_LLIST_FOREACH(i,head) \
|
||||
for (i = (head); i; i = i->next)
|
||||
|
||||
#define PA_LLIST_FOREACH_SAFE(i,n,head) \
|
||||
for (i = (head); i && ((n = i->next), 1); i = n)
|
||||
|
||||
#endif
|
||||
23
spa/plugins/alsa/acp/meson.build
Normal file
23
spa/plugins/alsa/acp/meson.build
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
acp_sources = [
|
||||
'acp.c',
|
||||
'compat.c',
|
||||
'alsa-mixer.c',
|
||||
'alsa-ucm.c',
|
||||
'alsa-util.c',
|
||||
'conf-parser.c',
|
||||
]
|
||||
|
||||
acp_c_args = [
|
||||
'-D_GNU_SOURCE',
|
||||
'-DHAVE_ALSA_UCM',
|
||||
'-DHAVE_CONFIG_H',
|
||||
'-DHAVE_READLINK',
|
||||
]
|
||||
|
||||
acp_lib = static_library(
|
||||
'acp',
|
||||
acp_sources,
|
||||
c_args : acp_c_args,
|
||||
include_directories : [configinc],
|
||||
dependencies : [ alsa_dep, mathlib, ],
|
||||
install : true)
|
||||
208
spa/plugins/alsa/acp/proplist.h
Normal file
208
spa/plugins/alsa/acp/proplist.h
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
/* ALSA Card Profile
|
||||
*
|
||||
* Copyright © 2020 Wim Taymans
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef PA_PROPLIST_H
|
||||
#define PA_PROPLIST_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "array.h"
|
||||
#include "acp.h"
|
||||
|
||||
#define PA_PROP_DEVICE_DESCRIPTION "device.description"
|
||||
|
||||
#define PA_PROP_DEVICE_CLASS "device.class"
|
||||
|
||||
#define PA_PROP_DEVICE_FORM_FACTOR "device.form_factor"
|
||||
|
||||
#define PA_PROP_DEVICE_INTENDED_ROLES "device.intended_roles"
|
||||
|
||||
#define PA_PROP_DEVICE_PROFILE_NAME "device.profile.name"
|
||||
|
||||
#define PA_PROP_DEVICE_STRING "device.string"
|
||||
|
||||
#define PA_PROP_DEVICE_API "device.api"
|
||||
|
||||
#define PA_PROP_DEVICE_PRODUCT_NAME "device.product.name"
|
||||
|
||||
#define PA_PROP_DEVICE_PROFILE_DESCRIPTION "device.profile.description"
|
||||
|
||||
typedef struct pa_proplist_item {
|
||||
char *key;
|
||||
char *value;
|
||||
} pa_proplist_item;
|
||||
|
||||
typedef struct pa_proplist {
|
||||
struct pa_array array;
|
||||
} pa_proplist;
|
||||
|
||||
static inline pa_proplist* pa_proplist_new(void)
|
||||
{
|
||||
pa_proplist *p = calloc(1, sizeof(*p));
|
||||
pa_array_init(&p->array, 16);
|
||||
return p;
|
||||
}
|
||||
static inline pa_proplist_item* pa_proplist_item_find(const pa_proplist *p, const void *key)
|
||||
{
|
||||
pa_proplist_item *item;
|
||||
pa_array_for_each(item, &p->array) {
|
||||
if (strcmp(key, item->key) == 0)
|
||||
return item;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void pa_proplist_item_free(pa_proplist_item* it)
|
||||
{
|
||||
free(it->key);
|
||||
free(it->value);
|
||||
}
|
||||
|
||||
static inline void pa_proplist_clear(pa_proplist* p)
|
||||
{
|
||||
pa_proplist_item *item;
|
||||
pa_array_for_each(item, &p->array)
|
||||
pa_proplist_item_free(item);
|
||||
pa_array_reset(&p->array);
|
||||
}
|
||||
|
||||
static inline void pa_proplist_free(pa_proplist* p)
|
||||
{
|
||||
pa_proplist_clear(p);
|
||||
pa_array_clear(&p->array);
|
||||
free(p);
|
||||
}
|
||||
|
||||
static inline unsigned pa_proplist_size(const pa_proplist *p)
|
||||
{
|
||||
return pa_array_get_len(&p->array, pa_proplist_item);
|
||||
}
|
||||
|
||||
static inline int pa_proplist_contains(const pa_proplist *p, const char *key)
|
||||
{
|
||||
return pa_proplist_item_find(p, key) ? 1 : 0;
|
||||
}
|
||||
|
||||
static inline int pa_proplist_sets(pa_proplist *p, const char *key, const char *value)
|
||||
{
|
||||
pa_proplist_item *item = pa_proplist_item_find(p, key);
|
||||
if (item != NULL)
|
||||
pa_proplist_item_free(item);
|
||||
else
|
||||
item = pa_array_add(&p->array, sizeof(*item));
|
||||
item->key = strdup(key);
|
||||
item->value = strdup(value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int pa_proplist_unset(pa_proplist *p, const char *key)
|
||||
{
|
||||
pa_proplist_item *item = pa_proplist_item_find(p, key);
|
||||
if (item == NULL)
|
||||
return -ENOENT;
|
||||
pa_proplist_item_free(item);
|
||||
pa_array_remove(&p->array, item);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static PA_PRINTF_FUNC(3,4) inline int pa_proplist_setf(pa_proplist *p, const char *key, const char *format, ...)
|
||||
{
|
||||
pa_proplist_item *item = pa_proplist_item_find(p, key);
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
if (item != NULL)
|
||||
pa_proplist_item_free(item);
|
||||
else
|
||||
item = pa_array_add(&p->array, sizeof(*item));
|
||||
item->key = strdup(key);
|
||||
vasprintf(&item->value, format, args);
|
||||
va_end(args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline const char *pa_proplist_gets(const pa_proplist *p, const char *key)
|
||||
{
|
||||
pa_proplist_item *item = pa_proplist_item_find(p, key);
|
||||
return item ? item->value : NULL;
|
||||
}
|
||||
|
||||
typedef enum pa_update_mode {
|
||||
PA_UPDATE_SET
|
||||
/**< Replace the entire property list with the new one. Don't keep
|
||||
* any of the old data around. */,
|
||||
PA_UPDATE_MERGE
|
||||
/**< Merge new property list into the existing one, not replacing
|
||||
* any old entries if they share a common key with the new
|
||||
* property list. */,
|
||||
PA_UPDATE_REPLACE
|
||||
/**< Merge new property list into the existing one, replacing all
|
||||
* old entries that share a common key with the new property
|
||||
* list. */
|
||||
} pa_update_mode_t;
|
||||
|
||||
|
||||
static inline void pa_proplist_update(pa_proplist *p, pa_update_mode_t mode, const pa_proplist *other)
|
||||
{
|
||||
pa_proplist_item *item;
|
||||
|
||||
if (mode == PA_UPDATE_SET)
|
||||
pa_proplist_clear(p);
|
||||
|
||||
pa_array_for_each(item, &other->array) {
|
||||
if (mode == PA_UPDATE_MERGE && pa_proplist_contains(p, item->key))
|
||||
continue;
|
||||
pa_proplist_sets(p, item->key, item->value);
|
||||
}
|
||||
}
|
||||
|
||||
static inline pa_proplist* pa_proplist_new_dict(const struct acp_dict *dict)
|
||||
{
|
||||
pa_proplist *p = pa_proplist_new();
|
||||
if (dict) {
|
||||
const struct acp_dict_item *item;
|
||||
struct acp_dict_item *it;
|
||||
acp_dict_for_each(item, dict) {
|
||||
it = pa_array_add(&p->array, sizeof(*it));
|
||||
it->key = strdup(item->key);
|
||||
it->value = strdup(item->value);
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static inline void pa_proplist_as_dict(const pa_proplist *p, struct acp_dict *dict)
|
||||
{
|
||||
dict->n_items = pa_proplist_size(p);
|
||||
dict->items = p->array.data;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* PA_PROPLIST_H */
|
||||
196
spa/plugins/alsa/acp/volume.h
Normal file
196
spa/plugins/alsa/acp/volume.h
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
/***
|
||||
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/>.
|
||||
***/
|
||||
|
||||
#ifndef PA_VOLUME_H
|
||||
#define PA_VOLUME_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <math.h>
|
||||
|
||||
typedef uint32_t pa_volume_t;
|
||||
|
||||
#define PA_VOLUME_MUTED ((pa_volume_t) 0U)
|
||||
#define PA_VOLUME_NORM ((pa_volume_t) 0x10000U)
|
||||
#define PA_VOLUME_MAX ((pa_volume_t) UINT32_MAX/2)
|
||||
|
||||
#ifdef INFINITY
|
||||
#define PA_DECIBEL_MININFTY ((double) -INFINITY)
|
||||
#else
|
||||
#define PA_DECIBEL_MININFTY ((double) -200.0)
|
||||
#endif
|
||||
|
||||
#define PA_CLAMP_VOLUME(v) (PA_CLAMP_UNLIKELY((v), PA_VOLUME_MUTED, PA_VOLUME_MAX))
|
||||
|
||||
typedef struct pa_cvolume {
|
||||
uint32_t channels; /**< Number of channels */
|
||||
pa_volume_t values[PA_CHANNELS_MAX]; /**< Per-channel volume */
|
||||
} pa_cvolume;
|
||||
|
||||
static inline double pa_volume_linear_to_dB(double v)
|
||||
{
|
||||
return 20.0 * log10(v);
|
||||
}
|
||||
|
||||
static inline double pa_sw_volume_to_linear(pa_volume_t v)
|
||||
{
|
||||
double f;
|
||||
if (v <= PA_VOLUME_MUTED)
|
||||
return 0.0;
|
||||
if (v == PA_VOLUME_NORM)
|
||||
return 1.0;
|
||||
f = ((double) v / PA_VOLUME_NORM);
|
||||
return f*f*f;
|
||||
}
|
||||
|
||||
static inline double pa_sw_volume_to_dB(pa_volume_t v)
|
||||
{
|
||||
if (v <= PA_VOLUME_MUTED)
|
||||
return PA_DECIBEL_MININFTY;
|
||||
return pa_volume_linear_to_dB(pa_sw_volume_to_linear(v));
|
||||
}
|
||||
|
||||
static inline double pa_volume_dB_to_linear(double v)
|
||||
{
|
||||
return pow(10.0, v / 20.0);
|
||||
}
|
||||
|
||||
static inline pa_volume_t pa_sw_volume_from_linear(double v)
|
||||
{
|
||||
if (v <= 0.0)
|
||||
return PA_VOLUME_MUTED;
|
||||
return (pa_volume_t) PA_CLAMP_VOLUME((uint64_t) lround(cbrt(v) * PA_VOLUME_NORM));
|
||||
}
|
||||
|
||||
static inline pa_volume_t pa_sw_volume_from_dB(double dB)
|
||||
{
|
||||
if (isinf(dB) < 0 || dB <= PA_DECIBEL_MININFTY)
|
||||
return PA_VOLUME_MUTED;
|
||||
return pa_sw_volume_from_linear(pa_volume_dB_to_linear(dB));
|
||||
}
|
||||
|
||||
static inline pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v)
|
||||
{
|
||||
uint32_t i;
|
||||
a->channels = (uint8_t) channels;
|
||||
for (i = 0; i < a->channels; i++)
|
||||
a->values[i] = PA_CLAMP_VOLUME(v);
|
||||
return a;
|
||||
}
|
||||
|
||||
static inline int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b)
|
||||
{
|
||||
uint32_t i;
|
||||
if (PA_UNLIKELY(a == b))
|
||||
return 1;
|
||||
if (a->channels != b->channels)
|
||||
return 0;
|
||||
for (i = 0; i < a->channels; i++)
|
||||
if (a->values[i] != b->values[i])
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b)
|
||||
{
|
||||
uint64_t result;
|
||||
result = ((uint64_t) a * (uint64_t) b + (uint64_t) PA_VOLUME_NORM / 2ULL) /
|
||||
(uint64_t) PA_VOLUME_NORM;
|
||||
if (result > (uint64_t)PA_VOLUME_MAX)
|
||||
pa_log_warn("pa_sw_volume_multiply: Volume exceeds maximum allowed value and will be clipped. Please check your volume settings.");
|
||||
return (pa_volume_t) PA_CLAMP_VOLUME(result);
|
||||
}
|
||||
|
||||
static inline pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest,
|
||||
const pa_cvolume *a, const pa_cvolume *b)
|
||||
{
|
||||
unsigned i;
|
||||
dest->channels = PA_MIN(a->channels, b->channels);
|
||||
for (i = 0; i < dest->channels; i++)
|
||||
dest->values[i] = pa_sw_volume_multiply(a->values[i], b->values[i]);
|
||||
return dest;
|
||||
}
|
||||
|
||||
static inline pa_cvolume *pa_sw_cvolume_multiply_scalar(pa_cvolume *dest,
|
||||
const pa_cvolume *a, pa_volume_t b)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < a->channels; i++)
|
||||
dest->values[i] = pa_sw_volume_multiply(a->values[i], b);
|
||||
dest->channels = (uint8_t) i;
|
||||
return dest;
|
||||
}
|
||||
|
||||
static inline pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b)
|
||||
{
|
||||
uint64_t result;
|
||||
if (b <= PA_VOLUME_MUTED)
|
||||
return 0;
|
||||
result = ((uint64_t) a * (uint64_t) PA_VOLUME_NORM + (uint64_t) b / 2ULL) / (uint64_t) b;
|
||||
if (result > (uint64_t)PA_VOLUME_MAX)
|
||||
pa_log_warn("pa_sw_volume_divide: Volume exceeds maximum allowed value and will be clipped. Please check your volume settings.");
|
||||
return (pa_volume_t) PA_CLAMP_VOLUME(result);
|
||||
}
|
||||
|
||||
static inline pa_cvolume *pa_sw_cvolume_divide_scalar(pa_cvolume *dest,
|
||||
const pa_cvolume *a, pa_volume_t b) {
|
||||
unsigned i;
|
||||
for (i = 0; i < a->channels; i++)
|
||||
dest->values[i] = pa_sw_volume_divide(a->values[i], b);
|
||||
dest->channels = (uint8_t) i;
|
||||
return dest;
|
||||
}
|
||||
|
||||
static inline pa_cvolume *pa_sw_cvolume_divide(pa_cvolume *dest,
|
||||
const pa_cvolume *a, const pa_cvolume *b)
|
||||
{
|
||||
unsigned i;
|
||||
dest->channels = PA_MIN(a->channels, b->channels);
|
||||
for (i = 0; i < dest->channels; i++)
|
||||
dest->values[i] = pa_sw_volume_divide(a->values[i], b->values[i]);
|
||||
return dest;
|
||||
}
|
||||
|
||||
#define pa_cvolume_reset(a, n) pa_cvolume_set((a), (n), PA_VOLUME_NORM)
|
||||
#define pa_cvolume_mute(a, n) pa_cvolume_set((a), (n), PA_VOLUME_MUTED)
|
||||
|
||||
static inline int pa_cvolume_compatible_with_channel_map(const pa_cvolume *v,
|
||||
const pa_channel_map *cm)
|
||||
{
|
||||
return v->channels == cm->channels;
|
||||
}
|
||||
|
||||
static inline pa_volume_t pa_cvolume_max(const pa_cvolume *a)
|
||||
{
|
||||
pa_volume_t m = PA_VOLUME_MUTED;
|
||||
unsigned c;
|
||||
for (c = 0; c < a->channels; c++)
|
||||
if (a->values[c] > m)
|
||||
m = a->values[c];
|
||||
return m;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* PA_VOLUME_H */
|
||||
799
spa/plugins/alsa/alsa-acp-device.c
Normal file
799
spa/plugins/alsa/alsa-acp-device.c
Normal file
|
|
@ -0,0 +1,799 @@
|
|||
/* Spa ALSA Device
|
||||
*
|
||||
* Copyright © 2018 Wim Taymans
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include <spa/support/log.h>
|
||||
#include <spa/utils/type.h>
|
||||
#include <spa/node/node.h>
|
||||
#include <spa/utils/keys.h>
|
||||
#include <spa/utils/names.h>
|
||||
#include <spa/support/loop.h>
|
||||
#include <spa/support/plugin.h>
|
||||
#include <spa/monitor/device.h>
|
||||
#include <spa/monitor/utils.h>
|
||||
#include <spa/param/param.h>
|
||||
#include <spa/pod/filter.h>
|
||||
#include <spa/pod/parser.h>
|
||||
#include <spa/debug/pod.h>
|
||||
|
||||
#include "acp/acp.h"
|
||||
|
||||
#define NAME "alsa-device"
|
||||
|
||||
#define MAX_POLL 16
|
||||
|
||||
static const char default_device[] = "hw:0";
|
||||
|
||||
struct props {
|
||||
char device[64];
|
||||
};
|
||||
|
||||
static void reset_props(struct props *props)
|
||||
{
|
||||
strncpy(props->device, default_device, 64);
|
||||
}
|
||||
|
||||
struct impl {
|
||||
struct spa_handle handle;
|
||||
struct spa_device device;
|
||||
|
||||
struct spa_log *log;
|
||||
struct spa_loop *loop;
|
||||
|
||||
uint32_t info_all;
|
||||
struct spa_device_info info;
|
||||
struct spa_param_info params[4];
|
||||
|
||||
struct spa_hook_list hooks;
|
||||
|
||||
struct props props;
|
||||
uint32_t n_nodes;
|
||||
|
||||
uint32_t profile;
|
||||
|
||||
struct acp_card *card;
|
||||
struct pollfd pfds[MAX_POLL];
|
||||
int n_pfds;
|
||||
struct spa_source sources[MAX_POLL];
|
||||
};
|
||||
|
||||
static void handle_acp_poll(struct spa_source *source)
|
||||
{
|
||||
struct impl *this = source->data;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < this->n_pfds; i++)
|
||||
this->pfds[i].revents = this->sources[i].rmask;
|
||||
acp_card_handle_events(this->card);
|
||||
for (i = 0; i < this->n_pfds; i++)
|
||||
this->sources[i].rmask = 0;
|
||||
}
|
||||
|
||||
static int setup_sources(struct impl *this)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < this->n_pfds; i++) {
|
||||
spa_loop_remove_source(this->loop, &this->sources[i]);
|
||||
}
|
||||
|
||||
this->n_pfds = acp_card_poll_descriptors(this->card, this->pfds, MAX_POLL);
|
||||
|
||||
for (i = 0; i < this->n_pfds; i++) {
|
||||
this->sources[i].func = handle_acp_poll;
|
||||
this->sources[i].data = this;
|
||||
this->sources[i].fd = this->pfds[i].fd;
|
||||
this->sources[i].mask = this->pfds[i].events;
|
||||
this->sources[i].rmask = 0;
|
||||
spa_loop_add_source(this->loop, &this->sources[i]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int emit_node(struct impl *this, struct acp_device *dev)
|
||||
{
|
||||
struct spa_dict_item *items;
|
||||
const struct acp_dict_item *it;
|
||||
uint32_t n_items;
|
||||
char device_name[128], path[180], channels[16];
|
||||
char card_id[16], *p;
|
||||
struct spa_device_object_info info;
|
||||
struct acp_card *card = this->card;
|
||||
const char *stream, *devstr;;
|
||||
|
||||
info = SPA_DEVICE_OBJECT_INFO_INIT();
|
||||
info.type = SPA_TYPE_INTERFACE_Node;
|
||||
|
||||
if (dev->direction == ACP_DIRECTION_PLAYBACK) {
|
||||
info.factory_name = SPA_NAME_API_ALSA_PCM_SINK;
|
||||
stream = "playback";
|
||||
} else {
|
||||
info.factory_name = SPA_NAME_API_ALSA_PCM_SOURCE;
|
||||
stream = "capture";
|
||||
}
|
||||
|
||||
info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS;
|
||||
|
||||
n_items = dev->props.n_items + 5;
|
||||
items = alloca(n_items * sizeof(*items));
|
||||
|
||||
snprintf(card_id, sizeof(card), "%d", card->index);
|
||||
|
||||
devstr = dev->device_strings[0];
|
||||
p = strstr(devstr, "%f");
|
||||
if (p) {
|
||||
snprintf(device_name, sizeof(device_name), "%.*s%d%s",
|
||||
(int)SPA_PTRDIFF(p, devstr), devstr,
|
||||
card->index, p+2);
|
||||
} else {
|
||||
snprintf(device_name, sizeof(device_name), "%s", devstr);
|
||||
}
|
||||
snprintf(path, sizeof(path), "alsa:pcm:%s:%s:%s", card_id, device_name, stream);
|
||||
items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_OBJECT_PATH, path);
|
||||
items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PATH, device_name);
|
||||
items[2] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_CARD, card_id);
|
||||
items[3] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_STREAM, stream);
|
||||
|
||||
snprintf(channels, sizeof(channels), "%d", dev->format.channels);
|
||||
items[4] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_CHANNELS, channels);
|
||||
n_items = 5;
|
||||
acp_dict_for_each(it, &dev->props)
|
||||
items[n_items++] = SPA_DICT_ITEM_INIT(it->key, it->value);
|
||||
|
||||
info.props = &SPA_DICT_INIT(items, n_items);
|
||||
|
||||
spa_device_emit_object_info(&this->hooks, dev->index, &info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int emit_info(struct impl *this, bool full)
|
||||
{
|
||||
int err = 0;
|
||||
struct spa_dict_item *items;
|
||||
uint32_t n_items;
|
||||
const struct acp_dict_item *it;
|
||||
struct acp_card *card = this->card;
|
||||
char path[128];
|
||||
|
||||
if (full)
|
||||
this->info.change_mask = this->info_all;
|
||||
if (this->info.change_mask) {
|
||||
n_items = card->props.n_items + 4;
|
||||
items = alloca(n_items * sizeof(*items));
|
||||
|
||||
n_items = 0;
|
||||
#define ADD_ITEM(key, value) items[n_items++] = SPA_DICT_ITEM_INIT(key, value)
|
||||
snprintf(path, sizeof(path), "alsa:pcm:%d", card->index);
|
||||
ADD_ITEM(SPA_KEY_OBJECT_PATH, path);
|
||||
ADD_ITEM(SPA_KEY_DEVICE_API, "alsa:pcm");
|
||||
ADD_ITEM(SPA_KEY_MEDIA_CLASS, "Audio/Device");
|
||||
ADD_ITEM(SPA_KEY_API_ALSA_PATH, (char *)this->props.device);
|
||||
acp_dict_for_each(it, &card->props)
|
||||
ADD_ITEM(it->key, it->value);
|
||||
this->info.props = &SPA_DICT_INIT(items, n_items);
|
||||
#undef ADD_ITEM
|
||||
spa_device_emit_info(&this->hooks, &this->info);
|
||||
this->info.change_mask = 0;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int impl_add_listener(void *object,
|
||||
struct spa_hook *listener,
|
||||
const struct spa_device_events *events,
|
||||
void *data)
|
||||
{
|
||||
struct impl *this = object;
|
||||
struct spa_hook_list save;
|
||||
struct acp_card *card;
|
||||
struct acp_card_profile *profile;
|
||||
uint32_t i;
|
||||
|
||||
spa_return_val_if_fail(this != NULL, -EINVAL);
|
||||
spa_return_val_if_fail(events != NULL, -EINVAL);
|
||||
|
||||
card = this->card;
|
||||
profile = card->profiles[card->active_profile_index];
|
||||
|
||||
spa_hook_list_isolate(&this->hooks, &save, listener, events, data);
|
||||
|
||||
if (events->info || events->object_info)
|
||||
emit_info(this, true);
|
||||
|
||||
for (i = 0; i < profile->n_devices; i++)
|
||||
emit_node(this, profile->devices[i]);
|
||||
|
||||
spa_hook_list_join(&this->hooks, &save);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int impl_sync(void *object, int seq)
|
||||
{
|
||||
struct impl *this = object;
|
||||
|
||||
spa_return_val_if_fail(this != NULL, -EINVAL);
|
||||
|
||||
spa_device_emit_result(&this->hooks, seq, 0, 0, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spa_pod *build_profile(struct spa_pod_builder *b, uint32_t id,
|
||||
struct acp_card_profile *pr)
|
||||
{
|
||||
struct spa_pod_frame f[2];
|
||||
uint32_t i, n_classes, n_capture = 0, n_playback = 0;
|
||||
|
||||
for (i = 0; i < pr->n_devices; i++) {
|
||||
switch (pr->devices[i]->direction) {
|
||||
case ACP_DIRECTION_PLAYBACK:
|
||||
n_playback++;
|
||||
break;
|
||||
case ACP_DIRECTION_CAPTURE:
|
||||
n_capture++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
n_classes = n_capture > 0 ? 1 : 0;
|
||||
n_classes += n_playback > 0 ? 1 : 0;
|
||||
|
||||
spa_pod_builder_int(b, n_classes);
|
||||
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamProfile, id);
|
||||
spa_pod_builder_add(b,
|
||||
SPA_PARAM_PROFILE_index, SPA_POD_Int(pr->index),
|
||||
SPA_PARAM_PROFILE_name, SPA_POD_String(pr->name),
|
||||
SPA_PARAM_PROFILE_description, SPA_POD_String(pr->description),
|
||||
SPA_PARAM_PROFILE_priority, SPA_POD_Int(pr->priority),
|
||||
SPA_PARAM_PROFILE_available, SPA_POD_Id(pr->available),
|
||||
0);
|
||||
spa_pod_builder_prop(b, SPA_PARAM_PROFILE_classes, 0);
|
||||
spa_pod_builder_push_struct(b, &f[1]);
|
||||
spa_pod_builder_int(b, n_classes);
|
||||
if (n_capture > 0) {
|
||||
spa_pod_builder_add_struct(b,
|
||||
SPA_POD_String("Audio/Source"),
|
||||
SPA_POD_Int(n_capture));
|
||||
}
|
||||
if (n_playback > 0) {
|
||||
spa_pod_builder_add_struct(b,
|
||||
SPA_POD_String("Audio/Sink"),
|
||||
SPA_POD_Int(n_playback));
|
||||
}
|
||||
spa_pod_builder_pop(b, &f[1]);
|
||||
return spa_pod_builder_pop(b, &f[0]);
|
||||
}
|
||||
|
||||
static struct spa_pod *build_route(struct spa_pod_builder *b, uint32_t id,
|
||||
struct acp_port *p, struct acp_device *dev)
|
||||
{
|
||||
struct spa_pod_frame f[2];
|
||||
const struct acp_dict_item *item;
|
||||
uint32_t i;
|
||||
enum spa_direction direction;
|
||||
|
||||
switch (p->direction) {
|
||||
case ACP_DIRECTION_PLAYBACK:
|
||||
direction = SPA_DIRECTION_OUTPUT;
|
||||
break;
|
||||
case ACP_DIRECTION_CAPTURE:
|
||||
direction = SPA_DIRECTION_INPUT;
|
||||
break;
|
||||
default:
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamRoute, id);
|
||||
spa_pod_builder_add(b,
|
||||
SPA_PARAM_ROUTE_index, SPA_POD_Int(p->index),
|
||||
SPA_PARAM_ROUTE_direction, SPA_POD_Id(direction),
|
||||
SPA_PARAM_ROUTE_name, SPA_POD_String(p->name),
|
||||
SPA_PARAM_ROUTE_description, SPA_POD_String(p->description),
|
||||
SPA_PARAM_ROUTE_priority, SPA_POD_Int(p->priority),
|
||||
SPA_PARAM_ROUTE_available, SPA_POD_Id(p->available),
|
||||
0);
|
||||
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_info, 0);
|
||||
spa_pod_builder_push_struct(b, &f[1]);
|
||||
spa_pod_builder_int(b, p->props.n_items);
|
||||
acp_dict_for_each(item, &p->props) {
|
||||
spa_pod_builder_add(b,
|
||||
SPA_POD_String(item->key),
|
||||
SPA_POD_String(item->value),
|
||||
NULL);
|
||||
}
|
||||
spa_pod_builder_pop(b, &f[1]);
|
||||
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profiles, 0);
|
||||
spa_pod_builder_push_array(b, &f[1]);
|
||||
for (i = 0; i < p->n_profiles; i++)
|
||||
spa_pod_builder_int(b, p->profiles[i]->index);
|
||||
spa_pod_builder_pop(b, &f[1]);
|
||||
if (dev != NULL) {
|
||||
uint32_t channels = dev->format.channels;
|
||||
float volumes[channels];
|
||||
bool mute;
|
||||
|
||||
|
||||
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_device, 0);
|
||||
spa_pod_builder_int(b, dev->index);
|
||||
|
||||
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_props, 0);
|
||||
spa_pod_builder_push_object(b, &f[1], SPA_TYPE_OBJECT_Props, id);
|
||||
if (SPA_FLAG_IS_SET(dev->flags, ACP_DEVICE_HW_MUTE)) {
|
||||
acp_device_get_mute(dev, &mute);
|
||||
spa_pod_builder_add(b,
|
||||
SPA_PROP_mute, SPA_POD_Bool(mute),
|
||||
0);
|
||||
}
|
||||
if (SPA_FLAG_IS_SET(dev->flags, ACP_DEVICE_HW_VOLUME)) {
|
||||
spa_zero(volumes);
|
||||
acp_device_get_volume(dev, volumes, channels);
|
||||
spa_pod_builder_add(b,
|
||||
SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float),
|
||||
SPA_TYPE_Float, channels, volumes),
|
||||
0);
|
||||
}
|
||||
spa_pod_builder_pop(b, &f[1]);
|
||||
}
|
||||
return spa_pod_builder_pop(b, &f[0]);
|
||||
}
|
||||
|
||||
static struct acp_port *find_port_for_device(struct acp_card *card, struct acp_device *dev)
|
||||
{
|
||||
uint32_t i;
|
||||
for (i = 0; i < dev->n_ports; i++) {
|
||||
struct acp_port *p = dev->ports[i];
|
||||
if (SPA_FLAG_IS_SET(p->flags, ACP_PORT_ACTIVE))
|
||||
return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int impl_enum_params(void *object, int seq,
|
||||
uint32_t id, uint32_t start, uint32_t num,
|
||||
const struct spa_pod *filter)
|
||||
{
|
||||
struct impl *this = object;
|
||||
struct spa_pod *param;
|
||||
struct spa_pod_builder b = { 0 };
|
||||
uint8_t buffer[4096];
|
||||
struct spa_result_device_params result;
|
||||
uint32_t count = 0;
|
||||
struct acp_card *card;
|
||||
struct acp_card_profile *pr;
|
||||
struct acp_port *p;
|
||||
struct acp_device *dev;
|
||||
|
||||
spa_return_val_if_fail(this != NULL, -EINVAL);
|
||||
spa_return_val_if_fail(num != 0, -EINVAL);
|
||||
|
||||
card = this->card;
|
||||
|
||||
result.id = id;
|
||||
result.next = start;
|
||||
next:
|
||||
result.index = result.next++;
|
||||
|
||||
spa_pod_builder_init(&b, buffer, sizeof(buffer));
|
||||
|
||||
switch (id) {
|
||||
case SPA_PARAM_EnumProfile:
|
||||
if (result.index >= card->n_profiles)
|
||||
return 0;
|
||||
|
||||
pr = card->profiles[result.index];
|
||||
param = build_profile(&b, id, pr);
|
||||
break;
|
||||
|
||||
case SPA_PARAM_Profile:
|
||||
if (result.index > 0)
|
||||
return 0;
|
||||
|
||||
pr = card->profiles[card->active_profile_index];
|
||||
param = build_profile(&b, id, pr);
|
||||
break;
|
||||
|
||||
case SPA_PARAM_EnumRoute:
|
||||
if (result.index >= card->n_ports)
|
||||
return 0;
|
||||
|
||||
p = card->ports[result.index];
|
||||
param = build_route(&b, id, p, NULL);
|
||||
break;
|
||||
|
||||
case SPA_PARAM_Route:
|
||||
while (true) {
|
||||
if (result.index >= card->n_devices)
|
||||
return 0;
|
||||
|
||||
dev = card->devices[result.index];
|
||||
if (SPA_FLAG_IS_SET(dev->flags, ACP_DEVICE_ACTIVE))
|
||||
break;
|
||||
|
||||
result.index++;
|
||||
}
|
||||
p = find_port_for_device(card, dev);
|
||||
if (p == NULL)
|
||||
return 0;
|
||||
result.next = result.index + 1;
|
||||
param = build_route(&b, id, p, dev);
|
||||
if (param == NULL)
|
||||
return -errno;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (spa_pod_filter(&b, &result.param, param, filter) < 0)
|
||||
goto next;
|
||||
|
||||
spa_device_emit_result(&this->hooks, seq, 0,
|
||||
SPA_RESULT_TYPE_DEVICE_PARAMS, &result);
|
||||
|
||||
if (++count != num)
|
||||
goto next;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apply_device_props(struct impl *this, struct acp_device *dev, struct spa_pod *props)
|
||||
{
|
||||
float volume = 0;
|
||||
bool mute = 0;
|
||||
struct spa_pod_prop *prop;
|
||||
struct spa_pod_object *obj = (struct spa_pod_object *) props;
|
||||
int changed = 0;
|
||||
|
||||
if (!spa_pod_is_object_type(props, SPA_TYPE_OBJECT_Props))
|
||||
return -EINVAL;
|
||||
|
||||
SPA_POD_OBJECT_FOREACH(obj, prop) {
|
||||
switch (prop->key) {
|
||||
case SPA_PROP_volume:
|
||||
if (spa_pod_get_float(&prop->value, &volume) == 0) {
|
||||
acp_device_set_volume(dev, &volume, 1);
|
||||
changed++;
|
||||
}
|
||||
break;
|
||||
case SPA_PROP_mute:
|
||||
if (spa_pod_get_bool(&prop->value, &mute) == 0) {
|
||||
acp_device_set_mute(dev, mute);
|
||||
changed++;
|
||||
}
|
||||
break;
|
||||
case SPA_PROP_channelVolumes:
|
||||
{
|
||||
float volumes[64];
|
||||
uint32_t n_volumes;
|
||||
|
||||
if ((n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float,
|
||||
volumes, 64)) > 0) {
|
||||
acp_device_set_volume(dev, volumes, n_volumes);
|
||||
changed++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int impl_set_param(void *object,
|
||||
uint32_t id, uint32_t flags,
|
||||
const struct spa_pod *param)
|
||||
{
|
||||
struct impl *this = object;
|
||||
int res;
|
||||
|
||||
spa_return_val_if_fail(this != NULL, -EINVAL);
|
||||
|
||||
switch (id) {
|
||||
case SPA_PARAM_Profile:
|
||||
{
|
||||
uint32_t id;
|
||||
|
||||
if ((res = spa_pod_parse_object(param,
|
||||
SPA_TYPE_OBJECT_ParamProfile, NULL,
|
||||
SPA_PARAM_PROFILE_index, SPA_POD_Int(&id))) < 0) {
|
||||
spa_log_warn(this->log, "can't parse profile");
|
||||
spa_debug_pod(0, NULL, param);
|
||||
return res;
|
||||
}
|
||||
|
||||
res = acp_card_set_profile(this->card, id);
|
||||
break;
|
||||
}
|
||||
case SPA_PARAM_Route:
|
||||
{
|
||||
uint32_t id, device;
|
||||
struct spa_pod *props = NULL;
|
||||
struct acp_device *dev;
|
||||
|
||||
if ((res = spa_pod_parse_object(param,
|
||||
SPA_TYPE_OBJECT_ParamRoute, NULL,
|
||||
SPA_PARAM_ROUTE_index, SPA_POD_Int(&id),
|
||||
SPA_PARAM_ROUTE_device, SPA_POD_Int(&device),
|
||||
SPA_PARAM_ROUTE_props, SPA_POD_OPT_Pod(&props))) < 0) {
|
||||
spa_log_warn(this->log, "can't parse route");
|
||||
spa_debug_pod(0, NULL, param);
|
||||
return res;
|
||||
}
|
||||
if (device >= this->card->n_devices)
|
||||
return -EINVAL;
|
||||
|
||||
dev = this->card->devices[device];
|
||||
res = acp_device_set_port(dev, id);
|
||||
if (props)
|
||||
apply_device_props(this, dev, props);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spa_device_methods impl_device = {
|
||||
SPA_VERSION_DEVICE_METHODS,
|
||||
.add_listener = impl_add_listener,
|
||||
.sync = impl_sync,
|
||||
.enum_params = impl_enum_params,
|
||||
.set_param = impl_set_param,
|
||||
};
|
||||
|
||||
static void card_props_changed(void *data)
|
||||
{
|
||||
struct impl *this = data;
|
||||
spa_log_info(this->log, "card properties changed");
|
||||
}
|
||||
|
||||
static bool has_device(struct acp_card_profile *pr, uint32_t index)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < pr->n_devices; i++)
|
||||
if (pr->devices[i]->index == index)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void card_profile_changed(void *data, uint32_t old_index, uint32_t new_index)
|
||||
{
|
||||
struct impl *this = data;
|
||||
struct acp_card *card = this->card;
|
||||
struct acp_card_profile *op = card->profiles[old_index];
|
||||
struct acp_card_profile *np = card->profiles[new_index];
|
||||
uint32_t i;
|
||||
|
||||
spa_log_info(this->log, "card profile changed from %s to %s",
|
||||
op->name, np->name);
|
||||
|
||||
for (i = 0; i < op->n_devices; i++) {
|
||||
uint32_t index = op->devices[i]->index;
|
||||
if (has_device(np, index))
|
||||
continue;
|
||||
spa_device_emit_object_info(&this->hooks, index, NULL);
|
||||
}
|
||||
for (i = 0; i < np->n_devices; i++) {
|
||||
emit_node(this, np->devices[i]);
|
||||
}
|
||||
setup_sources(this);
|
||||
|
||||
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
|
||||
this->params[1].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||
emit_info(this, false);
|
||||
}
|
||||
|
||||
static void card_profile_available(void *data, uint32_t index,
|
||||
enum acp_available old, enum acp_available available)
|
||||
{
|
||||
struct impl *this = data;
|
||||
struct acp_card *card = this->card;
|
||||
struct acp_card_profile *p = card->profiles[index];
|
||||
spa_log_info(this->log, "card profile %s available %d", p->name, available);
|
||||
|
||||
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
|
||||
this->params[0].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||
emit_info(this, false);
|
||||
}
|
||||
|
||||
static void card_port_available(void *data, uint32_t index,
|
||||
enum acp_available old, enum acp_available available)
|
||||
{
|
||||
struct impl *this = data;
|
||||
struct acp_card *card = this->card;
|
||||
struct acp_port *p = card->ports[index];
|
||||
spa_log_info(this->log, "card port %s available %d", p->name, available);
|
||||
|
||||
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
|
||||
this->params[2].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||
emit_info(this, false);
|
||||
}
|
||||
|
||||
static void on_volume_changed(void *data, struct acp_device *dev)
|
||||
{
|
||||
struct impl *this = data;
|
||||
spa_log_info(this->log, "device %s volume changed", dev->name);
|
||||
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
|
||||
this->params[3].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||
emit_info(this, false);
|
||||
}
|
||||
|
||||
static void on_mute_changed(void *data, struct acp_device *dev)
|
||||
{
|
||||
struct impl *this = data;
|
||||
spa_log_debug(this->log, "device %s mute changed", dev->name);
|
||||
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
|
||||
this->params[3].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||
emit_info(this, false);
|
||||
}
|
||||
|
||||
struct acp_card_events card_events = {
|
||||
ACP_VERSION_CARD_EVENTS,
|
||||
.props_changed = card_props_changed,
|
||||
.profile_changed = card_profile_changed,
|
||||
.profile_available = card_profile_available,
|
||||
.port_available = card_port_available,
|
||||
.volume_changed = on_volume_changed,
|
||||
.mute_changed = on_mute_changed,
|
||||
};
|
||||
|
||||
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
|
||||
{
|
||||
struct impl *this;
|
||||
|
||||
spa_return_val_if_fail(handle != NULL, -EINVAL);
|
||||
spa_return_val_if_fail(interface != NULL, -EINVAL);
|
||||
|
||||
this = (struct impl *) handle;
|
||||
|
||||
if (strcmp(type, SPA_TYPE_INTERFACE_Device) == 0)
|
||||
*interface = &this->device;
|
||||
else
|
||||
return -ENOENT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SPA_PRINTF_FUNC(6,0) void impl_acp_log_func(void *data,
|
||||
int level, const char *file, int line, const char *func,
|
||||
const char *fmt, va_list arg)
|
||||
{
|
||||
struct impl *this = data;
|
||||
spa_log_logv(this->log, (enum spa_log_level)level, file, line, func, fmt, arg);
|
||||
}
|
||||
|
||||
static int impl_clear(struct spa_handle *handle)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t
|
||||
impl_get_size(const struct spa_handle_factory *factory,
|
||||
const struct spa_dict *params)
|
||||
{
|
||||
return sizeof(struct impl);
|
||||
}
|
||||
|
||||
static int
|
||||
impl_init(const struct spa_handle_factory *factory,
|
||||
struct spa_handle *handle,
|
||||
const struct spa_dict *info,
|
||||
const struct spa_support *support,
|
||||
uint32_t n_support)
|
||||
{
|
||||
struct impl *this;
|
||||
const char *str;
|
||||
|
||||
spa_return_val_if_fail(factory != NULL, -EINVAL);
|
||||
spa_return_val_if_fail(handle != NULL, -EINVAL);
|
||||
|
||||
handle->get_interface = impl_get_interface;
|
||||
handle->clear = impl_clear;
|
||||
|
||||
this = (struct impl *) handle;
|
||||
|
||||
this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
|
||||
this->loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop);
|
||||
if (this->loop == NULL) {
|
||||
spa_log_error(this->log, "a Loop interface is needed");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
acp_set_log_func(impl_acp_log_func, this);
|
||||
acp_set_log_level(6);
|
||||
|
||||
this->device.iface = SPA_INTERFACE_INIT(
|
||||
SPA_TYPE_INTERFACE_Device,
|
||||
SPA_VERSION_DEVICE,
|
||||
&impl_device, this);
|
||||
spa_hook_list_init(&this->hooks);
|
||||
|
||||
reset_props(&this->props);
|
||||
|
||||
if (info && (str = spa_dict_lookup(info, SPA_KEY_API_ALSA_PATH)))
|
||||
snprintf(this->props.device, 64, "%s", str);
|
||||
|
||||
spa_log_debug(this->log, "probe card %s", this->props.device);
|
||||
this->card = acp_card_new(atoi(this->props.device+3), NULL);
|
||||
if (this->card == NULL)
|
||||
return -errno;
|
||||
|
||||
setup_sources(this);
|
||||
|
||||
acp_card_add_listener(this->card, &card_events, this);
|
||||
|
||||
this->info = SPA_DEVICE_INFO_INIT();
|
||||
this->info_all = SPA_DEVICE_CHANGE_MASK_PROPS |
|
||||
SPA_DEVICE_CHANGE_MASK_PARAMS;
|
||||
|
||||
this->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumProfile, SPA_PARAM_INFO_READ);
|
||||
this->params[1] = SPA_PARAM_INFO(SPA_PARAM_Profile, SPA_PARAM_INFO_READWRITE);
|
||||
this->params[2] = SPA_PARAM_INFO(SPA_PARAM_EnumRoute, SPA_PARAM_INFO_READ);
|
||||
this->params[3] = SPA_PARAM_INFO(SPA_PARAM_Route, SPA_PARAM_INFO_READWRITE);
|
||||
this->info.params = this->params;
|
||||
this->info.n_params = 4;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spa_interface_info impl_interfaces[] = {
|
||||
{SPA_TYPE_INTERFACE_Device,},
|
||||
};
|
||||
|
||||
static int
|
||||
impl_enum_interface_info(const struct spa_handle_factory *factory,
|
||||
const struct spa_interface_info **info,
|
||||
uint32_t *index)
|
||||
{
|
||||
spa_return_val_if_fail(factory != NULL, -EINVAL);
|
||||
spa_return_val_if_fail(info != NULL, -EINVAL);
|
||||
spa_return_val_if_fail(index != NULL, -EINVAL);
|
||||
|
||||
if (*index >= SPA_N_ELEMENTS(impl_interfaces))
|
||||
return 0;
|
||||
|
||||
*info = &impl_interfaces[(*index)++];
|
||||
return 1;
|
||||
}
|
||||
|
||||
const struct spa_handle_factory spa_alsa_acp_device_factory = {
|
||||
SPA_VERSION_HANDLE_FACTORY,
|
||||
SPA_NAME_API_ALSA_ACP_DEVICE,
|
||||
NULL,
|
||||
impl_get_size,
|
||||
impl_init,
|
||||
impl_enum_interface_info,
|
||||
};
|
||||
|
|
@ -31,6 +31,7 @@ extern const struct spa_handle_factory spa_alsa_sink_factory;
|
|||
extern const struct spa_handle_factory spa_alsa_udev_factory;
|
||||
extern const struct spa_handle_factory spa_alsa_device_factory;
|
||||
extern const struct spa_handle_factory spa_alsa_seq_bridge_factory;
|
||||
extern const struct spa_handle_factory spa_alsa_acp_device_factory;
|
||||
|
||||
SPA_EXPORT
|
||||
int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index)
|
||||
|
|
@ -54,6 +55,9 @@ int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t
|
|||
case 4:
|
||||
*factory = &spa_alsa_seq_bridge_factory;
|
||||
break;
|
||||
case 5:
|
||||
*factory = &spa_alsa_acp_device_factory;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
subdir('acp')
|
||||
|
||||
spa_alsa_sources = ['alsa.c',
|
||||
'alsa-udev.c',
|
||||
'alsa-acp-device.c',
|
||||
'alsa-pcm-device.c',
|
||||
'alsa-pcm-sink.c',
|
||||
'alsa-pcm-source.c',
|
||||
|
|
@ -7,9 +10,13 @@ spa_alsa_sources = ['alsa.c',
|
|||
'alsa-seq-source.c',
|
||||
'alsa-seq.c']
|
||||
|
||||
spa_alsa = shared_library('spa-alsa',
|
||||
spa_alsa_sources,
|
||||
include_directories : [spa_inc],
|
||||
dependencies : [ alsa_dep, libudev_dep, mathlib, ],
|
||||
install : true,
|
||||
install_dir : join_paths(spa_plugindir, 'alsa'))
|
||||
spa_alsa = shared_library(
|
||||
'spa-alsa',
|
||||
[ spa_alsa_sources ],
|
||||
c_args : acp_c_args,
|
||||
include_directories : [spa_inc, configinc],
|
||||
dependencies : [ alsa_dep, libudev_dep, mathlib ],
|
||||
link_with : [ acp_lib ],
|
||||
install : true,
|
||||
install_dir : join_paths(spa_plugindir, 'alsa')
|
||||
)
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ static struct node *alsa_create_node(struct device *device, uint32_t id,
|
|||
struct node *node;
|
||||
struct impl *impl = device->impl;
|
||||
int res;
|
||||
const char *dev, *subdev, *stream;
|
||||
const char *dev, *subdev, *stream, *profile;
|
||||
int priority;
|
||||
|
||||
pw_log_debug("new node %u", id);
|
||||
|
|
@ -210,11 +210,15 @@ static struct node *alsa_create_node(struct device *device, uint32_t id,
|
|||
pw_properties_set(node->props, PW_KEY_FACTORY_NAME, info->factory_name);
|
||||
|
||||
if ((dev = pw_properties_get(node->props, SPA_KEY_API_ALSA_PCM_DEVICE)) == NULL)
|
||||
dev = "0";
|
||||
if ((dev = pw_properties_get(node->props, "alsa.device")) == NULL)
|
||||
dev = "0";
|
||||
if ((subdev = pw_properties_get(node->props, SPA_KEY_API_ALSA_PCM_SUBDEVICE)) == NULL)
|
||||
subdev = "0";
|
||||
if ((subdev = pw_properties_get(node->props, "alsa.subdevice")) == NULL)
|
||||
subdev = "0";
|
||||
if ((stream = pw_properties_get(node->props, SPA_KEY_API_ALSA_PCM_STREAM)) == NULL)
|
||||
stream = "unknown";
|
||||
if ((profile = pw_properties_get(node->props, "device.profile.name")) == NULL)
|
||||
profile = "unknown";
|
||||
|
||||
if (!strcmp(stream, "capture"))
|
||||
node->direction = PW_DIRECTION_OUTPUT;
|
||||
|
|
@ -233,6 +237,11 @@ static struct node *alsa_create_node(struct device *device, uint32_t id,
|
|||
priority -= atol(dev) * 16;
|
||||
priority -= atol(subdev);
|
||||
|
||||
if (strstr(profile, "analog-") == profile)
|
||||
priority += 9;
|
||||
else if (strstr(profile, "iec958-") == profile)
|
||||
priority += 8;
|
||||
|
||||
if (pw_properties_get(node->props, PW_KEY_PRIORITY_MASTER) == NULL) {
|
||||
pw_properties_setf(node->props, PW_KEY_PRIORITY_MASTER, "%d", priority);
|
||||
pw_properties_setf(node->props, PW_KEY_PRIORITY_SESSION, "%d", priority);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue