程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Rockchip RK3399 - ALSA 声卡之PCM设备

----------------------------------------------------------------------------------------------------------------------------

开发板 :NanoPC-T4开发板
eMMC :16GB
LPDDR3 :4GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot :2023.04
linux   :6.3
----------------------------------------------------------------------------------------------------------------------------

我们在Rockchip RK3399 - ALC5651 & I2S基础中实际上已经介绍过PCM,它是一种音频编码格式,更确切的说是一种将声音从模拟信号转换成数字信号的技术。

音频驱动的两大核心任务就是:

  • playback :如何把用户空间的应用程序发过来的PCM数据,转化为人耳可以辨别的模拟音频;
  • capture :把mic拾取到得模拟信号,经过采样、量化,转换为PCM数据送回给用户空间的应用程序;

ALSA CORE已经实现了PCM中间层,我们编写的驱动都是调用PCM相关的API,基本上只需要实现底层的需要访问硬件的函数即可;

  • include/sound/pcm.h:提供访问 PCM 中间层代码的API;
  • include/sound/pcm_params.h:提供访问一些与hw_param 相关的函数;

一、核心数据结构

1.1 ALSA中的数据结构

1.1.1 struct pcm_new

pcm使用struct snd_pcm数据结构来描述,一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成。

在嵌入式系统中,通常不会像图中这么复杂,大多数情况下是一个声卡,一个pcm实例,pcm下面有一个playback stream和capture stream,playback和capture下面各自有一个substream。

snd_pcm数据结构定义在include/sound/pcm.h;

struct snd_pcm {
        struct snd_card *card;
        struct list_head list;
        int device; /* device number */
        unsigned int info_flags;
        unsigned short dev_class;
        unsigned short dev_subclass;
        char id[64];
        char name[80];
        struct snd_pcm_str streams[2];
        struct mutex open_mutex;
        wait_queue_head_t open_wait;
        void *private_data;
        void (*private_free) (struct snd_pcm *pcm);
        bool internal; /* pcm is for internal use only */
        bool nonatomic; /* whole PCM operations are in non-atomic context */
        bool no_device_suspend; /* don't invoke device PM suspend */
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
        struct snd_pcm_oss oss;
#endif
};

这里重要的变量如下:

  • card:声卡设备;
  • device:pcm设备编号;
  • streams:数组长度为2,元素0指向playback stream设备,元素1指向capture stream设备;
  • private_data:在很多数据结构里面都可以看到,一般用于指向私有数据;
1.1.2 struct snd_pcm_str

struct snd_pcm_str用于表示pcm stream,定义在include/sound/pcm.h;

struct snd_pcm_str {
        int stream;                             /* stream (direction) */
        struct snd_pcm *pcm;
        /* -- substreams -- */
        unsigned int substream_count;
        unsigned int substream_opened;
        struct snd_pcm_substream *substream;
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
        /* -- OSS things -- */
        struct snd_pcm_oss_stream oss;
#endif
#ifdef CONFIG_SND_VERBOSE_PROCFS
        struct snd_info_entry *proc_root;
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
        unsigned int xrun_debug;        /* 0 = disabled, 1 = verbose, 2 = stacktrace */
#endif
#endif
        struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
        struct device dev;
};

snd_pcm_str的主要作用是指向snd_pcm_substream,而snd_pcm_substream可以有多个,这也是snd_pcm_str存在的原因,否则snd_pcm直接指向snd_pcm_substream就可以了。

其中:

  • stream:类类型,比如SNDRV_PCM_STREAM_PLAYBACK表示播放,SNDRV_PCM_STREAM_CAPTURE表示捕获;
  • pcm:指向pcm设备;
  • substream_count:substream的个数;
  • substream_opened:substream打开标志;
  • substream:用于保存所有的substream,struct snd_pcm_substream实是一个链表节点类型的数据结构;
  • dev:设备驱动模型中的device,可以将snd_pcm_str看做其子类;
1.1.3 struct snd_pcm_substream

struct snd_pcm_substream用于表示pcm substream,定义在include/sound/pcm.h;

struct snd_pcm_substream {
        struct snd_pcm *pcm;
        struct snd_pcm_str *pstr;
        void *private_data;             /* copied from pcm->private_data */
        int number;
        char name[32];                  /* substream name */
        int stream;                     /* stream (direction) */
        struct pm_qos_request latency_pm_qos_req; /* pm_qos request */
        size_t buffer_bytes_max;        /* limit ring buffer size */
        struct snd_dma_buffer dma_buffer;
        size_t dma_max;
        /* -- hardware operations -- */
        const struct snd_pcm_ops *ops;
        /* -- runtime information -- */
        struct snd_pcm_runtime *runtime;
        /* -- timer section -- */
        struct snd_timer *timer;                /* timer */
        unsigned timer_running: 1;      /* time is running */
        long wait_time; /* time in ms for R/W to wait for avail */
        /* -- next substream -- */
        struct snd_pcm_substream *next;
        /* -- linked substreams -- */
        struct list_head link_list;     /* linked list member */
        struct snd_pcm_group self_group;        /* fake group for non linked substream (with substream lock inside) */
        struct snd_pcm_group *group;            /* pointer to current group */
        /* -- assigned files -- */
        int ref_count;
        atomic_t mmap_count;
        unsigned int f_flags;
        void (*pcm_release)(struct snd_pcm_substream *);
        struct pid *pid;
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
        /* -- OSS things -- */
        struct snd_pcm_oss_substream oss;
#endif
#ifdef CONFIG_SND_VERBOSE_PROCFS
        struct snd_info_entry *proc_root;
#endif /* CONFIG_SND_VERBOSE_PROCFS */
        /* misc flags */
        unsigned int hw_opened: 1;
};

snd_pcm_substream的内容有些多,此处只需要重要的进行介绍;

  • ops:pcm操作集,这部分具体的操作需要实现者的参与,留给实现者的函数指针集。这个和文件操作的设计策略是一致的;
  • runtime:pcm运行时实例,读写数据的时候由它来控制;
  • next:指向下一个pcm substream,用于将多个snd_pcm_substream对象链接起来;
  • pstrt:指向所属的pcm stream;
  • group:在用户空间可以通过SNDRV_PCM_IOCTL_LINK将多个substream链接起来。然后就可以对这些对象进行统一的操作。
1.1.4 snd_pcm_hw_params 

struct snd_pcm_hw_params定义在include/uapi/sound/asound.h,是用于配置音频硬件参数的结构体,比如通道数、采样率、数据格式等;

struct snd_pcm_hw_params {
        unsigned int flags;
        struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK -
                               SNDRV_PCM_HW_PARAM_FIRST_MASK + 1];
        struct snd_mask mres[5];        /* reserved masks */
        struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL -   // 宏的值为19
                                        SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];  // 宏的值为8
        struct snd_interval ires[9];    /* reserved intervals */
        unsigned int rmask;             /* W: requested masks */
        unsigned int cmask;             /* R: changed masks */
        unsigned int info;              /* R: Info flags for returned setup */
        unsigned int msbits;            /* R: used most significant bits */
        unsigned int rate_num;          /* R: rate numerator */
        unsigned int rate_den;          /* R: rate denominator */
        snd_pcm_uframes_t fifo_size;    /* R: chip FIFO size in frames */
        unsigned char reserved[64];     /* reserved for future */
};

