Linux ALSA ドライバー 4: 制御装置作成プロセスのソース コード解析 (5.18)

        コントロール インターフェイスは主に、ユーザー空間アプリケーション ( ) がオーディオコーデック チップ内のマルチウェイ スイッチとスライド コントロールalsa-libにアクセスして制御できるようにします。(ミキシング) では、Control インターフェイスが特に重要です. ALSA 0.9.x から、すべてのミキサー作業は、コントロール インターフェイスのAPIを通じて実装されます。Mixer

        ALSA は AC97 用の完全な制御インターフェース モデルを定義しています。コーデック チップが AC97 インターフェースのみをサポートしている場合は、このセクションの内容を気にする必要はありません。

   <sound/control.h>すべてのコントロール API を定義します。コーデックに独自のコントロールを実装する場合は、このヘッダー ファイルをコードに含めてください。

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: SNDRV_CTL_ELEM_IFACE_XXX で定義されたコントロールのタイプを示します。通常は MIXER が使用され、グローバルな CARD タイプとして定義することもできます.HWDEP、PCMRAWMIDI、TIMER などの Morey デバイス タイプとして定義する場合は、カードのデバイス ロジック番号をデバイスおよびサブデバイス フィールド。

        name:コントロールの名前を示します. ユーザー層はこの名前を介してコントロールにアクセスできます. 詳細は後述します.

        index:このコントロールのインデックス番号を格納します。サウンド カードの下に複数のコーデックがある場合。各コーデックには、同じ名前のコントロールがあります。このとき、これらのコントロールをインデックスで区別する必要があります.インデックスが0の場合、この区別戦略は無視できます.

        アクセス:アクセス制御、READ、WRITE、READWRITE など。各ビットはアクセス タイプを表し、これらのアクセス タイプは複数の OR 演算と組み合わせて使用​​できます。

        private_value:個人の長整数値が含まれており、info、get、および put のコールバック関数を介してアクセスできます。

        tlv:このフィールドは、コントロールのメタデータを提供します。

2. コントロールの名前

        コントロールの名前は、いくつかの標準に従う必要があります。通常は、コントロールの名前を定義するために 3 つの部分に分けることができます源--方向--功能

        ソース:コントロールの入力端子として理解することができます. Alsa はいくつかの一般的に使用されるソースを定義しています: マスター, PCM, CD, ラインなど.

        方向:コントロールのデータ フローの方向を表します (再生、キャプチャ、バイパス、キャプチャのバイパスなど)。方向が定義されていない場合は、コントロールが双方向 (再生とキャプチャ) であることを意味します。

        機能:コントロールの機能に応じて、次の文字列にすることができます: スイッチ、ボリューム、ルートなど。

命名の例外もいくつかあります。

                1.グローバル キャプチャと再生:「キャプチャ ソース」、「キャプチャ ボリューム」、「キャプチャ スイッチ」は、グローバル キャプチャ ソース、スイッチ、およびボリュームに使用されます。同じ「再生ボリューム」、「再生スイッチ」、それらはグローバル出力スイッチとボリュームに使用されます。

                2.トーン コントロール: トーン コントロールのスイッチとボリュームの名前は、 Tomw Control-XXX です。たとえば、「Tone-Control-Switch」、「Tone Control-Bass」、「Tone Control-Center」などです。

                3、3D コントロール:3D控件的命名规则:“3D Control-Switch”,“3D Control-Center”,“3D Control-Space”。

                4. MIC ブースト:マイク ボリューム ブースト スペースの名前は、「MIC Boost」または「MIC Bosst (6dB)」です。

