• Linux ALSA驱动之Control设备创建流程源码分析(5.18)


            Control接口主要让用户空间的应用程序(alsa-lib)可以访问和控制音频codec芯片中的多路开关,滑动控件等。对于 Mixer (混音)来说,Control接口显得尤为重要,从ALSA 0.9.x版本开始,所有的mixer工作都是通过control接口的API来实现的。

            ALSA已经为AC97定义了完整的控制接口模型,如果你的Codec芯片只支持AC97接口,你可以不用关心本节的内容。

       定义了所有的Control API。如果你要为你的codec实现自己的controls,请在代码中包含该头文件。

    1、snd_kcontrol_new

    1. struct snd_kcontrol_new {
    2. snd_ctl_elem_iface_t iface; /* interface identifier */
    3. unsigned int device; /* device/client number */
    4. unsigned int subdevice; /* subdevice (substream) number */
    5. const char *name; /* ASCII name of item */
    6. unsigned int index; /* index of item */
    7. unsigned int access; /* access rights */
    8. unsigned int count; /* count of same elements */
    9. snd_kcontrol_info_t *info;
    10. snd_kcontrol_get_t *get;
    11. snd_kcontrol_put_t *put;
    12. union {
    13. snd_kcontrol_tlv_rw_t *c;
    14. const unsigned int *p;
    15. } tlv;
    16. unsigned long private_value;
    17. };

            iface:表示control的类型,用SNDRV_CTL_ELEM_IFACE_XXX来定义。通常使用MIXER,也可以定于属于全局的CARD类型,如果定义为属于莫雷设备的类型,例如HWDEP、PCMRAWMIDI、TIMER等,此时必须在device和subdevice字段中支出卡的设备逻辑编号。

            name:表示control的名字,用户层可以通过这个名字访问这个control,后续会细聊

            index:存放这个 control 的索引号。如果声卡下不止一个codec。每个codec有相同的名字的control。此时就需要通过index来区分这些controls,当index为0,则可以忽略这种区分策略

            access:访问权限的控制,READ,WRITE,READWRITE等。每一个bit代表一种访问类型,这些访问类型可以多个或运算组合在一起使用。

            private_value:包含了一个人员的长整数类型的值,该值可以通过info、get、put这几个回调函数访问。

            tlv:该字段为control提供元数据。

    2、control的名字

            control的名字需要遵循一些标准,通常可以分成3部分来定义control的名字:源--方向--功能

            源:可以理解为该control的输入端,alsa已经预定义了一些常用的源,例如:Master,PCM,CD,Line等等。

            方向:代表该control的数据流向,例如:Playback,Capture,Bypass,Bypass Capture等等,也可以不定义方向,这时表示该Control是双向的(playback和capture)。

            功能:根据control的功能,可以是以下字符串:Switch,Volume,Route等等。

    也有一些命名上的特例:

                    1、全局的capture和playback:"Capture Source",“Capture Volume”,“Capture Switch”,他们用于全局的capture source、switch和volume。同样的“Playback Volume”,“Playeback Switch”,它们用于全局的输出switch和volume。

                    2、Tone-controles:音调控制的开关和音量命名为:Tomw Control-XXX,例如,“Tone-Control-Switch”,“Tone Control-Bass”,“Tone Control-Center”。

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

                    4、MIC boost:麦克风音量加强空间命名为:“MIC Boost”或“MIC Bosst(6dB)”。

    3、访问标志(ACCESS Flags)

            Access字段是一个bitmask,它保存了改control的访问类型。默认的访问类型是:SNDDRV_CTL_ELEM_ACCESS_READWRITE,表明该control支持读和写操作。如果access字段没有定义(.access==0),此时也认为是READWRITE类型。

            如果是一个只读control,access应该设置为:SNDDRV_CTL_ELEM_ACCESS_READ,这时,我们不必定义put回调函数。类似地,如果是只写control,access应该设置为:SNDDRV_CTL_ELEM_ACCESS_WRITE,这时,我们不必定义get回调函数。

            如果control的值会频繁地改变(例如:电平表),我们可以使用VOLATILE类型,这意味着该control会在没有通知的情况下改变,应用程序应该定时地查询该control的值。

    4、元数据(METADATA)

            很多mixer control需要提供以dB为单位的信息,我们可以使用DECLARE_TLV_xxx宏来定义一些包含这种信息的变量,然后把control的tlv.p字段指向这些变量,最后,在access字段中加上SNDRV_CTL_ELEM_ACCESS_TLV_READ标志,例如:

    1. static const DECLARE_TLV_DB_SCALE(snd_cx88_db_scale, -6300, 100, 0);
    2. static const struct snd_kcontrol_new snd_cx88_volume = {
    3. .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
    4. .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
    5. SNDRV_CTL_ELEM_ACCESS_TLV_READ,
    6. .name = "Analog-TV Volume",
    7. .info = snd_cx88_volume_info,
    8. .get = snd_cx88_volume_get,
    9. .put = snd_cx88_volume_put,
    10. .tlv.p = snd_cx88_db_scale,
    11. };

    5、函数详解

    5.1、snd_ctl_new1函数

    1. /**
    2. * snd_ctl_new1 - create a control instance from the template
    3. * @ncontrol: the initialization record
    4. * @private_data: the private data to set
    5. *
    6. * Allocates a new struct snd_kcontrol instance and initialize from the given
    7. * template. When the access field of ncontrol is 0, it's assumed as
    8. * READWRITE access. When the count field is 0, it's assumes as one.
    9. *
    10. * Return: The pointer of the newly generated instance, or %NULL on failure.
    11. */
    12. struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
    13. void *private_data)
    14. {
    15. struct snd_kcontrol *kctl;
    16. unsigned int count;
    17. unsigned int access;
    18. int err;
    19. if (snd_BUG_ON(!ncontrol || !ncontrol->info))
    20. return NULL;
    21. count = ncontrol->count;
    22. if (count == 0)
    23. count = 1;
    24. access = ncontrol->access;
    25. if (access == 0)
    26. access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
    27. access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE |
    28. SNDRV_CTL_ELEM_ACCESS_VOLATILE |
    29. SNDRV_CTL_ELEM_ACCESS_INACTIVE |
    30. SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE |
    31. SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND |
    32. SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK |
    33. SNDRV_CTL_ELEM_ACCESS_LED_MASK |
    34. SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK);
    35. /* 创建snd_kcontrol */
    36. err = snd_ctl_new(&kctl, count, access, NULL);
    37. if (err < 0)
    38. return NULL;
    39. /* 根据snd_kcontrol_new初始化snd_kcontrol */
    40. /* The 'numid' member is decided when calling snd_ctl_add(). */
    41. kctl->id.iface = ncontrol->iface;
    42. kctl->id.device = ncontrol->device;
    43. kctl->id.subdevice = ncontrol->subdevice;
    44. if (ncontrol->name) {
    45. strscpy(kctl->id.name, ncontrol->name, sizeof(kctl->id.name));
    46. if (strcmp(ncontrol->name, kctl->id.name) != 0)
    47. pr_warn("ALSA: Control name '%s' truncated to '%s'\n",
    48. ncontrol->name, kctl->id.name);
    49. }
    50. kctl->id.index = ncontrol->index;
    51. kctl->info = ncontrol->info;
    52. kctl->get = ncontrol->get;
    53. kctl->put = ncontrol->put;
    54. kctl->tlv.p = ncontrol->tlv.p;
    55. kctl->private_value = ncontrol->private_value;
    56. kctl->private_data = private_data;
    57. return kctl;
    58. }

            分配一个新的snd_kcontrol实例,并把my_control中相应的值复制到该实例中,所以,在定义my_control时,通常我们可以加上__devinitdata前缀.snd_ctl_add则把该control绑定到声卡对象card当中。

    1. struct snd_kcontrol {
    2. struct list_head list; /* list of controls */
    3. struct snd_ctl_elem_id id;
    4. unsigned int count; /* count of same elements */
    5. snd_kcontrol_info_t *info;
    6. snd_kcontrol_get_t *get;
    7. snd_kcontrol_put_t *put;
    8. union {
    9. snd_kcontrol_tlv_rw_t *c;
    10. const unsigned int *p;
    11. } tlv;
    12. unsigned long private_value;
    13. void *private_data;
    14. void (*private_free)(struct snd_kcontrol *kcontrol);
    15. struct snd_kcontrol_volatile vd[]; /* volatile data */
    16. };
    17. #define snd_kcontrol(n) list_entry(n, struct snd_kcontrol, list)

    5.2、 snd_ctl_add函数

    1. /* add/replace a new kcontrol object; call with card->controls_rwsem locked */
    2. static int __snd_ctl_add_replace(struct snd_card *card,
    3. struct snd_kcontrol *kcontrol,
    4. enum snd_ctl_add_mode mode)
    5. {
    6. struct snd_ctl_elem_id id;
    7. unsigned int idx;
    8. struct snd_kcontrol *old;
    9. int err;
    10. id = kcontrol->id;
    11. if (id.index > UINT_MAX - kcontrol->count)
    12. return -EINVAL;
    13. old = snd_ctl_find_id(card, &id);
    14. if (!old) {
    15. if (mode == CTL_REPLACE)
    16. return -EINVAL;
    17. } else {
    18. if (mode == CTL_ADD_EXCLUSIVE) {
    19. dev_err(card->dev,
    20. "control %i:%i:%i:%s:%i is already present\n",
    21. id.iface, id.device, id.subdevice, id.name,
    22. id.index);
    23. return -EBUSY;
    24. }
    25. err = snd_ctl_remove(card, old);
    26. if (err < 0)
    27. return err;
    28. }
    29. if (snd_ctl_find_hole(card, kcontrol->count) < 0)
    30. return -ENOMEM;
    31. /* 把snd_kcontrol挂入snd_card的controls链表 */
    32. list_add_tail(&kcontrol->list, &card->controls);
    33. card->controls_count += kcontrol->count;
    34. /* 设置元素ID */
    35. kcontrol->id.numid = card->last_numid + 1;
    36. card->last_numid += kcontrol->count;
    37. for (idx = 0; idx < kcontrol->count; idx++)
    38. snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_ADD, kcontrol, idx);
    39. return 0;
    40. }
    41. static int snd_ctl_add_replace(struct snd_card *card,
    42. struct snd_kcontrol *kcontrol,
    43. enum snd_ctl_add_mode mode)
    44. {
    45. int err = -EINVAL;
    46. if (! kcontrol)
    47. return err;
    48. if (snd_BUG_ON(!card || !kcontrol->info))
    49. goto error;
    50. down_write(&card->controls_rwsem);
    51. err = __snd_ctl_add_replace(card, kcontrol, mode);
    52. up_write(&card->controls_rwsem);
    53. if (err < 0)
    54. goto error;
    55. return 0;
    56. error:
    57. snd_ctl_free_one(kcontrol);
    58. return err;
    59. }
    60. /**
    61. * snd_ctl_add - add the control instance to the card
    62. * @card: the card instance
    63. * @kcontrol: the control instance to add
    64. *
    65. * Adds the control instance created via snd_ctl_new() or
    66. * snd_ctl_new1() to the given card. Assigns also an unique
    67. * numid used for fast search.
    68. *
    69. * It frees automatically the control which cannot be added.
    70. *
    71. * Return: Zero if successful, or a negative error code on failure.
    72. *
    73. */
    74. int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
    75. {
    76. return snd_ctl_add_replace(card, kcontrol, CTL_ADD_EXCLUSIVE);
    77. }

    5.3、info回调函数

            用于得到对应control的详细信息,需要把信息存入snd_ctl_elem_info 对象中。

    1. struct snd_ctl_elem_info {
    2. struct snd_ctl_elem_id id; /* W: element ID */
    3. snd_ctl_elem_type_t type; /* R: value type - SNDRV_CTL_ELEM_TYPE_* */
    4. unsigned int access; /* R: value access (bitmask) - SNDRV_CTL_ELEM_ACCESS_* */
    5. unsigned int count; /* count of values */
    6. __kernel_pid_t owner; /* owner's PID of this control */
    7. union {
    8. struct {
    9. long min; /* R: minimum value */
    10. long max; /* R: maximum value */
    11. long step; /* R: step (0 variable) */
    12. } integer;
    13. struct {
    14. long long min; /* R: minimum value */
    15. long long max; /* R: maximum value */
    16. long long step; /* R: step (0 variable) */
    17. } integer64;
    18. struct {
    19. unsigned int items; /* R: number of items */
    20. unsigned int item; /* W: item number */
    21. char name[64]; /* R: value name */
    22. __u64 names_ptr; /* W: names list (ELEM_ADD only) */
    23. unsigned int names_length;
    24. } enumerated;
    25. unsigned char reserved[128];
    26. } value;
    27. unsigned char reserved[64];
    28. };

            其中的value是个一个共用体,需要根据control的类型,确定值的类型,control type包括如下几类:

    1. typedef int __bitwise snd_ctl_elem_type_t;
    2. #define SNDRV_CTL_ELEM_TYPE_NONE ((__force snd_ctl_elem_type_t) 0) /* invalid */
    3. #define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((__force snd_ctl_elem_type_t) 1) /* boolean type */
    4. #define SNDRV_CTL_ELEM_TYPE_INTEGER ((__force snd_ctl_elem_type_t) 2) /* integer type */
    5. #define SNDRV_CTL_ELEM_TYPE_ENUMERATED ((__force snd_ctl_elem_type_t) 3) /* enumerated type */
    6. #define SNDRV_CTL_ELEM_TYPE_BYTES ((__force snd_ctl_elem_type_t) 4) /* byte array */
    7. #define SNDRV_CTL_ELEM_TYPE_IEC958 ((__force snd_ctl_elem_type_t) 5) /* IEC958 (S/PDIF) setup */
    8. #define SNDRV_CTL_ELEM_TYPE_INTEGER64 ((__force snd_ctl_elem_type_t) 6) /* 64-bit integer type */
    9. #define SNDRV_CTL_ELEM_TYPE_LAST SNDRV_CTL_ELEM_TYPE_INTEGER64

            下面是以SNDRV_CTL_ELEM_TYPE_INTEGER和以SNDRV_CTL_ELEM_TYPE_BOOLEAN为例定义的info回调函数:

    1. static int snd_cx88_volume_info(struct snd_kcontrol *kcontrol,
    2. struct snd_ctl_elem_info *info)
    3. {
    4. info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
    5. info->count = 2;
    6. info->value.integer.min = 0;
    7. info->value.integer.max = 0x3f;
    8. return 0;
    9. }
    10. static int snd_saa7134_capsrc_info(struct snd_kcontrol * kcontrol,
    11. struct snd_ctl_elem_info * uinfo)
    12. {
    13. uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
    14. uinfo->count = 2;
    15. uinfo->value.integer.min = 0;
    16. uinfo->value.integer.max = 1;
    17. return 0;
    18. }

    5.4、get回调函数

            这个函数用来读取当前 control 的值并返回到用户空间,需要把值放在snd_ctl_elem_value结构体中,与info结构体类似,value字段是一个共用体,与类型相关。如果value的cont大于1, 需要把值全部放入到 value[]数组中。

    1. static int snd_cx88_volume_info(struct snd_kcontrol *kcontrol,
    2. struct snd_ctl_elem_info *info)
    3. {
    4. info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
    5. info->count = 2;
    6. info->value.integer.min = 0;
    7. info->value.integer.max = 0x3f;
    8. return 0;
    9. }
    10. static int snd_cx88_volume_get(struct snd_kcontrol *kcontrol,
    11. struct snd_ctl_elem_value *value)
    12. {
    13. struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
    14. struct cx88_core *core = chip->core;
    15. int vol = 0x3f - (cx_read(AUD_VOL_CTL) & 0x3f),
    16. bal = cx_read(AUD_BAL_CTL);
    17. value->value.integer.value[(bal & 0x40) ? 0 : 1] = vol;
    18. vol -= (bal & 0x3f);
    19. value->value.integer.value[(bal & 0x40) ? 1 : 0] = vol < 0 ? 0 : vol;
    20. return 0;
    21. }

    5.5、put回调函数

             put回调函数用于把应用程序的控制值设置到control中。

    1. static void snd_cx88_wm8775_volume_put(struct snd_kcontrol *kcontrol,
    2. struct snd_ctl_elem_value *value)
    3. {
    4. struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
    5. struct cx88_core *core = chip->core;
    6. u16 left = value->value.integer.value[0];
    7. u16 right = value->value.integer.value[1];
    8. int v, b;
    9. /* Pass volume & balance onto any WM8775 */
    10. if (left >= right) {
    11. v = left << 10;
    12. b = left ? (0x8000 * right) / left : 0x8000;
    13. } else {
    14. v = right << 10;
    15. b = right ? 0xffff - (0x8000 * left) / right : 0x8000;
    16. }
    17. wm8775_s_ctrl(core, V4L2_CID_AUDIO_VOLUME, v);
    18. wm8775_s_ctrl(core, V4L2_CID_AUDIO_BALANCE, b);
    19. }
    20. /* OK - TODO: test it */
    21. static int snd_cx88_volume_put(struct snd_kcontrol *kcontrol,
    22. struct snd_ctl_elem_value *value)
    23. {
    24. struct cx88_audio_dev *chip = snd_kcontrol_chip(kcontrol);
    25. struct cx88_core *core = chip->core;
    26. int left, right, v, b;
    27. int changed = 0;
    28. u32 old;
    29. if (core->sd_wm8775)
    30. snd_cx88_wm8775_volume_put(kcontrol, value);
    31. left = value->value.integer.value[0] & 0x3f;
    32. right = value->value.integer.value[1] & 0x3f;
    33. b = right - left;
    34. if (b < 0) {
    35. v = 0x3f - left;
    36. b = (-b) | 0x40;
    37. } else {
    38. v = 0x3f - right;
    39. }
    40. /* Do we really know this will always be called with IRQs on? */
    41. spin_lock_irq(&chip->reg_lock);
    42. old = cx_read(AUD_VOL_CTL);
    43. if (v != (old & 0x3f)) {
    44. cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, (old & ~0x3f) | v);
    45. changed = 1;
    46. }
    47. if ((cx_read(AUD_BAL_CTL) & 0x7f) != b) {
    48. cx_write(AUD_BAL_CTL, b);
    49. changed = 1;
    50. }
    51. spin_unlock_irq(&chip->reg_lock);
    52. return changed;
    53. }

    6、Control设备创建流程

            Control设备和PCM设备一样,都属于声卡下的逻辑设备。用户空间的应用程序通过alsa-lib访问该Control设备,读取或设置control的控制状态,从而达到控制音频Codec进行各种Mixer等控制操作。

            Control设备的创建过程大体上和PCM设备的创建过程相同。详细的创建过程可以参考下方时序图。

          我们需要在我们的驱动程序初始化时主动调用snd_pcm_new()函数创建pcm设备,而control设备则在snd_ctl_new1()内被创建,snd_ctl_new1()通过调用snd_ctl_create()函数创建control设备节点。所以我们无需显式地创建control设备,只要建立声卡,control设备被自动地创建。

          和pcm设备一样,control设备的名字遵循一定的规则:controlCxx,这里的xx代表声卡的编号。

            snd_ctl_dev_register()函数会在snd_card_register()中,即声卡的注册阶段被调用。注册完成后,control设备的相关信息被保存在snd_minors[]数组中,用control设备的次设备号作索引,即可在snd_minors[]数组中找出相关的信息。注册完成后的数据结构关系可以用下图进行表述:

     

  • 相关阅读:
    postgresql,在pgAdmin中修改列名称和列的类型
    Linux代码初试__进度条
    ArcGIS计算地形湿度指数
    基于Android健身预约系统APP开发
    详解linux内核链表list_head及其接口应用
    从某大型企业实践看CMDB建设的核心问题:价值本源和数据治理
    页面转变为灰色,如此简单
    金蝶EAS本地WebService发布
    【AI学习】LLaMA 系列模型的进化(一)
    6.2 如何理解Go语言中的接口
  • 原文地址:https://blog.csdn.net/code_lyb/article/details/126165827