该结构体定义了一组成员变量,用于存储和管理音频硬件参数的信息。下面是各成员变量的含义:

  • flags:标志位,用于指示参数的特性和状态。
  • masks:用于描述硬件参数的位掩码。在数组中的每个元素都代表一个具体的参数;
  • mres:保留的位掩码;
  • intervals:这是一个数组,数组长度为12,用于描述硬件参数的取值范围;每一个元素依次表示的意义为:
    • SNDRV_PCM_HW_PARAM_SAMPLE_BITS:Bits per sample ,每个采样点的位数,即位宽度;
    • SNDRV_PCM_HW_PARAM_FRAME_BITS:Bits per frame,每帧所占用的位数,假设有一个音频流,采样率为 44100 Hz,声道数为 2(立体声),每个采样点的比特数为 16bit。在这种情况下,每个采样点占用 16 个比特,每帧就包含了双声道的采样点数据。因此,每帧的比特数可以计算为:16 bits/sample × 2 channels = 32 bits/frame;
    • SNDRV_PCM_HW_PARAM_CHANNELS:Channels,通道数;
    • SNDRV_PCM_HW_PARAM_RATE:Approx rate,近似采样率;
    • SNDRV_PCM_HW_PARAM_PERIOD_TIME:Approx distance between interrupts in us;
    • SNDRV_PCM_HW_PARAM_PERIOD_SIZE:Approx frames between interrupts;
    • SNDRV_PCM_HW_PARAM_PERIOD_BYTES:Approx bytes between interrupts;
    • SNDRV_PCM_HW_PARAM_PERIODS:Approx interrupts per buffer;
    • SNDRV_PCM_HW_PARAM_BUFFER_TIME:Approx duration of buffer in us;
    • SNDRV_PCM_HW_PARAM_BUFFER_SIZE:Size of buffer in frames;
    • SNDRV_PCM_HW_PARAM_BUFFER_BYTES:Size of buffer in bytes
    • SNDRV_PCM_HW_PARAM_TICK_TIME:Approx tick duration in us;
  • ires:保留的取值范围;
  • rmask:请求的位掩码;
  • cmask:变化的位掩码;
  • info:返回的设置信息标志,用于指示配置的相关信息;
  • msbits:使用的最高有效位数;
  • rate_num:采样率的分子部分(分子/分母构成实际的采样率);
  • rate_den:采样率的分母部分;
  • fifo_size:芯片 FIFO(First In First Out)缓冲区的大小;
  • reserved:保留字段,用于未来的扩展;

其中struct snd_interval数据结构定义如下:

struct snd_interval {
        unsigned int min, max;
        unsigned int openmin:1,
                     openmax:1,
                     integer:1,
                     empty:1;
};

1.2 ASoC中的数据结构

1.2.1 struct snd_soc_pcm_stream

 struct snd_soc_pcm_stream是ASoC中定义的pcm stream,定义在include/sound/soc.h,用于描述SoC pcm stream的信息;

/* SoC PCM stream information */
struct snd_soc_pcm_stream {
        const char *stream_name;
        u64 formats;                    /* SNDRV_PCM_FMTBIT_* */
        unsigned int rates;             /* SNDRV_PCM_RATE_* */
        unsigned int rate_min;          /* min rate */
        unsigned int rate_max;          /* max rate */
        unsigned int channels_min;      /* min channels */
        unsigned int channels_max;      /* max channels */
        unsigned int sig_bits;          /* number of bits of content */
};

其中:

  • stream_name:stream名称;
  • formats:位深度,也就是数据传输的位宽;
  • rate:音频采样率;
  • rate_min:最低采样率;
  • rate_max:最高采样率;
  • channels_min:最少通道数;
  • channels_max:最大通道数;
  • sig_bits:采样位宽;

位深度种类很多,比如:

#define SNDRV_PCM_FMTBIT_S8             _SNDRV_PCM_FMTBIT(S8)          // 8位整数采样格式
#define SNDRV_PCM_FMTBIT_U8             _SNDRV_PCM_FMTBIT(U8)        
#define SNDRV_PCM_FMTBIT_S16_LE         _SNDRV_PCM_FMTBIT(S16_LE)      // 16为整数采样格式(小端)
#define SNDRV_PCM_FMTBIT_S16_BE         _SNDRV_PCM_FMTBIT(S16_BE)
#define SNDRV_PCM_FMTBIT_U16_LE         _SNDRV_PCM_FMTBIT(U16_LE)
#define SNDRV_PCM_FMTBIT_U16_BE         _SNDRV_PCM_FMTBIT(U16_BE)
#define SNDRV_PCM_FMTBIT_S24_LE         _SNDRV_PCM_FMTBIT(S24_LE)
#define SNDRV_PCM_FMTBIT_S24_BE         _SNDRV_PCM_FMTBIT(S24_BE)
#define SNDRV_PCM_FMTBIT_U24_LE         _SNDRV_PCM_FMTBIT(U24_LE)
#define SNDRV_PCM_FMTBIT_U24_BE         _SNDRV_PCM_FMTBIT(U24_BE)
#define SNDRV_PCM_FMTBIT_S32_LE         _SNDRV_PCM_FMTBIT(S32_LE)
#define SNDRV_PCM_FMTBIT_S32_BE         _SNDRV_PCM_FMTBIT(S32_BE)
#define SNDRV_PCM_FMTBIT_U32_LE         _SNDRV_PCM_FMTBIT(U32_LE)
#define SNDRV_PCM_FMTBIT_U32_BE         _SNDRV_PCM_FMTBIT(U32_BE)
#define SNDRV_PCM_FMTBIT_FLOAT_LE       _SNDRV_PCM_FMTBIT(FLOAT_LE)
#define SNDRV_PCM_FMTBIT_FLOAT_BE       _SNDRV_PCM_FMTBIT(FLOAT_BE)
View Code

音频采样率种类很多,比如:

/* If you change this don't forget to change rates[] table in pcm_native.c */
#define SNDRV_PCM_RATE_5512             (1<<0)          /* 5512Hz */
#define SNDRV_PCM_RATE_8000             (1<<1)          /* 8000Hz */
#define SNDRV_PCM_RATE_11025            (1<<2)          /* 11025Hz */
#define SNDRV_PCM_RATE_16000            (1<<3)          /* 16000Hz */
#define SNDRV_PCM_RATE_22050            (1<<4)          /* 22050Hz */
#define SNDRV_PCM_RATE_32000            (1<<5)          /* 32000Hz */
#define SNDRV_PCM_RATE_44100            (1<<6)          /* 44100Hz */
#define SNDRV_PCM_RATE_48000            (1<<7)          /* 48000Hz */
#define SNDRV_PCM_RATE_64000            (1<<8)          /* 64000Hz */
#define SNDRV_PCM_RATE_88200            (1<<9)          /* 88200Hz */
#define SNDRV_PCM_RATE_96000            (1<<10)         /* 96000Hz */
#define SNDRV_PCM_RATE_176400           (1<<11)         /* 176400Hz */
#define SNDRV_PCM_RATE_192000           (1<<12)         /* 192000Hz */

#define SNDRV_PCM_RATE_CONTINUOUS       (1<<30)         /* continuous range */
#define SNDRV_PCM_RATE_KNOT             (1<<31)         /* supports more non-continuos rates */

#define SNDRV_PCM_RATE_8000_44100       (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_11025|\
                                         SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_22050|\
                                         SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_44100)
#define SNDRV_PCM_RATE_8000_48000       (SNDRV_PCM_RATE_8000_44100|SNDRV_PCM_RATE_48000)
#define SNDRV_PCM_RATE_8000_96000       (SNDRV_PCM_RATE_8000_48000|SNDRV_PCM_RATE_64000|\
                                         SNDRV_PCM_RATE_88200|SNDRV_PCM_RATE_96000)
#define SNDRV_PCM_RATE_8000_192000      (SNDRV_PCM_RATE_8000_96000|SNDRV_PCM_RATE_176400|\
                                         SNDRV_PCM_RATE_192000)
View Code
1.2.2 struct snd_soc_pcm_runtime 

struct snd_soc_pcm_runtime是ASoC中定义的pcm runtime,定义在include/sound/soc.h,这个数据结构非常用重要;在注册ASoC声卡时会调用soc_new_pcm_runtime为每一个音频数据链路(struct snd_soc_dai_link)分配一个snd_soc_pcm_runtime ,snd_soc_pcm_runtime 是ASoC的桥梁,保存音频数据链路的dai、以及dai所属的component等信息;

/* SoC machine DAI configuration, glues a codec and cpu DAI together */
struct snd_soc_pcm_runtime {
        struct device *dev;
        struct snd_soc_card *card;
        struct snd_soc_dai_link *dai_link;
        struct snd_pcm_ops ops;

        unsigned int params_select; /* currently selected param for dai link */