3. アクセスフラグ(ACCESS Flags)

        Access フィールドは、コントロールのアクセス タイプを格納するビットマスクです。デフォルトのアクセス タイプは SNDDRV_CTL_ELEM_ACCESS_READWRITE で、コントロールが読み取りおよび書き込み操作をサポートしていることを示します。アクセス フィールドが定義されていない場合 (.access==0) も、READWRITE タイプと見なされます。

        読み取り専用コントロールの場合、アクセスは SNDDRV_CTL_ELEM_ACCESS_READ に設定する必要があります。現時点では、put コールバック関数を定義する必要はありません。同様に、書き込み専用コントロールの場合、アクセスは SNDDRV_CTL_ELEM_ACCESS_WRITE に設定する必要があります。現時点では、get コールバック関数を定義する必要はありません。

        コントロールの値が頻繁に変更される場合 (レベル メーターなど)、VOLATILE タイプを使用できます。これは、コントロールが通知なしで変更されることを意味し、アプリケーションは定期的にコントロールの値をクエリする必要があります。

4. メタデータ (METADATA)

        多くのミキサー コントロールは dB で情報を提供する必要があります. DECLARE_TLV_xxx マクロを使用して、この情報を含むいくつかの変数を定義し、コントロールの tlv.p フィールドをこれらの変数にポイントすることができます. 最後に、SNDRV_CTL_ELEM_ACCESS_TLV_READ フラグをアクセス フィールドに追加します.例えば:

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.機能詳細

5.1、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;
}

        snd_kcontrol の新しいインスタンスを割り当て、my_control の対応する値をこのインスタンスにコピーします。したがって、通常、my_control を定義するときに、__devinitdata のプレフィックスを追加できます。snd_ctl_add は、コントロールをサウンド カード オブジェクト カードにバインドします。

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、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、情報コールバック関数

        対応するコントロールの詳細情報を取得するには、情報を 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];
};

        その中の値は共用体であり、コントロールのタイプに応じて値のタイプを決定する必要があります。コントロール タイプには次のタイプがあります。

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

        以下は、例として SNDRV_CTL_ELEM_TYPE_INTEGER および SNDRV_CTL_ELEM_TYPE_BOOLEAN によって定義された情報コールバック関数です。

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、コールバック関数を取得する

        この関数は、現在のコントロールの値を読み取り、それをユーザー空間に返すために使用されます. 値は、info 構造に類似した snd_ctl_elem_value 構造に配置する必要があります. 値フィールドは共用体であり、タイプ。value の cont が 1 より大きい場合、すべての値を 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、コールバック関数を入れる

         put コールバック関数は、アプリケーションのコントロール値をコントロールに設定するために使用されます。

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. 制御装置の作成プロセス

        コントロール デバイスは、PCM デバイスと同様に、サウンド カードの下にある論理デバイスです。ユーザー空間アプリケーションは、alsa-lib を介してコントロール デバイスにアクセスし、コントロールのコントロール状態を読み取りまたは設定して、オーディオ コーデックを制御し、さまざまなミキサーやその他のコントロール操作を実行します。

        コントロールデバイスを作成するプロセスは、PCM デバイスのプロセスとほぼ同じです。詳細な作成プロセスについては、以下のシーケンス図を参照してください。

      ドライバが初期化され、制御デバイスが snd_ctl_new1() で作成され、snd_ctl_new1() が snd_ctl_create() 関数を呼び出して制御デバイス ノードを作成するときに、snd_pcm_new() 関数をアクティブに呼び出して pcm デバイスを作成する必要があります。そのため、コントロール デバイスを明示的に作成する必要はありません。サウンド カードが作成されている限り、コントロール デバイスは自動的に作成されます。

      pcm デバイスと同様に、制御デバイスの名前は特定の規則に従います: controlCxx。ここで、xx はサウンド カードの番号を表します。

        snd_ctl_dev_register() 関数は、サウンド カードの登録フェーズである snd_card_register() で呼び出されます。登録が完了すると、制御デバイスの関連情報が snd_minors[] 配列に格納され、制御デバイスのマイナー デバイス番号をインデックスとして使用して、snd_minors[] 配列で関連情報を見つけることができます。登録後のデータ構造の関係は、次の図で表すことができます。

おすすめ

転載: blog.csdn.net/code_lyb/article/details/126165827