ALSA學習筆記 (3)PCM

1. 概述

每個pcm實例對應一個pcm設備文件。一個pcm實例由一個playback stream和一個capture stream組成,而每個 pcm stream由一個或多個pcm子流組成。當一個子流已經存在,並且已經被打開,當再次被打開的時候,會被阻塞。
相關結構體:
snd_pcm: 是掛在snd_card下面的一個snd_device
snd_pcm_str streams[2]: 該數組中的兩個元素指向兩個snd_pcm_str結構,分別代表playback stream和capture stream, 其中用戶空間中每個PCM設備對應一個pcm 流,設備號相同的設備對應成對的兩個pcm流。
snd_pcm_substream: snd_pcm_str中的substream字段,指向snd_pcm_substream結構,snd_pcm_substream是pcm中間層的核心,絕大部分任務都是在substream中處理,尤其是他的 snd_pcm_ops ops字段,許多user空間的應用程序通過alsa-lib對驅動程序的請求都是由該結構中的函數處理。
runtime字段則指向snd_pcm_runtime結構, snd_pcm_runtime記錄這substream的一些重要的軟件和硬件運行環境和參數。

2. 示例代碼

2.1 創建pcm實例

struct snd_pcm *pcm;
snd_pcm_new(chip->card, “My Chip”, 0 , 1, 1,&pcm)

2.2 設置PCM設備的操作函數

snd_pcm_set_ops(pcm,SNDRV_PCM_STREAM_PLAYBACK, &snd_mychip_playback_ops);
snd_pcm_set_ops(pcm,SNDRV_PCM_STREAM_CAPTURE, &snd_mychip_capture_ops);

2.3 定義PCM的操作函數

/* playback 操作函數*/
static struct snd_pcm_ops snd_mychip_playback_ops = {
    .open = snd_mychip_playback_open,
    .close = snd_mychip_playback_close,
    // .................
};
/* capture 操作函數*/
static struct snd_pcm_ops snd_mychip_capture_ops = {
    .open = snd_mychip_capture_open,
    .close = snd_mychip_capture_close,
    // .................
};
/* open 函數中需要傳入一個定義好的硬件參數*/
static int snd_mychip_playback_open(struct snd_pcm_substream *substream)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    runtime->hw = snd_mychip_playback_hw;
}

2.4 定義硬件參數

static struct snd_pcm_hardware snd_mychip_playback_hw = {
    .info = (SNDRV_PCM_INFO_MMAP |
            SNDRV_PCM_INFO_INTERLEAVED |
            SNDRV_PCM_INFO_BLOCK_TRANSFER |
            SNDRV_PCM_INFO_MMAP_VALID),
    .formats = SNDRV_PCM_FORMAT_S16_LE,
    .rates = SNDRV_PCM_RATE_8000_48000,
    .rate_min = 8000,
    .rate_max = 48000,
    .channels_min = 2,
    .channels_max = 2,
    .buffer_bytes_max = 32768,
    .period_bytes_min = 4096,
    .period_bytes_max = 32768,
    .periods_min = 1,
    .periods_max = 1024,
};
static struct snd_pcm_hardware snd_mychip_capture_hw = {...};

3. 創建pcm實例代碼分析

我們可以通過snd_pcm_new創建一個PCM實例,其中傳入的參數包括
card: 所屬的聲卡
id: PCM實例的ID(名字)
device: PCM實例的編號
playback_count: PCM播放流中子流的個數
capture_count: PCM錄音流中子流的個數
rpcm: 返回的PCM實例

具體的創建的流程

  1. 邏輯設備的操作回調函數結構體
  2. 爲snd_pcm結構體分配空間,根據傳入參數賦值
  3. 根據傳入的playback和capture的個數創建PCM流 snd_pcm_str
    3.1 根據傳入的參數,爲PCM流(snd_pcm_str)賦值:方向,所屬的PCM,PCM子流的個數
    3.2 根據傳入子流的個數,爲PCM流創建子流
    爲子流分配空間,賦值(包括pcm,pcm流,ID, 方向…),添加子流到子流的鏈表,內部結構體初始化
  4. 創建一個PCM邏輯設備,創建邏輯設備,並添加到邏輯設備鏈表

具體的代碼分析如下:

int snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, struct snd_pcm **rpcm)