        /* Dynamic PCM BE runtime data */
        struct snd_soc_dpcm_runtime dpcm[SNDRV_PCM_STREAM_LAST + 1];
        struct snd_soc_dapm_widget *c2c_widget[SNDRV_PCM_STREAM_LAST + 1];

        long pmdown_time;

        /* runtime devices */
        struct snd_pcm *pcm;
        struct snd_compr *compr;

        /*
         * dais = cpu_dai + codec_dai
         * see
         *      soc_new_pcm_runtime()
         *      asoc_rtd_to_cpu()
         *      asoc_rtd_to_codec()
         */
        struct snd_soc_dai **dais;

        struct delayed_work delayed_work;
        void (*close_delayed_work_func)(struct snd_soc_pcm_runtime *rtd);
#ifdef CONFIG_DEBUG_FS
        struct dentry *debugfs_dpcm_root;
#endif

        unsigned int num; /* 0-based and monotonic increasing */
        struct list_head list; /* rtd list of the soc card */

        /* function mark */
        struct snd_pcm_substream *mark_startup;
        struct snd_pcm_substream *mark_hw_params;
        struct snd_pcm_substream *mark_trigger;
        struct snd_compr_stream  *mark_compr_startup;

        /* bit field */
        unsigned int pop_wait:1;
        unsigned int fe_compr:1; /* for Dynamic PCM */

        int num_components;
        struct snd_soc_component *components[]; /* CPU/Codec/Platform */
};

其中:

  • dev:设备驱动模型中的device,可以将snd_soc_pcm_runtime看做其子类;
    • dev->parent一般会被设置为ASoC声卡card->dev,以后面我们将要介绍的machine驱动(simple-card)为例,card->dev为rt5651-sound设备;
    • dev->release一般会被设置为soc_release_rtd_dev;
    • dev设备名称会为设置为snd_soc_dai_link的名称,比如ALC5651音频驱动,这个名称为"rockchip-i2s-rt5651";
    • dev设备驱动数据会被设置为当前snd_soc_pcm_runtime ;
  • card:指向ASoC声卡的指针;
  • dai_link:指向音频数据链路的指针;
  • dpcm:Dynamic PCM BE runtime data;
  • dais:指向一个指针数组,指针数组中每个元素指向一个dai,用于保存当前音频数据链路上的dai,包括cpu dai、codec dai;
  • delayed_word:延迟工作;
  • num:为ASoC声卡设备的每一个pcm runtime会分配一个编号,从0开始;
  • pmdown_time:下电超时时间;
  • list:链表节点,用于构建双向链表,将当前pcm runtime添加到ASoC声卡的rtd_list链表中;
  • components:指向一个数组,用于存储当前音频数据链路上的代表platform以及codec设备的component;
  • num_components:components数组的长度;

如果你去比较snd_soc_dai_link和soc_new_pcm_runtime数据结构,你就可以发现这个数据结构实际上都是用来描述音频数据链路的,只不过一个描述的是静态数据,另一个描述是动态数据。

1.3 关系图

下面一张图列出了PCM中间层几个重要的结构,可以让我们从uml的角度看一看这列结构的关系,理清他们之间的关系,对我们理解PCM中间层的实现方式。

其中我们比较关注的点是:

  • pcm是挂在snd_card成员devices链表下面的一个snd_device;
  • snd_pcm中的字段:streams[2],该数组中的两个元素指向两个snd_pcm_str结构,分别代表playback stream和capture stream;
  • snd_pcm_str中的substream字段,指向snd_pcm_substream结构;
  • snd_pcm_substream是pcm中间层的核心,绝大部分任务都是在substream中处理,尤其是它的ops(snd_pcm_ops)字段,许多user空间的应用程序通过alsa-lib对驱动程序的请求都是由该结构中的函数处理。它的runtime字段则指向snd_pcm_runtime结构,snd_pcm_runtime记录这substream的一些重要的软件和硬件运行环境和参数

二、核心API

2.1 ALSA创建PCM设备(snd_pcm_new)

在ALSA中,PCM设备的创建可以通过snd_pcm_new函数来完成,函数接收6个参数:

  • card:ALSA声卡设备;
  • id:pcm设备唯一标识;
  • device:设备号,表示目前创建的是该声卡下的第几个pcm,第一个pcm设备从0开始;
  • playback_count: 表示该pcm将会有几个playback substream;
  • capture_count:表示该pcm将会有几个capture substream;
  • rpcm:保存创建的pcm设备;

函数定义在sound/core/pcm.c,

