Pilote Linux ALSA 4 : analyse du code source du processus de création de périphérique de contrôle (5.18)

        L'interface de contrôle permet principalement aux applications de l'espace utilisateur ( alsa-lib) d'accéder et de contrôler les commutateurs multidirectionnels et les commandes à glissière de la puce du codec audio . Pour Mixer(le mixage), l'interface de contrôle est particulièrement importante. À partir d'ALSA 0.9.x, tout le travail de mixage est implémenté via l' API de l'interface de contrôle.

        ALSA a défini un modèle d'interface de contrôle complet pour AC97, si votre puce Codec ne prend en charge que l'interface AC97, vous n'avez pas besoin de vous soucier du contenu de cette section.

   <sound/control.h>Définit toutes les API de contrôle. Si vous envisagez d'implémenter vos propres contrôles pour votre codec, veuillez inclure ce fichier d'en-tête dans votre code.

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 : Indique le type de contrôle, défini par SNDRV_CTL_ELEM_IFACE_XXX. Habituellement MIXER est utilisé, et il peut également être défini comme un type global de CARD. S'il est défini comme un type d'appareil Morey, tel que HWDEP, PCMRAWMIDI, TIMER, etc., le numéro logique de l'appareil de la carte doit être payé dans le champs de l'appareil et du sous-appareil.

        name : indique le nom du contrôle. La couche utilisateur peut accéder au contrôle via ce nom, qui sera détaillé ultérieurement

        index : stocke le numéro d'index de ce champ. S'il y a plus d'un codec sous la carte son. Chaque codec a une commande avec le même nom. A ce stade, il est nécessaire de distinguer ces champs par index, lorsque l'index est à 0, cette stratégie de distinction peut être ignorée .

        accès : contrôle d'accès, READ, WRITE, READWRITE, etc. Chaque bit représente un type d'accès, et ces types d'accès peuvent être utilisés en combinaison avec plusieurs opérations OU.

        valeur_privée : contient une valeur entière longue d'une personne, accessible via les fonctions de rappel info, get et put.

        tlv : ce champ fournit des métadonnées pour le contrôle.

2. Le nom du contrôle

        Le nom du contrôle doit suivre certaines normes, peut généralement être divisé en 3 parties pour définir le nom du contrôle : 源--方向--功能.

        Source : Il peut être compris comme la borne d'entrée de la commande. Alsa a prédéfini certaines sources couramment utilisées, telles que : Master, PCM, CD, Line, etc.

        Direction : Représente la direction du flux de données du contrôle, telle que : Lecture, Capture, Contournement, Contournement de la capture, etc., ou aucune direction n'est définie, ce qui signifie que le contrôle est bidirectionnel (lecture et capture).

        Fonction : Selon la fonction de la commande, il peut s'agir des chaînes suivantes : Switch, Volume, Route, etc.

Il existe également quelques exceptions de dénomination :

                1. Capture et lecture globales : "Capture Source", "Capture Volume", "Capture Switch", ils sont utilisés pour la source de capture globale, le commutateur et le volume. Le même "Volume de lecture", "Commutateur de lecture", ils sont utilisés pour le commutateur de sortie global et le volume.

                2. Commandes de tonalité : le commutateur et le volume de la commande de tonalité sont nommés : Tomw Control-XXX, par exemple, "Tone-Control-Switch", "Tone Control-Bass", "Tone Control-Center".

                3、Commandes 3D:3D控件的命名规则:“3D Control-Switch”,“3D Control-Center”,“3D Control-Space”。

                4. MIC boost : L'espace d'amplification du volume du microphone est nommé : « MIC Boost » ou « MIC Bosst (6dB) ».

3. Indicateurs d'accès (ACCESS Flags)

        Le champ Accès est un masque de bits qui stocke le type d'accès du contrôle. Le type d'accès par défaut est : SNDDRV_CTL_ELEM_ACCESS_READWRITE, indiquant que le contrôle prend en charge les opérations de lecture et d'écriture. Si le champ d'accès n'est pas défini (.access==0), il est également considéré comme étant de type READWRITE.

        S'il s'agit d'un contrôle en lecture seule, l'accès doit être défini sur : SNDDRV_CTL_ELEM_ACCESS_READ, pour le moment, nous n'avons pas besoin de définir la fonction de rappel put. De même, s'il s'agit d'un contrôle en écriture seule, l'accès doit être défini sur : SNDDRV_CTL_ELEM_ACCESS_WRITE, à ce stade, nous n'avons pas besoin de définir la fonction de rappel get.

        Si la valeur du contrôle changera fréquemment (par exemple : un indicateur de niveau), nous pouvons utiliser le type VOLATILE, ce qui signifie que le contrôle changera sans notification, et l'application devra interroger périodiquement la valeur du contrôle.