// 直接調用函數_snd_pcm_new,參數internal傳入false
_snd_pcm_new(card, id, device, playback_count, capture_count, false, rpcm);
		struct snd_pcm *pcm;

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)
	// 1. 邏輯設備的操作函數結構體, 主要用於註冊子設備
	static struct snd_device_ops ops = {
		.dev_free = snd_pcm_dev_free,
		.dev_register =	snd_pcm_dev_register,
		.dev_disconnect = snd_pcm_dev_disconnect,
	};
	// 2. 爲snd_pcm結構體分配空間,根據傳入參數賦值
	pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
	pcm->card = card;
	pcm->device = device;
	pcm->internal = internal; 
	strlcpy(pcm->id, id, sizeof(pcm->id));
	// 3. 根據傳入的playback和capture的個數創建PCM流 snd_pcm_str
	snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)
	snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)
		snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
		// stream = SNDRV_PCM_STREAM_PLAYBACK; substream_count = playback_count;
		// 3.1 根據傳入的參數,爲PCM流(snd_pcm_str)賦值:方向,所屬的PCM,PCM子流的個數
		struct snd_pcm_str *pstr = &pcm->streams[stream];
		struct snd_pcm_substream *substream, *prev;
		pstr->stream = stream;
		pstr->pcm = pcm;
		pstr->substream_count = substream_count;
		//proc
		snd_pcm_stream_proc_init(pstr);
		// 3.2 根據傳入子流的個數,爲PCM流創建子流
		for (idx = 0, prev = NULL; idx < substream_count; idx++)
			// 爲子流分配空間,賦值(pcm,pcm流,ID, 方向.....)
			substream = kzalloc(sizeof(*substream), GFP_KERNEL);
			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;
			//proc
			snd_pcm_substream_proc_init(substream);
			//結構體初始化
			substream->group = &substream->self_group;
			spin_lock_init(&substream->self_group.lock);
			INIT_LIST_HEAD(&substream->self_group.substreams);
			list_add_tail(&substream->link_list, &substream->self_group.substreams);

			atomic_set(&substream->mmap_count, 0);
			prev = substream;
	mutex_init(&pcm->open_mutex);
	init_waitqueue_head(&pcm->open_wait);
	// 4. 創建一個PCM邏輯設備,創建邏輯設備,並添加到邏輯設備鏈表
	snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)

5 PCM邏輯設備的註冊

當聲卡被註冊時,會註冊所有的邏輯設備。主要的工作是創建PCM設備節點
具體的流程:

  1. 添加pcm結構體到全局鏈表snd_pcm_devices
  2. 確定PCM設備節點名字
  3. 創建一個snd_minor,並添加到全局結構體 snd_minors
  4. 註冊一個設備節點
static int snd_pcm_dev_register(struct snd_device *device)
	//1. 添加pcm結構體到全局鏈表snd_pcm_devices
	struct snd_pcm *pcm = device->device_data;
	snd_pcm_add(pcm);

	for (cidx = 0; cidx < 2; cidx++) {
		//2. 確定PCM設備節點名字
		switch (cidx) {
		case SNDRV_PCM_STREAM_PLAYBACK:
			sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
			devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
			break;
		case SNDRV_PCM_STREAM_CAPTURE:
			sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
			devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
			break;
		}
		// 註冊創建PCM設備節點
		snd_register_device_for_dev(devtype, pcm->card, pcm->device, 
						&snd_pcm_f_ops[cidx], pcm, str, dev);
			//3 創建一個snd_minor,並添加到全局結構體 snd_minors
			struct snd_minor *preg = kmalloc(sizeof *preg, GFP_KERNEL);
			preg->type = type;
			preg->card = card ? card->number : -1;
			preg->device = dev;
			preg->f_ops = f_ops;
			preg->private_data = private_data;
			preg->card_ptr = card;
			snd_minors[minor] = preg;
			//4 註冊一個設備節點
			minor = snd_find_free_minor(type);
			preg->dev = device_create(sound_class, device, MKDEV(major, minor),
				  private_data, "%s", name);

6 PCM信息運行時指針

打開一個 PCM 子流的時候,PCM 運行時實例就會分配給這個子流。這個指針可以通過 substream->runtime 獲得。指針擁有多種信息:hw_params 和 sw_params 的配置的拷貝,緩衝區指針,mmap 記錄,自旋鎖。大部分的驅動程序操作集的函數來說是隻讀,我們可以在定義操作函數的時候訪問這些成員。我們下面具體分析各個成員:

6.1 硬件描述

包含了基本硬件配置的定義,需要在 open 的時候對它們進行定義。
runtime 實例擁有這個描述符的拷貝而不是已經存在的描述符的指針。

Struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw = snd_mychip_playback_hw; /*通用定義*/

典型的硬件描述如下:

static struct snd_pcm_hardware snd_mychip_playback_hw = {
	.info = (SNDRV_PCM_INFO_MMAP |
			SNDRV_PCM_INFO_INTERLEAVED |
			SNDRV_PCM_INFO_BLOCK_TRANSFER |
			SNDRV_PCM_INFO_MMAP_VALID),
	.formats = SNDRV_PCM_FORMAT_S16_LE,
	.rates = SNDRV_PCM_RATE_8000_48000,
	.rate_min = 8000,
	.rate_max = 48000,
	.channels_min = 2,
	.channels_max = 2,
	.buffer_bytes_max = 32768,
	.period_bytes_min = 4096,
	.period_bytes_max = 32768,
	.periods_min = 1,
	.periods_max = 1024,
};

info 字段包含 pcm 的類型和能力
formats 字段包含了支持格式的標誌位(SNDRV_PCM_FMTBIT_XXX)
rates 字段包含了支持的採樣率(SNDRV_PCM_RATE_XXX)
rate_min 和 rate_max 定義了最小和最大的採樣率。應該和採樣率相對應。
channel_min 和 channel_max 定義了最大和最小的通道,以前可能你已看到。
buffer_bytes_max 定義了以字節爲單位的最大的緩衝區大小

6.2 運行狀態

可以通過 runtime->status 來獲得運行狀態

6.3 私有數據

runtime->private_data是在 PCM open 的時候指向一個動態數據

7 結構圖

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章