A interface de controle permite principalmente que aplicativos de espaço de usuário ( alsa-lib
) acessem e controlem os interruptores multidirecionais e controles deslizantes no chip de codec de áudio . Para Mixer
(mixagem), a interface de controle é particularmente importante. A partir de ALSA 0.9.x, todo o trabalho do mixer é implementado através da API da interface de controle.
A ALSA definiu um modelo de interface de controle completo para AC97, se o seu chip Codec suporta apenas a interface AC97, você não precisa se preocupar com o conteúdo desta seção.
<sound/control.h>
Define todas as APIs de controle. Se você for implementar seus próprios controles para seu codec, inclua este arquivo de cabeçalho em seu código.
1、snd_kcontrol_new
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
const char *name; /* ASCII name of item */
unsigned int index; /* index of item */
unsigned int access; /* access rights */
unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
};
iface: Indica o tipo de controle, definido por SNDRV_CTL_ELEM_IFACE_XXX. Normalmente MIXER é usado, e também pode ser definido como um tipo de CARD global. Se for definido como um tipo de dispositivo Morey, como HWDEP, PCMRAWMIDI, TIMER, etc., o número lógico do dispositivo do cartão deve ser pago no campos de dispositivo e subdispositivo.
name: Indica o nome do controle. A camada de usuário pode acessar o controle através deste nome, que será discutido em detalhes mais adiante
index: armazena o número do índice deste controle. Se houver mais de um codec na placa de som. Cada codec possui um controle com o mesmo nome. Neste momento, é necessário distinguir estes controles por índice, quando o índice for 0, esta estratégia de distinção pode ser ignorada .
acesso: controle de acesso, READ, WRITE, READWRITE, etc. Cada bit representa um tipo de acesso e esses tipos de acesso podem ser usados em combinação com várias operações OR.
private_value: contém um valor inteiro longo de uma pessoa, que pode ser acessado por meio das funções de callback info, get e put.
tlv: Este campo fornece metadados para o controle.
2. O nome do controle
O nome do controle precisa seguir alguns padrões, geralmente pode ser dividido em 3 partes para definir o nome do controle: 源--方向--功能
.
Fonte: Pode ser entendida como o terminal de entrada do controle.Alsa tem pré-definidas algumas fontes comumente utilizadas, tais como: Master, PCM, CD, Linha e assim por diante.
Direção: Representa a direção do fluxo de dados do controle, como: Playback, Captura, Bypass, Bypass Captura, etc., ou não há direção definida, o que significa que o Controle é bidirecional (playback e captura).
Função: De acordo com a função do controle, podem ser as seguintes strings: Chave, Volume, Rota, etc.
Há também algumas exceções de nomenclatura:
1. Captura e reprodução globais: "Capture Source", "Capture Volume", "Capture Switch", são usados para fonte de captura global, switch e volume. O mesmo "Volume de reprodução", "Interruptor de reprodução", são usados para o interruptor de saída global e volume.
2. Controles de tom: A chave e o volume do controle de tom são nomeados: Tomw Control-XXX, por exemplo, "Tone-Control-Switch", "Tone Control-Bass", "Tone Control-Center" .
3、Controles 3D:3D控件的命名规则:“3D Control-Switch”,“3D Control-Center”,“3D Control-Space”。
4. MIC Boost: O espaço para aumentar o volume do microfone é denominado: "MIC Boost" ou "MIC Bosst (6dB)".
3. Sinalizadores de acesso (sinalizadores de ACESSO)
O campo Acesso é uma máscara de bits que armazena o tipo de acesso do controle. O tipo de acesso padrão é: SNDRV_CTL_ELEM_ACCESS_READWRITE, indicando que o controle oferece suporte a operações de leitura e gravação. Se o campo de acesso não estiver definido (.access==0), também é considerado do tipo READWRITE.
Se for um controle somente leitura, o acesso deve ser configurado para: SNDRV_CTL_ELEM_ACCESS_READ, neste momento, não precisamos definir a função put callback. Da mesma forma, se for um controle somente gravação, o acesso deve ser definido como: SNDRV_CTL_ELEM_ACCESS_WRITE, neste momento, não precisamos definir a função get callback.
Se o valor do controle mudar com frequência (por exemplo: um medidor de nível), podemos usar o tipo VOLATILE, o que significa que o controle mudará sem notificação, e o aplicativo deve consultar o valor do controle periodicamente.
4. Metadados (METADADOS)
Muitos controles de mixer precisam fornecer informações em dB. Podemos usar macros DECLARE_TLV_xxx para definir algumas variáveis que contêm essas informações e, em seguida, apontar o campo tlv.p do controle para essas variáveis. Finalmente, adicione o sinalizador SNDRV_CTL_ELEM_ACCESS_TLV_READ ao campo de acesso. Por exemplo:
static const DECLARE_TLV_DB_SCALE(snd_cx88_db_scale, -6300, 100, 0);
static const struct snd_kcontrol_new snd_cx88_volume = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
.name = "Analog-TV Volume",
.info = snd_cx88_volume_info,
.get = snd_cx88_volume_get,
.put = snd_cx88_volume_put,
.tlv.p = snd_cx88_db_scale,
};
5. Detalhes da função
5.1, função snd_ctl_new1
/**
* snd_ctl_new1 - create a control instance from the template
* @ncontrol: the initialization record
* @private_data: the private data to set
*
* Allocates a new struct snd_kcontrol instance and initialize from the given
* template. When the access field of ncontrol is 0, it's assumed as
* READWRITE access. When the count field is 0, it's assumes as one.
*
* Return: The pointer of the newly generated instance, or %NULL on failure.
*/
struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
void *private_data)
{
struct snd_kcontrol *kctl;
unsigned int count;
unsigned int access;
int err;
if (snd_BUG_ON(!ncontrol || !ncontrol->info))
return NULL;
count = ncontrol->count;
if (count == 0)
count = 1;
access = ncontrol->access;
if (access == 0)
access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_VOLATILE |
SNDRV_CTL_ELEM_ACCESS_INACTIVE |
SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND |
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK |
SNDRV_CTL_ELEM_ACCESS_LED_MASK |
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK);
/* 创建snd_kcontrol */
err = snd_ctl_new(&kctl, count, access, NULL);
if (err < 0)
return NULL;
/* 根据snd_kcontrol_new初始化snd_kcontrol */
/* The 'numid' member is decided when calling snd_ctl_add(). */
kctl->id.iface = ncontrol->iface;
kctl->id.device = ncontrol->device;
kctl->id.subdevice = ncontrol->subdevice;
if (ncontrol->name) {
strscpy(kctl->id.name, ncontrol->name, sizeof(kctl->id.name));
if (strcmp(ncontrol->name, kctl->id.name) != 0)
pr_warn("ALSA: Control name '%s' truncated to '%s'\n",
ncontrol->name, kctl->id.name);
}
kctl->id.index = ncontrol->index;
kctl->info = ncontrol->info;
kctl->get = ncontrol->get;
kctl->put = ncontrol->put;
kctl->tlv.p = ncontrol->tlv.p;
kctl->private_value = ncontrol->private_value;
kctl->private_data = private_data;
return kctl;
}
Aloque uma nova instância de snd_kcontrol e copie o valor correspondente em my_control para esta instância, assim, ao definir my_control, geralmente podemos adicionar o prefixo de __devinitdata. snd_ctl_add vincula o controle à placa de objeto da placa de som.
struct snd_kcontrol {
struct list_head list; /* list of controls */
struct snd_ctl_elem_id id;
unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
void *private_data;
void (*private_free)(struct snd_kcontrol *kcontrol);
struct snd_kcontrol_volatile vd[]; /* volatile data */
};
#define snd_kcontrol(n) list_entry(n, struct snd_kcontrol, list)
5.2, função snd_ctl_add
/* add/replace a new kcontrol object; call with card->controls_rwsem locked */
static int __snd_ctl_add_replace(struct snd_card *card,
struct snd_kcontrol *kcontrol,
enum snd_ctl_add_mode mode)
{
struct snd_ctl_elem_id id;
unsigned int idx;
struct snd_kcontrol *old;
int err;
id = kcontrol->id;
if (id.index > UINT_MAX - kcontrol->count)
return -EINVAL;
old = snd_ctl_find_id(card, &id);
if (!old) {
if (mode == CTL_REPLACE)
return -EINVAL;
} else {
if (mode == CTL_ADD_EXCLUSIVE) {
dev_err(card->dev,
"control %i:%i:%i:%s:%i is already present\n",
id.iface, id.device, id.subdevice, id.name,
id.index);
return -EBUSY;
}
err = snd_ctl_remove(card, old);
if (err < 0)
return err;
}
if (snd_ctl_find_hole(card, kcontrol->count) < 0)
return -ENOMEM;
/* 把snd_kcontrol挂入snd_card的controls链表 */
list_add_tail(&kcontrol->list, &card->controls);
card->controls_count += kcontrol->count;
/* 设置元素ID */
kcontrol->id.numid = card->last_numid + 1;
card->last_numid += kcontrol->count;
for (idx = 0; idx < kcontrol->count; idx++)
snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_ADD, kcontrol, idx);
return 0;
}
static int snd_ctl_add_replace(struct snd_card *card,
struct snd_kcontrol *kcontrol,
enum snd_ctl_add_mode mode)
{
int err = -EINVAL;
if (! kcontrol)
return err;
if (snd_BUG_ON(!card || !kcontrol->info))
goto error;
down_write(&card->controls_rwsem);
err = __snd_ctl_add_replace(card, kcontrol, mode);
up_write(&card->controls_rwsem);
if (err < 0)
goto error;
return 0;
error:
snd_ctl_free_one(kcontrol);
return err;
}
/**
* snd_ctl_add - add the control instance to the card
* @card: the card instance
* @kcontrol: the control instance to add
*
* Adds the control instance created via snd_ctl_new() or
* snd_ctl_new1() to the given card. Assigns also an unique
* numid used for fast search.
*
* It frees automatically the control which cannot be added.
*
* Return: Zero if successful, or a negative error code on failure.
*
*/
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
{
return snd_ctl_add_replace(card, kcontrol, CTL_ADD_EXCLUSIVE);
}
5.3, função de retorno de chamada de informações
Para obter as informações detalhadas do controle correspondente, as informações precisam ser armazenadas no objeto snd_ctl_elem_info.
struct snd_ctl_elem_info {
struct snd_ctl_elem_id id; /* W: element ID */
snd_ctl_elem_type_t type; /* R: value type - SNDRV_CTL_ELEM_TYPE_* */
unsigned int access; /* R: value access (bitmask) - SNDRV_CTL_ELEM_ACCESS_* */
unsigned int count; /* count of values */
__kernel_pid_t owner; /* owner's PID of this control */
union {
struct {
long min; /* R: minimum value */
long max; /* R: maximum value */
long step; /* R: step (0 variable) */
} integer;
struct {
long long min; /* R: minimum value */
long long max; /* R: maximum value */
long long step; /* R: step (0 variable) */
} integer64;
struct {
unsigned int items; /* R: number of items */
unsigned int item; /* W: item number */
char name[64]; /* R: value name */
__u64 names_ptr; /* W: names list (ELEM_ADD only) */
unsigned int names_length;
} enumerated;
unsigned char reserved[128];
} value;
unsigned char reserved[64];
};
O valor nele é uma união e o tipo do valor precisa ser determinado de acordo com o tipo do controle. O tipo de controle inclui os seguintes tipos:
typedef int __bitwise snd_ctl_elem_type_t;
#define SNDRV_CTL_ELEM_TYPE_NONE ((__force snd_ctl_elem_type_t) 0) /* invalid */
#define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((__force snd_ctl_elem_type_t) 1) /* boolean type */
#define SNDRV_CTL_ELEM_TYPE_INTEGER ((__force snd_ctl_elem_type_t) 2) /* integer type */
#define SNDRV_CTL_ELEM_TYPE_ENUMERATED ((__force snd_ctl_elem_type_t) 3) /* enumerated type */
#define SNDRV_CTL_ELEM_TYPE_BYTES ((__force snd_ctl_elem_type_t) 4) /* byte array */
#define SNDRV_CTL_ELEM_TYPE_IEC958 ((__force snd_ctl_elem_type_t) 5) /* IEC958 (S/PDIF) setup */
#define SNDRV_CTL_ELEM_TYPE_INTEGER64 ((__force snd_ctl_elem_type_t) 6) /* 64-bit integer type */
#define SNDRV_CTL_ELEM_TYPE_LAST SNDRV_CTL_ELEM_TYPE_INTEGER64
A seguir está a função de callback info definida por SNDRV_CTL_ELEM_TYPE_INTEGER e SNDRV_CTL_ELEM_TYPE_BOOLEAN como um exemplo:
static int snd_cx88_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *info)
{
info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
info->count = 2;
info->value.integer.min = 0;
info->value.integer.max = 0x3f;
return 0;
}
static int snd_saa7134_capsrc_info(struct snd_kcontrol * kcontrol,
struct snd_ctl_elem_info * uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
5.4, obter função de retorno de chamada
Esta função é usada para ler o valor do controle atual e devolvê-lo ao espaço do usuário. O valor precisa ser colocado na estrutura snd_ctl_elem_value, que é semelhante à estrutura info. O campo de valor é uma união e está relacionado ao tipo. Se o cont de value for maior que 1, você precisa colocar todos os valores no array value[].
static int snd_cx88_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *info)
{
info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
info->count = 2;
info->value.integer.min = 0;
info->value.integer.max = 0x3f;
return 0;
}
static int snd_cx88_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *value)
{
struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
struct cx88_core *core = chip->core;
int vol = 0x3f - (cx_read(AUD_VOL_CTL) & 0x3f),
bal = cx_read(AUD_BAL_CTL);
value->value.integer.value[(bal & 0x40) ? 0 : 1] = vol;
vol -= (bal & 0x3f);
value->value.integer.value[(bal & 0x40) ? 1 : 0] = vol < 0 ? 0 : vol;
return 0;
}
5.5, coloque a função de retorno de chamada
A função de retorno de chamada é usada para definir o valor de controle do aplicativo no controle.
static void snd_cx88_wm8775_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *value)
{
struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
struct cx88_core *core = chip->core;
u16 left = value->value.integer.value[0];
u16 right = value->value.integer.value[1];
int v, b;
/* Pass volume & balance onto any WM8775 */
if (left >= right) {
v = left << 10;
b = left ? (0x8000 * right) / left : 0x8000;
} else {
v = right << 10;
b = right ? 0xffff - (0x8000 * left) / right : 0x8000;
}
wm8775_s_ctrl(core, V4L2_CID_AUDIO_VOLUME, v);
wm8775_s_ctrl(core, V4L2_CID_AUDIO_BALANCE, b);
}
/* OK - TODO: test it */
static int snd_cx88_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *value)
{
struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
struct cx88_core *core = chip->core;
int left, right, v, b;
int changed = 0;
u32 old;
if (core->sd_wm8775)
snd_cx88_wm8775_volume_put(kcontrol, value);
left = value->value.integer.value[0] & 0x3f;
right = value->value.integer.value[1] & 0x3f;
b = right - left;
if (b < 0) {
v = 0x3f - left;
b = (-b) | 0x40;
} else {
v = 0x3f - right;
}
/* Do we really know this will always be called with IRQs on? */
spin_lock_irq(&chip->reg_lock);
old = cx_read(AUD_VOL_CTL);
if (v != (old & 0x3f)) {
cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, (old & ~0x3f) | v);
changed = 1;
}
if ((cx_read(AUD_BAL_CTL) & 0x7f) != b) {
cx_write(AUD_BAL_CTL, b);
changed = 1;
}
spin_unlock_irq(&chip->reg_lock);
return changed;
}
6. Controle o processo de criação do dispositivo
O dispositivo de controle, como o dispositivo PCM, é um dispositivo lógico sob a placa de som. O aplicativo de espaço do usuário acessa o dispositivo de controle por meio de alsa-lib, lê ou define o estado de controle do controle, de modo a controlar o codec de áudio para executar várias operações de controle e mixagem.
O processo de criação de um dispositivo de controle é praticamente o mesmo de um dispositivo PCM. Para o processo de criação detalhado, consulte o diagrama de sequência abaixo.
Precisamos chamar ativamente a função snd_pcm_new() para criar um dispositivo pcm quando nosso driver é inicializado e o dispositivo de controle é criado em snd_ctl_new1(), e snd_ctl_new1() cria um nó de dispositivo de controle chamando a função snd_ctl_create(). Portanto, não precisamos criar explicitamente o dispositivo de controle, desde que a placa de som seja criada, o dispositivo de controle é criado automaticamente.
Assim como o dispositivo pcm, o nome do dispositivo de controle segue algumas regras: controlCxx, onde xx representa o número da placa de som.
A função snd_ctl_dev_register() será chamada em snd_card_register(), que é a fase de registro da placa de som. Depois que o registro é concluído, as informações relevantes do dispositivo de controle são armazenadas na matriz snd_minors[] e as informações relevantes podem ser encontradas na matriz snd_minors[] usando o número do dispositivo secundário do dispositivo de controle como um índice. O relacionamento da estrutura de dados após o registro pode ser expresso pela figura a seguir: