Controlador Linux ALSA cuatro: análisis del código fuente del proceso de creación de dispositivos de control (5.18)

        La interfaz de control permite principalmente que las aplicaciones del espacio del usuario ( alsa-lib) accedan y controlen los interruptores multidireccionales y los controles deslizantes en el chip del códec de audio . Para Mixer(mezclar), la interfaz de Control es particularmente importante A partir de ALSA 0.9.x, todo el trabajo de mezcla se implementa a través de la API de la interfaz de control.

        ALSA ha definido un modelo de interfaz de control completo para AC97, si su chip Codec solo es compatible con la interfaz AC97, no necesita preocuparse por el contenido de esta sección.

   <sound/control.h>Define todas las API de control. Si va a implementar sus propios controles para su códec, incluya este archivo de encabezado en su código.

1, snd_kcontrol_nuevo

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 el tipo de control, definido por SNDRV_CTL_ELEM_IFACE_XXX. Usualmente se utiliza MIXER, y también se puede definir como un tipo de TARJETA global, si se define como un tipo de dispositivo Morey, como HWDEP, PCMRAWMIDI, TIMER, etc., se debe pagar el número lógico de dispositivo de la tarjeta en el Campos de dispositivo y subdispositivo.

        nombre: Indica el nombre del control, la capa de usuario puede acceder al control a través de este nombre, el cual se discutirá en detalle más adelante

        index: almacena el número de índice de este control. Si hay más de un códec debajo de la tarjeta de sonido. Cada códec tiene un control con el mismo nombre. En este momento es necesario distinguir estos controles por índice, cuando el índice es 0 se puede ignorar esta estrategia de distinción .

        acceso: control de acceso, LEER, ESCRIBIR, LEER ESCRIBIR, etc. Cada bit representa un tipo de acceso, y estos tipos de acceso se pueden usar en combinación con múltiples operaciones OR.

        private_value: contiene un valor entero largo de una persona, al que se puede acceder a través de las funciones de devolución de llamada de info, get y put.

        tlv: este campo proporciona metadatos para el control.

2. El nombre del control

        El nombre del control debe seguir algunos estándares, por lo general se puede dividir en 3 partes para definir el nombre del control: 源--方向--功能.

        Fuente: Puede entenderse como el terminal de entrada del control, Alsa tiene predefinidas algunas fuentes de uso común, tales como: Máster, PCM, CD, Línea, etc.

        Dirección: Representa la dirección del flujo de datos del control, como: Reproducción, Captura, Bypass, Bypass Capture, etc., o no se define dirección, lo que significa que el Control es bidireccional (reproducción y captura).

        Función: Según la función del control, pueden ser las siguientes cadenas: Interruptor, Volumen, Ruta, etc.

También hay algunas excepciones de nombres:

                1. Captura y reproducción global: "Fuente de captura", "Volumen de captura", "Interruptor de captura", se utilizan para fuente de captura global, interruptor y volumen. El mismo "Volumen de reproducción", "Interruptor de reproducción", se utilizan para el interruptor de salida global y el volumen.

                2. Controles de tono: El interruptor y el volumen del control de tono se denominan: Tomw Control-XXX, por ejemplo, "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: el espacio de aumento de volumen del micrófono se llama: "MIC Boost" o "MIC Bosst (6dB)".

3. Banderas de acceso (Banderas de ACCESO)

        El campo Acceso es una máscara de bits que almacena el tipo de acceso del control. El tipo de acceso predeterminado es: SNDDRV_CTL_ELEM_ACCESS_READWRITE, lo que indica que el control admite operaciones de lectura y escritura. Si el campo de acceso no está definido (.access==0), también se considera de tipo READWRITE.

        Si es un control de solo lectura, el acceso debe establecerse en: SNDDRV_CTL_ELEM_ACCESS_READ, en este momento, no necesitamos definir la función de devolución de llamada. De manera similar, si es un control de solo escritura, el acceso debe establecerse en: SNDDRV_CTL_ELEM_ACCESS_WRITE, en este momento, no necesitamos definir la función de devolución de llamada.

        Si el valor del control cambiará con frecuencia (por ejemplo: un medidor de nivel), podemos usar el tipo VOLÁTIL, lo que significa que el control cambiará sin notificación, y la aplicación debe consultar el valor del control periódicamente.

4. Metadatos (METADATOS)

        Muchos controles de mezclador necesitan proporcionar información en dB.Podemos usar macros DECLARE_TLV_xxx para definir algunas variables que contienen esta información, y luego apuntar el campo tlv.p del control a estas variables.Finalmente, agregue la bandera SNDRV_CTL_ELEM_ACCESS_TLV_READ al campo de acceso. Por ejemplo:

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. Detalles de la función

5.1, función 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;
}

        Asigne una nueva instancia de snd_kcontrol y copie el valor correspondiente en my_control a esta instancia, por lo que, al definir my_control, generalmente podemos agregar el prefijo de __devinitdata.snd_ctl_add vincula el control a la tarjeta de objeto de la tarjeta de sonido.

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, función 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, función de devolución de llamada de información

        Para obtener la información detallada del control correspondiente, la información debe almacenarse en el 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];
};

        El valor que contiene es una unión, y el tipo de valor debe determinarse de acuerdo con el tipo de control. El tipo de control incluye los siguientes 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

        La siguiente es la función de devolución de llamada de información definida por SNDRV_CTL_ELEM_TYPE_INTEGER y SNDRV_CTL_ELEM_TYPE_BOOLEAN como ejemplo:

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, ​​obtenga la función de devolución de llamada

        Esta función se utiliza para leer el valor del control actual y devolverlo al espacio del usuario. El valor debe colocarse en la estructura snd_ctl_elem_value, que es similar a la estructura de información. El campo de valor es una unión y está relacionado con el escribe. Si el valor continuo es mayor que 1, debe colocar todos los valores en la matriz 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, poner función de devolución de llamada

         La función de devolución de llamada put se utiliza para establecer el valor de control de la aplicación en el control.

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. Proceso de creación de dispositivos de control

        El dispositivo de control, como el dispositivo PCM, es un dispositivo lógico debajo de la tarjeta de sonido. La aplicación del espacio del usuario accede al dispositivo de control a través de alsa-lib, lee o establece el estado de control del control, a fin de controlar el códec de audio para realizar varias operaciones de mezclador y otras operaciones de control.

        El proceso de creación de un dispositivo de control es aproximadamente el mismo que el de un dispositivo PCM. Para ver el proceso de creación detallado, consulte el diagrama de secuencia a continuación.

      Necesitamos llamar activamente a la función snd_pcm_new() para crear un dispositivo pcm cuando se inicializa nuestro controlador, y el dispositivo de control se crea en snd_ctl_new1(), y snd_ctl_new1() crea un nodo de dispositivo de control llamando a la función snd_ctl_create(). Por lo tanto, no necesitamos crear explícitamente el dispositivo de control, siempre que se cree la tarjeta de sonido, el dispositivo de control se crea automáticamente.

      Al igual que el dispositivo pcm, el nombre del dispositivo de control sigue ciertas reglas: controlCxx, donde xx representa el número de la tarjeta de sonido.

        La función snd_ctl_dev_register() se llamará en snd_card_register(), que es la fase de registro de la tarjeta de sonido. Una vez completado el registro, la información relevante del dispositivo de control se almacena en la matriz snd_minors[], y la información relevante se puede encontrar en la matriz snd_minors[] utilizando el número de dispositivo menor del dispositivo de control como índice. La relación de la estructura de datos después del registro se puede expresar mediante la siguiente figura:

Supongo que te gusta

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