/**
 * snd_pcm_new - create a new PCM instance
 * @card: the card instance
 * @id: the id string
 * @device: the device index (zero based)
 * @playback_count: the number of substreams for playback
 * @capture_count: the number of substreams for capture
 * @rpcm: the pointer to store the new pcm instance
 *
 * Creates a new PCM instance.
 *
 * The pcm operators have to be set afterwards to the new instance
 * via snd_pcm_set_ops().
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_pcm_new(struct snd_card *card, const char *id, int device,
                int playback_count, int capture_count, struct snd_pcm **rpcm)
{
        return _snd_pcm_new(card, id, device, playback_count, capture_count,
                        false, rpcm);
}

这段代码实际上就是调用_snd_pcm_new创建一个pcm_new实例,并根据playback_count、capture_count等参数初始化playback stream和capture stream,card、device等成员;

static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
                int playback_count, int capture_count, bool internal,
                struct snd_pcm **rpcm)
{
        struct snd_pcm *pcm;
        int err;
        static const struct snd_device_ops ops = {  // 声卡pcm设备操作集
                .dev_free = snd_pcm_dev_free,
                .dev_register = snd_pcm_dev_register,
                .dev_disconnect = snd_pcm_dev_disconnect,
        };
        static const struct snd_device_ops internal_ops = {
                .dev_free = snd_pcm_dev_free,
        };

        if (snd_BUG_ON(!card))
                return -ENXIO;
        if (rpcm)
                *rpcm = NULL;
        pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);   // 动态申请内存,申请一个snd_pcm数据结构
        if (!pcm)
                return -ENOMEM;
        pcm->card = card;               // 设置声卡设备
        pcm->device = device;           // 设置pcm设备编号 
        pcm->internal = internal;       // 设置内部标志位  
        mutex_init(&pcm->open_mutex);         // 初始化互斥锁
        init_waitqueue_head(&pcm->open_wait); // 初始化等待队列头
        INIT_LIST_HEAD(&pcm->list);           // 初始化链表节点
        if (id)
                strscpy(pcm->id, id, sizeof(pcm->id));  // 设置id

        err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK,
                                 playback_count);     // 创建playback stream
        if (err < 0)
                goto free_pcm;

        err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count);    // 创建capture stream
        if (err < 0)
                goto free_pcm;

        err = snd_device_new(card, SNDRV_DEV_PCM, pcm,    // device_data设置为pcm
                             internal ? &internal_ops : &ops);  //  创建一个新的snd_device实例,并将添加到声卡设备的devices链表中
        if (err < 0)
                goto free_pcm;

        if (rpcm)
                *rpcm = pcm;  // 写回
        return 0;

free_pcm:
        snd_pcm_free(pcm);
        return err;
}
2.1.1 snd_pcm_new_stream

这里我们需要关注一下pcm stream的创建函数snd_pcm_new_stream,第一个参数为pcm实例,第二个参数为pcm数据流方向,即playback还是capture,第三个参数为substream数量;

/**
 * snd_pcm_new_stream - create a new PCM stream
 * @pcm: the pcm instance
 * @stream: the stream direction, SNDRV_PCM_STREAM_XXX
 * @substream_count: the number of substreams
 *
 * Creates a new stream for the pcm.
 * The corresponding stream on the pcm must have been empty before
 * calling this, i.e. zero must be given to the argument of
 * snd_pcm_new().
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count) // 假设pcm->internal=0
{
        int idx, err;
        struct snd_pcm_str *pstr = &pcm->streams[stream];
        struct snd_pcm_substream *substream, *prev;

#if IS_ENABLED(CONFIG_SND_PCM_OSS)
        mutex_init(&pstr->oss.setup_mutex);
#endif
        pstr->stream = stream;    // 设置pcm数据流方向,即playback还是capture
        pstr->pcm = pcm;          // 设置pcm实例
        pstr->substream_count = substream_count;  // substream数量
        if (!substream_count)
                return 0;

        snd_device_initialize(&pstr->dev, pcm->card);  // 初始化pstr->dev成员
        pstr->dev.groups = pcm_dev_attr_groups;
        pstr->dev.type = &pcm_dev_type;
        dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device,
                     stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');  // 设置设备名称为pcmC0Dxp或pcmC0Dxc

        if (!pcm->internal) {
                err = snd_pcm_stream_proc_init(pstr);  // proc文件系统相关
                if (err < 0) {
                        pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n");
                        return err;
                }
        }
        prev = NULL;
        for (idx = 0, prev = NULL; idx < substream_count; idx++) { // 创建若干个snd_pcm_substream实例,填充 substream 字段,
// 如 streams,number,stream等,并将所有的 substream 填充到 pcm->snd_pcm_substream->substream 中。 substream
= kzalloc(sizeof(*substream), GFP_KERNEL); if (!substream) return -ENOMEM; substream->pcm = pcm; substream->pstr = pstr; substream->number = idx; substream->stream = stream; sprintf(substream->name, "subdevice #%i", idx); substream->buffer_bytes_max = UINT_MAX; if (prev == NULL) pstr->substream = substream; else prev->next = substream; if (!pcm->internal) { err = snd_pcm_substream_proc_init(substream); if (err < 0) { pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n"); if (prev == NULL) pstr->substream = NULL; else prev->next = NULL; kfree(substream); return err; } } substream->group = &substream->self_group; snd_pcm_group_init(&substream->self_group); list_add_tail(&substream->link_list, &substream->self_group.substreams); atomic_set(&substream->mmap_count, 0); prev = substream; } return 0; }
2.1.2 snd_pcm_stream_proc_init

snd_pcm_stream_proc_init函数实现如下:

static int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr)
{
        struct snd_pcm *pcm = pstr->pcm;
        struct snd_info_entry *entry;
        char name[16];

        sprintf(name, "pcm%i%c", pcm->device,
                pstr->stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');
        entry = snd_info_create_card_entry(pcm->card, name,
                                           pcm->card->proc_root);  // 创建pcmxp/c目录
        if (!entry)
                return -ENOMEM;
        entry->mode = S_IFDIR | 0555;
        pstr->proc_root = entry;
        entry = snd_info_create_card_entry(pcm->card, "info", pstr->proc_root);  // 在 pcmxp|c下创建info文件
        if (entry)
                snd_info_set_text_ops(entry, pstr, snd_pcm_stream_proc_info_read);
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
        entry = snd_info_create_card_entry(pcm->card, "xrun_debug",
                                           pstr->proc_root);
        if (entry) {
                snd_info_set_text_ops(entry, pstr, snd_pcm_xrun_debug_read);
                entry->c.text.write = snd_pcm_xrun_debug_write;
                entry->mode |= 0200;
        }
#endif
        return 0;
}

当我们查看/proc/asound/card0/pcm0p/info文件的内容:

root@rk3399:/# cat /proc/asound/card0/pcm0p/info
card: 0
device: 0
subdevice: 0
stream: PLAYBACK
id: ff880000.i2s-rt5651-aif1 rt5651-aif1-0
name: ff880000.i2s-rt5651-aif1 rt5651-aif1-0
subname: subdevice #0
class: 0
subclass: 0
subdevices_count: 1
subdevices_avail: 1

实际上就是调用的snd_pcm_stream_proc_info_read函数;

static void snd_pcm_proc_info_read(struct snd_pcm_substream *substream,
                                   struct snd_info_buffer *buffer)
{
        struct snd_pcm_info *info;
        int err;

        if (! substream)
                return;

        info = kmalloc(sizeof(*info), GFP_KERNEL);
        if (!info)
                return;

        err = snd_pcm_info(substream, info);
        if (err < 0) {
                snd_iprintf(buffer, "error %d\n", err);
                kfree(info);
                return;
        }
        snd_iprintf(buffer, "card: %d\n", info->card);
        snd_iprintf(buffer, "device: %d\n", info->device);
        snd_iprintf(buffer, "subdevice: %d\n", info->subdevice);
        snd_iprintf(buffer, "stream: %s\n", snd_pcm_stream_name(info->stream));
        snd_iprintf(buffer, "id: %s\n", info->id);
        snd_iprintf(buffer, "name: %s\n", info->name);
        snd_iprintf(buffer, "subname: %s\n", info->subname);
        snd_iprintf(buffer, "class: %d\n", info->dev_class);
        snd_iprintf(buffer, "subclass: %d\n", info->dev_subclass);
        snd_iprintf(buffer, "subdevices_count: %d\n", info->subdevices_count);
        snd_iprintf(buffer, "subdevices_avail: %d\n", info->subdevices_avail);
        kfree(info);
}

static void snd_pcm_stream_proc_info_read(struct snd_info_entry *entry,
                                          struct snd_info_buffer *buffer)
{
        snd_pcm_proc_info_read(((struct snd_pcm_str *)entry->private_data)->substream,
                               buffer);
}
2.1.3 snd_pcm_dev_register

在注册声卡设备card时会遍历声卡设备的逻辑设备链表devices,并调用声卡逻辑设备操作集中的dev_register函数,对于pcm设备也就是snd_pcm_dev_register函数,该函数调用snd_register_device依次注册pcm播放设备、pcm录音设备;

static int snd_pcm_dev_register(struct snd_device *device)
{
        int cidx, err;
        struct snd_pcm_substream *substream;
        struct snd_pcm *pcm;

        if (snd_BUG_ON(!device || !device->device_data))
                return -ENXIO;
        pcm = device->device_data;          // 获取snd_pcm实例

        mutex_lock(&register_mutex);     
        err = snd_pcm_add(pcm);        // 将pcm添加到全局链表snd_pcm_devices
        if (err)
                goto unlock;
        for (cidx = 0; cidx < 2; cidx++) {
                int devtype = -1;     // 设备类型
                if (pcm->streams[cidx].substream == NULL)
                        continue;
                switch (cidx) {
                case SNDRV_PCM_STREAM_PLAYBACK:    //0 
                        devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
                        break;
                case SNDRV_PCM_STREAM_CAPTURE:     //1
                        devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
                        break;
                }
                /* register pcm */
                err = snd_register_device(devtype, pcm->card, pcm->device, // 设备类型 声卡设备 设备号
                                          &snd_pcm_f_ops[cidx], pcm,
                                          &pcm->streams[cidx].dev);   if (err < 0) {
                        list_del_init(&pcm->list);
                        goto unlock;
                }

                for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
                        snd_pcm_timer_init(substream);
        }

        pcm_call_notify(pcm, n_register);

 unlock:
        mutex_unlock(&register_mutex);
        return err;
}
2.1.4 时序图表示

通过分析,我们将声卡设备创建、以及pcm设备创建的完整流程通过时序图表示如下:

(1) snd_card_create :pcm是声卡下的一个设备(部件),所以第一步是要创建一个声卡;

(2) snd_pcm_new:调用该API创建一个pcm,在该API中会做以下事情;

  • 建立playback stream,相应的substream也同时建立;
  • 建立capture stream,相应的substream也同时建立;
  • 调用snd_device_new把该pcm挂到声卡中,参数ops中的dev_register字段指向了函数snd_pcm_dev_register,这个回调函数会在声卡的注册阶段被调用;

(3) snd_pcm_set_ops:设置操作该pcm的控制/操作接口函数,参数中的snd_pcm_ops结构中的函数通常就是我们驱动要实现的函数;
(4) snd_card_register:注册声卡,在这个阶段会遍历声卡下的所有逻辑设备,并且调用各设备的注册回调函数,对于pcm,就是第二步提到的snd_pcm_dev_register函数,该回调函数建立了和用户空间应用程序(比如alsa-lib)通信所用的设备文件节点:/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc;

2.2 ASoC创建PCM设备

