Linux ALSA driver quatro: Controlar a análise do código-fonte do processo de criação do dispositivo (5.18)

        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:

Acho que você gosta

Origin blog.csdn.net/code_lyb/article/details/126165827
Recomendado
Clasificación