4. Métadonnées (METADONNEES)

        De nombreux contrôles de mélangeur doivent fournir des informations en dB. Nous pouvons utiliser les macros DECLARE_TLV_xxx pour définir certaines variables contenant ces informations, puis faire pointer le champ tlv.p du contrôle vers ces variables. Enfin, ajoutez l'indicateur SNDRV_CTL_ELEM_ACCESS_TLV_READ au champ d'accès. Par example:

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. Détails des fonctions

5.1, fonction 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;
}

        Allouez une nouvelle instance de snd_kcontrol et copiez la valeur correspondante dans my_control dans cette instance, ainsi, lors de la définition de my_control, nous pouvons généralement ajouter le préfixe de __devinitdata. snd_ctl_add lie le contrôle à l'objet carte son.

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, fonction 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, fonction de rappel d'informations

        Pour obtenir les informations détaillées du contrôle correspondant, les informations doivent être stockées dans l'objet 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];
};

        La valeur qu'il contient est une union et le type de la valeur doit être déterminé en fonction du type du contrôle. Le type de contrôle comprend les types suivants :

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

        Voici la fonction de rappel d'informations définie par SNDRV_CTL_ELEM_TYPE_INTEGER et SNDRV_CTL_ELEM_TYPE_BOOLEAN à titre d'exemple :

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, ​​obtenir la fonction de rappel

        Cette fonction est utilisée pour lire la valeur du contrôle en cours et la renvoyer dans l'espace utilisateur. La valeur doit être placée dans la structure snd_ctl_elem_value, qui est similaire à la structure d'information. Le champ de valeur est une union et est lié au taper. Si le cont de value est supérieur à 1, vous devez mettre toutes les valeurs dans le tableau 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, mettre la fonction de rappel

         La fonction de rappel put est utilisée pour définir la valeur de contrôle de l'application dans le contrôle.

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. Contrôlez le processus de création de l'appareil

        Le périphérique de contrôle, comme le périphérique PCM, est un périphérique logique sous la carte son. L'application de l'espace utilisateur accède au dispositif de contrôle via alsa-lib, lit ou définit l'état de contrôle du contrôle, de manière à contrôler le codec audio pour effectuer diverses opérations de mixage et autres opérations de contrôle.

        Le processus de création d'un périphérique de contrôle est à peu près le même que celui d'un périphérique PCM. Pour le processus de création détaillé, veuillez vous référer au diagramme de séquence ci-dessous.

      Nous devons appeler activement la fonction snd_pcm_new() pour créer un périphérique pcm lorsque notre pilote est initialisé, et le périphérique de contrôle est créé dans snd_ctl_new1(), et snd_ctl_new1() crée un nœud de périphérique de contrôle en appelant la fonction snd_ctl_create(). Nous n'avons donc pas besoin de créer explicitement le périphérique de contrôle, tant que la carte son est créée, le périphérique de contrôle est automatiquement créé.

      Comme le périphérique pcm, le nom du périphérique de contrôle suit certaines règles : controlCxx, où xx représente le numéro de la carte son.

        La fonction snd_ctl_dev_register() sera appelée dans snd_card_register(), qui est la phase d'enregistrement de la carte son. Une fois l'enregistrement terminé, les informations pertinentes du dispositif de contrôle sont stockées dans le tableau snd_minors[], et les informations pertinentes peuvent être trouvées dans le tableau snd_minors[] en utilisant le numéro de dispositif mineur du dispositif de contrôle comme index. La relation de structure de données après enregistrement peut être exprimée par la figure suivante :

Je suppose que tu aimes

Origine blog.csdn.net/code_lyb/article/details/126165827
conseillé
Classement