在ASoC中,PCM设备的创建可以通过soc_create_pcm函数来完成,函数接收5个参数:

  • pcm:存放通过snd_pcm_new创建的pcm设备;
  • rtd:pcm runtime;
  • num:设备号,表示目前创建的是该声卡下的第几个pcm,第一个pcm设备从0开始;
  • playback: 表示该pcm将会有几个playback substream;
  • capture:表示该pcm将会有几个capture substream;

函数定义在sound/soc/soc-pcm.c,实际上该函数就是调用snd_pcm_new来完成pcm设备的创建;

static int soc_create_pcm(struct snd_pcm **pcm,
                          struct snd_soc_pcm_runtime *rtd,
                          int playback, int capture, int num)
{
        char new_name[64];
        int ret;

        /* create the PCM */
        if (rtd->dai_link->params) {
                snprintf(new_name, sizeof(new_name), "codec2codec(%s)",
                         rtd->dai_link->stream_name);

                ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,
                                           playback, capture, pcm);
        } else if (rtd->dai_link->no_pcm) {
                snprintf(new_name, sizeof(new_name), "(%s)",
                        rtd->dai_link->stream_name);

                ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,
                                playback, capture, pcm);
        } else {   // 进入这个分支
                if (rtd->dai_link->dynamic)
                        snprintf(new_name, sizeof(new_name), "%s (*)",
                                rtd->dai_link->stream_name);
                else
                        snprintf(new_name, sizeof(new_name), "%s %s-%d",  // 比如ff880000.i2s-rt5651-aif1 rt5651-aif1-0
                                rtd->dai_link->stream_name,               // ff880000.i2s-rt5651-aif1 
                                soc_codec_dai_name(rtd), num);            // rt5651-aif1 0

                ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,   // 创建pcm设备 比new_name=“ff880000.i2s-rt5651-aif1 rt5651-aif1-0”,num=0
                        capture, pcm);
        }
        if (ret < 0) {
                dev_err(rtd->card->dev, "ASoC: can't create pcm %s for dailink %s: %d\n",
                        new_name, rtd->dai_link->name, ret);
                return ret;
        }
        dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name);

        return 0;
}

三、PCM设备文件

当我们移植外音频驱动之后,我们是可以在/dev/snd目录下看到pcm设备文件的,比如在NanoPC-T4开发板移植了ALC5651声卡驱动之后;

root@rk3399:~# ll /dev/snd
drwxr-xr-x  2 root root       60 Aug 24 21:31 by-path/
crw-rw----  1 root audio 116,  4 Aug 24 21:31 controlC0
crw-rw----  1 root audio 116,  3 Aug 24 21:31 pcmC0D0c
crw-rw----  1 root audio 116,  2 Aug 24 21:31 pcmC0D0p
crw-rw----  1 root audio 116,  1 Aug 24 21:31 seq
crw-rw----  1 root audio 116, 33 Aug 24 21:31 timer

可以看到这些字符设备的主设备号都是116,其中:

  • controlC0:用于声卡的控制,例如通道选择,混音,麦克控制,音量加减,开关等;

  • pcmC0D0c:用于录音的pcm设备;

  • pcmC0D0p:用于播放的pcm设备;

  • seq:音序器;

  • timer:定时器;

C0D0代表的是声卡0中的设备0,pcmC0D0c最后一个c代表capture,pcmC0D0p最后一个p代表playback。

3.1 应用层打开PCM设备

当我们在应用成打开一个PCM设备,比如dev/snd/pcmC0D0p。对于字符设备,当我们进行open操作时,实际上会执行字符设备的文件操作集的open函数,也就是snd_pcm_f_ops[0]中的open函数,即snd_pcm_playback_open;

/*
 *  Register section
 */

const struct file_operations snd_pcm_f_ops[2] = {
        {
                .owner =                THIS_MODULE,
                .write =                snd_pcm_write,
                .write_iter =           snd_pcm_writev,
                .open =                 snd_pcm_playback_open,
                .release =              snd_pcm_release,
                .llseek =               no_llseek,
                .poll =                 snd_pcm_poll,
                .unlocked_ioctl =       snd_pcm_ioctl,
                .compat_ioctl =         snd_pcm_ioctl_compat,
                .mmap =                 snd_pcm_mmap,
                .fasync =               snd_pcm_fasync,
                .get_unmapped_area =    snd_pcm_get_unmapped_area,
        },
        {
                .owner =                THIS_MODULE,
                .read =                 snd_pcm_read,
                .read_iter =            snd_pcm_readv,
                .open =                 snd_pcm_capture_open,
                .release =              snd_pcm_release,
                .llseek =               no_llseek,
                .poll =                 snd_pcm_poll,
                .unlocked_ioctl =       snd_pcm_ioctl,
                .compat_ioctl =         snd_pcm_ioctl_compat,
                .mmap =                 snd_pcm_mmap,
                .fasync =               snd_pcm_fasync,
                .get_unmapped_area =    snd_pcm_get_unmapped_area,
        }
};

下面的时序图展示了应用程序如何最终调用到snd_pcm_f_ops中的回调函数:

下面我绘制了一张流程图更加形象的展示打开PCM设备的过程:

3.1.1 snd_pcm_playback_open

当打开音频文件时,会调用snd_pcm_playback_open函数,定义在sound/core/pcm_native.c;

static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{
        struct snd_pcm *pcm;
        int err = nonseekable_open(inode, file);
        if (err < 0)
                return err;
        pcm = snd_lookup_minor_data(iminor(inode),     // 获取pcm实例
                                    SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
        err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); // 重点
        if (pcm)
                snd_card_unref(pcm->card);
        return err;
}
3.1.2 snd_pcm_open

snd_pcm_open函数实现如下,如果是堵塞模式下,打开PCM设备,将会将当前进程设置为TASK_INTERRUPTIBLE,并调用schedule让出CPU;

static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream)
{
        int err;
        wait_queue_entry_t wait;

        if (pcm == NULL) {
                err = -ENODEV;
                goto __error1;
        }
        err = snd_card_file_add(pcm->card, file);
        if (err < 0)
                goto __error1;
        if (!try_module_get(pcm->card->module)) {
                err = -EFAULT;
                goto __error2;
        }
        init_waitqueue_entry(&wait, current);    // 初始化等待队列元素,并设置private为当前进程
        add_wait_queue(&pcm->open_wait, &wait);  // 向等待队列open_wait添加元素wait
        mutex_lock(&pcm->open_mutex);
        while (1) {
                err = snd_pcm_open_file(file, pcm, stream);
                if (err >= 0)
                        break;
                if (err == -EAGAIN) {
                        if (file->f_flags & O_NONBLOCK) {  // 非堵塞
                                err = -EBUSY;
                                break;
                        }
                } else
                        break;
                set_current_state(TASK_INTERRUPTIBLE); // 设置设置当前进程状态为TASK_INTERRUPTIBLE,在该状态下,进程如果休眠的话可以被信号和wake_up唤醒
                mutex_unlock(&pcm->open_mutex);
                schedule(); //  // 让出CPU,进程进入睡眠,那什么时候会唤醒该进程呢,一般会在xxxxx中调用wake_up_interruptible(&pcm->open_wait)唤醒该进程
                mutex_lock(&pcm->open_mutex);
                if (pcm->card->shutdown) {
                        err = -ENODEV;
                        break;
                }
                if (signal_pending(current)) {  // 如果有信号处理,跳出处理信号,然后便会进入对应的signal处理函数
                        err = -ERESTARTSYS;
                        break;
                }
        }
        remove_wait_queue(&pcm->open_wait, &wait); // 从等待队列移除元素wait
        mutex_unlock(&pcm->open_mutex);
        if (err < 0)
                goto __error;
        return err;

      __error:
        module_put(pcm->card->module);
      __error2:
        snd_card_file_remove(pcm->card, file);
      __error1:
        return err;
}
3.1.3 snd_pcm_open_file

snd_pcm_open_file函数实现如下:

static int snd_pcm_open_file(struct file *file,
                             struct snd_pcm *pcm,
                             int stream)
{
        struct snd_pcm_file *pcm_file;
        struct snd_pcm_substream *substream;
        int err;

        err = snd_pcm_open_substream(pcm, stream, file, &substream); // 重点
        if (err < 0)
                return err;

        pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL);
        if (pcm_file == NULL) {
                snd_pcm_release_substream(substream);
                return -ENOMEM;
        }
        pcm_file->substream = substream;
        if (substream->ref_count == 1)
                substream->pcm_release = pcm_release_private;
        file->private_data = pcm_file;

        return 0;
}
3.1.4 snd_pcm_open_substream

snd_pcm_open_substream函数实现如下:

int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,
                           struct file *file,
                           struct snd_pcm_substream **rsubstream)
{
        struct snd_pcm_substream *substream;
        int err;

        err = snd_pcm_attach_substream(pcm, stream, file, &substream); // 获取一个空闲的pcm substream(substream = pcm->streams[stream]->substream[n])
        if (err < 0)
                return err;
        if (substream->ref_count > 1) {
                *rsubstream = substream;
                return 0;
        }

        err = snd_pcm_hw_constraints_init(substream);
        if (err < 0) {
                pcm_dbg(pcm, "snd_pcm_hw_constraints_init failed\n");
                goto error;
        }

        err = substream->ops->open(substream);  // 重点
        if (err < 0)
                goto error;

        substream->hw_opened = 1;

        err = snd_pcm_hw_constraints_complete(substream);
        if (err < 0) {
                pcm_dbg(pcm, "snd_pcm_hw_constraints_complete failed\n");
                goto error;
        }

        /* automatically set EXPLICIT_SYNC flag in the managed mode whenever
         * the DMA buffer requires it
         */
        if (substream->managed_buffer_alloc &&
            substream->dma_buffer.dev.need_sync)
                substream->runtime->hw.info |= SNDRV_PCM_INFO_EXPLICIT_SYNC;

        *rsubstream = substream;
        return 0;

 error:
        snd_pcm_release_substream(substream);
        return err;
}

pcm subustream的ops(即PCM操作集)在ASoC声卡注册的时候被初始为rtd->ops,其中rtd为pcm runtime;

rtd->ops.open           = soc_pcm_open;
rtd->ops.hw_params      = soc_pcm_hw_params;
rtd->ops.prepare        = soc_pcm_prepare;
rtd->ops.trigger        = soc_pcm_trigger;
rtd->ops.hw_free        = soc_pcm_hw_free;
rtd->ops.close          = soc_pcm_close;
rtd->ops.pointer        = soc_pcm_pointer;

这些函数都定义在sound/soc/soc-pcm.c。

3.1.5 soc_pcm_open

soc_pcm_open定义在sound/soc/soc-pcm.c;

/*
 * Called by ALSA when a PCM substream is opened, the runtime->hw record is
 * then initialized and any private data can be allocated. This also calls
 * startup for the cpu DAI, component, machine and codec DAI.
 */
static int __soc_pcm_open(struct snd_soc_pcm_runtime *rtd,
                          struct snd_pcm_substream *substream)
{
        struct snd_soc_component *component;
        struct snd_soc_dai *dai;
        int i, ret = 0;

        snd_soc_dpcm_mutex_assert_held(rtd);

        for_each_rtd_components(rtd, i, component)
                pinctrl_pm_select_default_state(component->dev);

        ret = snd_soc_pcm_component_pm_runtime_get(rtd, substream);
        if (ret < 0)
                goto err;

        ret = soc_pcm_components_open(substream);
        if (ret < 0)
                goto err;

        ret = snd_soc_link_startup(substream); // 执行rtd->dai_link->ops->startup(substream)
        if (ret < 0)
                goto err;

        /* startup the audio subsystem */
        for_each_rtd_dais(rtd, i, dai) {  // dai = (rtd)->dais[i]
                ret = snd_soc_dai_startup(dai, substream); // dai->driver->ops->startup(substream, dai)
                if (ret < 0)
                        goto err;
        }

        /* Dynamic PCM DAI links compat checks use dynamic capabilities */
        if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm)
                goto dynamic;

        /* Check that the codec and cpu DAIs are compatible */
        soc_pcm_init_runtime_hw(substream);

        soc_pcm_update_symmetry(substream);

        ret = soc_hw_sanity_check(substream);
        if (ret < 0)
                goto err;

        soc_pcm_apply_msb(substream);

        /* Symmetry only applies if we've already got an active stream. */
        for_each_rtd_dais(rtd, i, dai) {
                ret = soc_pcm_apply_symmetry(substream, dai);
                if (ret != 0)
                        goto err;
        }
dynamic:
        snd_soc_runtime_activate(rtd, substream->stream);
        ret = 0;
err:
        if (ret < 0)
                soc_pcm_clean(rtd, substream, 1);

        return soc_pcm_ret(rtd, ret);
}


/* PCM open ops for non-DPCM streams */
static int soc_pcm_open(struct snd_pcm_substream *substream)
{
        struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
        int ret;

        snd_soc_dpcm_mutex_lock(rtd);
        ret = __soc_pcm_open(rtd, substream);
        snd_soc_dpcm_mutex_unlock(rtd);
        return ret;
}

可以看到这里会执行音频数据链路dai_link操作集ops中的startup函数,对应上面代码snd_soc_link_startup(substream);

同时会执行音频数据链路dai driver操作集中的startup函数,对应上面代码snd_soc_dai_startup(dai, substream);也就是在这个阶段会回调cpu dai、codec dai驱动操作集ops中的startup函数。

3.2 应用层调用ioctl函数

当我们在应用程序中调用ioctl函数来进行一些音频参数配置,比如:

ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS,...);

其中:

  • 第一个参数为文件描述符;

  • 第二个参数为命令码,应用程序通过下发命令码来控制驱动程序完成对应操作;

  • 第三个参数…是可变参数arg,一些情况下应用程序需要向驱动程序传参,参数就通过ag来传递。ioctl函数中的…只能传递一个参数,但内核不会检查这个参数的类型。那么,就有两种传参方式:只传一个整数,传递一个指针;

当应用程序调用ioctl函数时,会执行对应驱动程序文件操作集中的unlocked_ioctl、或者compat_ioctl;

long (*unlocked_ioctl) (struct file * fp, unsigned int request, unsigned long args);
long (*compat_ioctl) (struct file * fp, unsigned int request, unsigned long args);

其中:

  • unlocked_ioctl在无大内核锁(BKL)的情况下调用。64位用户程序运行在64位的kernel,或32位的用户程序运行在32位的kernel上,都是调用unlocked_ioctl函数;

  • compat_ioctl是64位系统提供32位ioctl的兼容方法,也在无大内核锁的情况下调用。即如果是32位的用户程序调用64位的kernel,则会调用compat_ioctl。如果驱动程序没有实现compat_ioctl,则用户程序在执行ioctl时会返回错误Not a typewriter;

3.2.1 snd_pcm_ioctl

这里我们以unlocked_ioctl为例,该函数被设置为了snd_pcm_ioctl,函数定义在sound/core/pcm_native.c;

static long snd_pcm_ioctl(struct file *file, unsigned int cmd,
                          unsigned long arg)
{
        struct snd_pcm_file *pcm_file;

        pcm_file = file->private_data;

        if (((cmd >> 8) & 0xff) != 'A')
                return -ENOTTY;

        return snd_pcm_common_ioctl(file, pcm_file->substream, cmd,
                                     (void __user *)arg);
}
3.2.2 snd_pcm_common_ioctl

snd_pcm_common_ioctl函数定义在sound/core/pcm_native.c,该函数实现了各种命令;

static int snd_pcm_common_ioctl(struct file *file,
                                 struct snd_pcm_substream *substream,
                                 unsigned int cmd, void __user *arg)
{
        struct snd_pcm_file *pcm_file = file->private_data;
        int res;

        if (PCM_RUNTIME_CHECK(substream))
                return -ENXIO;

        if (substream->runtime->state == SNDRV_PCM_STATE_DISCONNECTED)
                return -EBADFD;

        res = snd_power_wait(substream->pcm->card);
        if (res < 0)
                return res;

        switch (cmd) {
        case SNDRV_PCM_IOCTL_PVERSION:
                return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0;
        case SNDRV_PCM_IOCTL_INFO:
                return snd_pcm_info_user(substream, arg);
        case SNDRV_PCM_IOCTL_TSTAMP:    /* just for compatibility */
                return 0;
        case SNDRV_PCM_IOCTL_TTSTAMP:
                return snd_pcm_tstamp(substream, arg);
        case SNDRV_PCM_IOCTL_USER_PVERSION:
                if (get_user(pcm_file->user_pversion,
                             (unsigned int __user *)arg))
                        return -EFAULT;
                return 0;
        case SNDRV_PCM_IOCTL_HW_REFINE:
                return snd_pcm_hw_refine_user(substream, arg);
        case SNDRV_PCM_IOCTL_HW_PARAMS:  // 硬件参数配置  对ASoC的各个驱动模块做hw_params()动作
                return  snd_pcm_hw_params_user(substream, arg); // // 触发执行dai->driver->ops->hw_params()、rtd->dai_link->ops->hw_params()
        case SNDRV_PCM_IOCTL_HW_FREE:
                return snd_pcm_hw_free(substream);
        case SNDRV_PCM_IOCTL_SW_PARAMS:  // 软件参数配置
                return snd_pcm_sw_params_user(substream, arg);
        case SNDRV_PCM_IOCTL_STATUS32:
                return snd_pcm_status_user32(substream, arg, false);
        case SNDRV_PCM_IOCTL_STATUS_EXT32:
                return snd_pcm_status_user32(substream, arg, true);
        case SNDRV_PCM_IOCTL_STATUS64:
                return snd_pcm_status_user64(substream, arg, false);
        case SNDRV_PCM_IOCTL_STATUS_EXT64:
                return snd_pcm_status_user64(substream, arg, true);
        case SNDRV_PCM_IOCTL_CHANNEL_INFO:
                return snd_pcm_channel_info_user(substream, arg);
        case SNDRV_PCM_IOCTL_PREPARE:   // 对ASoC的各个驱动模块做prepare()动作
                return snd_pcm_prepare(substream, file);  // 触发执行dai->driver->ops->prepare()、rtd->dai_link->ops->prepare()
        case SNDRV_PCM_IOCTL_RESET:
                return snd_pcm_reset(substream);
        case SNDRV_PCM_IOCTL_START:  // 对ASoC的各个驱动模块做trigger()动作  
                return snd_pcm_start_lock_irq(substream); // 触发执行dai->driver->ops->trigger()、rtd->dai_link->ops->trigger()
        case SNDRV_PCM_IOCTL_LINK:
                return snd_pcm_link(substream, (int)(unsigned long) arg);
        case SNDRV_PCM_IOCTL_UNLINK:
                return snd_pcm_unlink(substream);
        case SNDRV_PCM_IOCTL_RESUME:
                return snd_pcm_resume(substream);
        case SNDRV_PCM_IOCTL_XRUN:
                return snd_pcm_xrun(substream);
        case SNDRV_PCM_IOCTL_HWSYNC:
                return snd_pcm_hwsync(substream);
        case SNDRV_PCM_IOCTL_DELAY:
        {
                snd_pcm_sframes_t delay = 0;
                snd_pcm_sframes_t __user *res = arg;
                int err;

                err = snd_pcm_delay(substream, &delay);
                if (err)
                        return err;
                if (put_user(delay, res))
                        return -EFAULT;
                return 0;
        }
        case __SNDRV_PCM_IOCTL_SYNC_PTR32:
                return snd_pcm_ioctl_sync_ptr_compat(substream, arg);
        case __SNDRV_PCM_IOCTL_SYNC_PTR64:
                return snd_pcm_sync_ptr(substream, arg);
#ifdef CONFIG_SND_SUPPORT_OLD_API
        case SNDRV_PCM_IOCTL_HW_REFINE_OLD:
                return snd_pcm_hw_refine_old_user(substream, arg);
        case SNDRV_PCM_IOCTL_HW_PARAMS_OLD:
                return snd_pcm_hw_params_old_user(substream, arg);
#endif
        case SNDRV_PCM_IOCTL_DRAIN:
                return snd_pcm_drain(substream, file);
        case SNDRV_PCM_IOCTL_DROP:
                return snd_pcm_drop(substream);
        case SNDRV_PCM_IOCTL_PAUSE:
                return snd_pcm_pause_lock_irq(substream, (unsigned long)arg);
        case SNDRV_PCM_IOCTL_WRITEI_FRAMES: //
        case SNDRV_PCM_IOCTL_READI_FRAMES:  //
                return snd_pcm_xferi_frames_ioctl(substream, arg);
        case SNDRV_PCM_IOCTL_WRITEN_FRAMES:
        case SNDRV_PCM_IOCTL_READN_FRAMES:
                return snd_pcm_xfern_frames_ioctl(substream, arg);
        case SNDRV_PCM_IOCTL_REWIND:
                return snd_pcm_rewind_ioctl(substream, arg);
        case SNDRV_PCM_IOCTL_FORWARD:
                return snd_pcm_forward_ioctl(substream, arg);
        }
        pcm_dbg(substream->pcm, "unknown ioctl = 0x%x\n", cmd);
        return -ENOTTY;
}

这里我们仅仅关注SNDRV_PCM_IOCTL_HW_PARAMS命令,该命令的实现函数为snd_pcm_hw_params_user。

此外还有一些很重要的命令比如SNDRV_PCM_IOCTL_SW_PARAMS、SNDRV_PCM_IOCTL_PREPARE、SNDRV_PCM_IOCTL_START我们就不再分析了,感兴趣可以自行研究。

3.2.3 snd_pcm_hw_params_user

snd_pcm_hw_params_user函数定义在sound/core/pcm_native.c;

static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream,
                                  struct snd_pcm_hw_params __user * _params)
{
        struct snd_pcm_hw_params *params;
        int err;

        params = memdup_user(_params, sizeof(*params));
        if (IS_ERR(params))
                return PTR_ERR(params);

        err = snd_pcm_hw_params(substream, params);  // 重点
        if (err < 0)
                goto end;

        if (copy_to_user(_params, params, sizeof(*params)))
                err = -EFAULT;
end:
        kfree(params);
        return err;
}
3.2.4 snd_pcm_hw_params

snd_pcm_hw_params函数定义在在sound/core/pcm_native.c;

static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
                             struct snd_pcm_hw_params *params)
{
        struct snd_pcm_runtime *runtime;
        int err, usecs;
        unsigned int bits;
        snd_pcm_uframes_t frames;

        if (PCM_RUNTIME_CHECK(substream))
                return -ENXIO;
        runtime = substream->runtime;
        err = snd_pcm_buffer_access_lock(runtime);
        if (err < 0)
                return err;
        snd_pcm_stream_lock_irq(substream);
        switch (runtime->state) {
        case SNDRV_PCM_STATE_OPEN:
        case SNDRV_PCM_STATE_SETUP:
        case SNDRV_PCM_STATE_PREPARED:
                if (!is_oss_stream(substream) &&
                    atomic_read(&substream->mmap_count))
                        err = -EBADFD;
                break;
        default:
                err = -EBADFD;
                break;
        }
        snd_pcm_stream_unlock_irq(substream);
        if (err)
                goto unlock;

        snd_pcm_sync_stop(substream, true);

        params->rmask = ~0U;
        err = snd_pcm_hw_refine(substream, params);
        if (err < 0)
                goto _error;

        err = snd_pcm_hw_params_choose(substream, params);
        if (err < 0)
                goto _error;

        err = fixup_unreferenced_params(substream, params);
        if (err < 0)
                goto _error;

        if (substream->managed_buffer_alloc) {
                err = snd_pcm_lib_malloc_pages(substream,
                                               params_buffer_bytes(params));
                if (err < 0)
                        goto _error;
                runtime->buffer_changed = err > 0;
        }
        if (substream->ops->hw_params != NULL) {
                err = substream->ops->hw_params(substream, params);  // 重点,调用了soc_pcm_hw_params
                if (err < 0)
                        goto _error;
        }

        runtime->access = params_access(params);
        runtime->format = params_format(params);
        runtime->subformat = params_subformat(params);
        runtime->channels = params_channels(params);
        runtime->rate = params_rate(params);
        runtime->period_size = params_period_size(params);
        runtime->periods = params_periods(params);
        runtime->buffer_size = params_buffer_size(params);
        runtime->info = params->info;
        runtime->rate_num = params->rate_num;
        runtime->rate_den = params->rate_den;
        runtime->no_period_wakeup =
                        (params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) &&
                        (params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP);

        bits = snd_pcm_format_physical_width(runtime->format);
        runtime->sample_bits = bits;
        bits *= runtime->channels;
        runtime->frame_bits = bits;
        frames = 1;
        while (bits % 8 != 0) {
                bits *= 2;
                frames *= 2;
        }
        runtime->byte_align = bits / 8;
        runtime->min_align = frames;

        /* Default sw params */
        runtime->tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
        runtime->period_step = 1;
        runtime->control->avail_min = runtime->period_size;
        runtime->start_threshold = 1;
        runtime->stop_threshold = runtime->buffer_size;
        runtime->silence_threshold = 0;
        runtime->silence_size = 0;
        runtime->boundary = runtime->buffer_size;
        while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size)
                runtime->boundary *= 2;

        /* clear the buffer for avoiding possible kernel info leaks */
        if (runtime->dma_area && !substream->ops->copy_user) {
                size_t size = runtime->dma_bytes;

                if (runtime->info & SNDRV_PCM_INFO_MMAP)
                        size = PAGE_ALIGN(size);
                memset(runtime->dma_area, 0, size);
        }
        snd_pcm_timer_resolution_change(substream);
        snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP);

        if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req))
                cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
        usecs = period_to_usecs(runtime);
        if (usecs >= 0)
                cpu_latency_qos_add_request(&substream->latency_pm_qos_req,
                                            usecs);
        err = 0;
 _error:
        if (err) {
                /* hardware might be unusable from this time,
                 * so we force application to retry to set
                 * the correct hardware parameter settings
                 */
                snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
                if (substream->ops->hw_free != NULL)
                        substream->ops->hw_free(substream);
                if (substream->managed_buffer_alloc)
                        snd_pcm_lib_free_pages(substream);
        }
 unlock:
        snd_pcm_buffer_access_unlock(runtime);
        return err;
}

pcm subustream的ops(即PCM操作集)在ASoC声卡注册的时候被初始为rtd->ops,其中rtd为pcm runtime;

rtd->ops.open           = soc_pcm_open;
rtd->ops.hw_params      = soc_pcm_hw_params;
rtd->ops.prepare        = soc_pcm_prepare;
rtd->ops.trigger        = soc_pcm_trigger;
rtd->ops.hw_free        = soc_pcm_hw_free;
rtd->ops.close          = soc_pcm_close;
rtd->ops.pointer        = soc_pcm_pointer;

这些函数都定义在sound/soc/soc-pcm.c。

3.2.5 soc_pcm_hw_params

soc_pcm_hw_params定义在sound/soc/soc-pcm.c;

/*
 * Called by ALSA when the hardware params are set by application. This
 * function can also be called multiple times and can allocate buffers
 * (using snd_pcm_lib_* ). It's non-atomic.
 */
static int __soc_pcm_hw_params(struct snd_soc_pcm_runtime *rtd,
                               struct snd_pcm_substream *substream,
                               struct snd_pcm_hw_params *params)
{
        struct snd_soc_dai *cpu_dai;
        struct snd_soc_dai *codec_dai;
        int i, ret = 0;

        snd_soc_dpcm_mutex_assert_held(rtd);

        ret = soc_pcm_params_symmetry(substream, params);
        if (ret)
                goto out;

        ret = snd_soc_link_hw_params(substream, params); // 执行rtd->dai_link->ops->hw_params(substream,params)
        if (ret < 0)
                goto out;

        for_each_rtd_codec_dais(rtd, i, codec_dai) { // 遍历codec dai
                struct snd_pcm_hw_params codec_params;
                unsigned int tdm_mask = snd_soc_dai_tdm_mask_get(codec_dai, substream->stream);

                /*
                 * Skip CODECs which don't support the current stream type,
                 * the idea being that if a CODEC is not used for the currently
                 * set up transfer direction, it should not need to be
                 * configured, especially since the configuration used might
                 * not even be supported by that CODEC. There may be cases
                 * however where a CODEC needs to be set up although it is
                 * actually not being used for the transfer, e.g. if a
                 * capture-only CODEC is acting as an LRCLK and/or BCLK master
                 * for the DAI link including a playback-only CODEC.
                 * If this becomes necessary, we will have to augment the
                 * machine driver setup with information on how to act, so
                 * we can do the right thing here.
                 */
                if (!snd_soc_dai_stream_valid(codec_dai, substream->stream))
                        continue;

                /* copy params for each codec */
                codec_params = *params;

                /* fixup params based on TDM slot masks */
                if (tdm_mask)
                        soc_pcm_codec_params_fixup(&codec_params, tdm_mask);

                ret = snd_soc_dai_hw_params(codec_dai, substream,
                                            &codec_params); // codec_dai->driver->ops->hw_params(substream, params, codec_dai)
                if(ret < 0)
                        goto out;

                soc_pcm_set_dai_params(codec_dai, &codec_params);
                snd_soc_dapm_update_dai(substream, &codec_params, codec_dai);
        }

        for_each_rtd_cpu_dais(rtd, i, cpu_dai) {    // cpu dai
                /*
                 * Skip CPUs which don't support the current stream
                 * type. See soc_pcm_init_runtime_hw() for more details
                 */
                if (!snd_soc_dai_stream_valid(cpu_dai, substream->stream))
                        continue;

                ret = snd_soc_dai_hw_params(cpu_dai, substream, params); // cpu_dai->driver->ops->hw_params(substream, params, cpu_dai)
                if (ret < 0)
                        goto out;

                /* store the parameters for each DAI */
                soc_pcm_set_dai_params(cpu_dai, params);
                snd_soc_dapm_update_dai(substream, params, cpu_dai);
        }

        ret = snd_soc_pcm_component_hw_params(substream, params);
out:
        if (ret < 0)
                soc_pcm_hw_clean(rtd, substream, 1);

        return soc_pcm_ret(rtd, ret);
}


/* hw_params PCM ops for non-DPCM streams */
static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
                             struct snd_pcm_hw_params *params)
{
        struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
        int ret;

        snd_soc_dpcm_mutex_lock(rtd);
        ret = __soc_pcm_hw_params(rtd, substream, params);
        snd_soc_dpcm_mutex_unlock(rtd);
        return ret;
}

可以看到这里会执行音频数据链路dai_link操作集ops中的hw_params函数,对应上面代码snd_soc_link_hw_params(substream, params);

同时会执行音频数据链路dai driver操作集中的hw_params函数,对应上面代码snd_soc_dai_hw_params(dai, substream,params);也就是在这个阶段会回调cpu dai、codec dai驱动操作集ops中的hw_params函数。

3.2.6 总结

经过一遍分析,我们大概可以得到ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS,params)函数调用栈;

ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS,params);
    snd_pcm_ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS,params);
        snd_pcm_hw_params_user(substream, params)
            snd_pcm_hw_params(substream, params)
                substream->ops->hw_params(substream, params)
                    soc_pcm_hw_params(substream, params)
                        rtd->dai_link->ops->hw_params(substream, params)
                        codec_dai->driver->ops->hw_params(dai, substream,params)
                        cpu_dai->driver->ops->hw_params(dai, substream,params)

参考文章

[1] RK3399 探索之旅 / Audio 驱动层速读

[2] Linux ALSA音频驱动之一:框架概述

[3] Linux音频子系统

[4] http://www.alsa-project.org/

[5] Linux ALSA驱动之二:声卡的创建流程

[6] Linux内核中的链表——struct list_head

[7] Linux ALSA 音频系统:物理链路篇

[8] Sound Subsystem Documentation

[9] alsa pcm函数调用流程

[10] Linux ALSA声卡驱动之三:PCM设备的创建

[11] alsa pcm函数调用流程

posted @ 2023-06-15 23:47  大奥特曼打小怪兽  阅读(1034)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步