在介紹PCM 之前,我們先給出創建PCM實例的框架。
#include <sound/pcm.h>
....
/* hardware definition */
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_FMTBIT_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,
};
/* hardware definition */
static struct snd_pcm_hardware snd_mychip_capture_hw = {
.info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FMTBIT_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,
};
/* open callback */
static int snd_mychip_playback_open(struct snd_pcm_substream *substream)
{
struct mychip *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw = snd_mychip_playback_hw;
/* more hardware-initialization will be done here */
....
return 0;
}
/* close callback */
static int snd_mychip_playback_close(struct snd_pcm_substream *substream)
{
struct mychip *chip = snd_pcm_substream_chip(substream);
/* the hardware-specific codes will be here */
....
return 0;
}
/* open callback */
static int snd_mychip_capture_open(struct snd_pcm_substream *substream)
{
struct mychip *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw = snd_mychip_capture_hw;
/* more hardware-initialization will be done here */
....
return 0;
}
/* close callback */
static int snd_mychip_capture_close(struct snd_pcm_substream *substream)
{
struct mychip *chip = snd_pcm_substream_chip(substream);
/* the hardware-specific codes will be here */
....
return 0;
}
/* hw_params callback */
static int snd_mychip_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
return snd_pcm_lib_malloc_pages(substream,
params_buffer_bytes(hw_params));
}
/* hw_free callback */
static int snd_mychip_pcm_hw_free(struct snd_pcm_substream *substream)
{
return snd_pcm_lib_free_pages(substream);
}
/* prepare callback */
static int snd_mychip_pcm_prepare(struct snd_pcm_substream *substream)
{
struct mychip *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
/* set up the hardware with the current configuration
* for example...
*/
mychip_set_sample_format(chip, runtime->format);
mychip_set_sample_rate(chip, runtime->rate);
mychip_set_channels(chip, runtime->channels);
mychip_set_dma_setup(chip, runtime->dma_addr,
chip->buffer_size,
chip->period_size);
return 0;
}
/* trigger callback */
static int snd_mychip_pcm_trigger(struct snd_pcm_substream *substream,
int cmd)
{
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
/* do something to start the PCM engine */
....
break;
case SNDRV_PCM_TRIGGER_STOP:
/* do something to stop the PCM engine */
....
break;
default:
return -EINVAL;
}
}
/* pointer callback */
static snd_pcm_uframes_t
snd_mychip_pcm_pointer(struct snd_pcm_substream *substream)
{
struct mychip *chip = snd_pcm_substream_chip(substream);
unsigned int current_ptr;
/* get the current hardware pointer */
current_ptr = mychip_get_hw_pointer(chip);
return current_ptr;
}
/* operators */
static struct snd_pcm_ops snd_mychip_playback_ops = {
.open = snd_mychip_playback_open,
.close = snd_mychip_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_mychip_pcm_hw_params,
.hw_free = snd_mychip_pcm_hw_free,
.prepare = snd_mychip_pcm_prepare,
.trigger = snd_mychip_pcm_trigger,
.pointer = snd_mychip_pcm_pointer,
};
/* operators */
static struct snd_pcm_ops snd_mychip_capture_ops = {
.open = snd_mychip_capture_open,
.close = snd_mychip_capture_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_mychip_pcm_hw_params,
.hw_free = snd_mychip_pcm_hw_free,
.prepare = snd_mychip_pcm_prepare,
.trigger = snd_mychip_pcm_trigger,
.pointer = snd_mychip_pcm_pointer,
};
/*
* definitions of capture are omitted here...
*/
/* create a pcm device */
static int snd_mychip_new_pcm(struct mychip *chip)
{
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(chip->card, "My Chip", 0, 1, 1, &pcm);
if (err < 0)
return err;
pcm->private_data = chip;
strcpy(pcm->name, "My Chip");
chip->pcm = pcm;
/* set operators */
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);
/* pre-allocation of buffers */
/* NOTE: this may fail */
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data(chip->pci),
64*1024, 64*1024);
return 0;
}
1.創建pcm實例
ALSA driver為我們提供接口snd_pcm_new來創建PCM實例。但是我們最好是寫一個如上述snd_mychip_new_pcm的函數來來對構建pcm實例的過程進行封裝。
/**
* 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
*/
int snd_pcm_new(struct snd_card *card, const char *id, int device, int playback_count, int capture_count, struct snd_pcm **rpcm)
第三個參數表示新創建的PCM實例的index(0,1,2,3).可以在一個card上創建多個PCM 實例。每一個PCM又可以包含多個substream.如果芯片支持多路播放,那么將有多個substream.每次open/close都作用於某個substream.在創建PCM的substream時就指定了number(0~playback_count).當App在調用alsa lib API:snd_pcm_open時,alsa core通過snd_pcm_attach_substream函數來open一個空閑的substream.
2.設置PCM的操作函數
創建完PCM函數后,就可設置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);
操作函數即我們寫driver時需要實現的功能,以供alsa-core調用。ALSA PCM的操作函數包括:
static struct snd_pcm_ops snd_mychip_playback_ops = {
.open = snd_mychip_pcm_open,
.close = snd_mychip_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_mychip_pcm_hw_params,
.hw_free = snd_mychip_pcm_hw_free,
.prepare = snd_mychip_pcm_prepare,
.trigger = snd_mychip_pcm_trigger,
.pointer = snd_mychip_pcm_pointer,
};
每個函數都包含一個snd_pcm_substream 的指針,指向當前操作的substream.
在上面的例子中,每個操作函數里面都包含如下宏調用:
其中返回的是substream->private_data,sustream的private_data是pcm->private_data的一份拷貝。拷貝動作是在snd_pcm_open時調用的snd_pcm_attach_substream中進行。一般來說pcm的private_data是芯片專用數據,當然我們也可以overwrite以保存別的數據。
2.1 open
當open PCM的一路substream時,open函數就會被調用。
static int snd_xxx_open(struct snd_pcm_substream *substream)
{
struct mychip *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw = snd_mychip_playback_hw;
return 0;
}
在open函數內,至少應該初始化此substream的runtime->hw結構體.snd_mychip_playback_hw是預先定義的硬件描述。
也可以在open函數里為substream分配private_data.如下:
data = kmalloc(sizeof(*data), GFP_KERNEL); substream->runtime->private_data = data;
如果芯片所支持的sample rate,samples等硬件配置有限制,也可以在open函數內設置限制。
2.2 close
當PCM的substream close時就會調用到close 函數。
如果有在open函數內分配了runtime的private_data, 那private data在close函數釋放。
static int snd_xxx_close(struct snd_pcm_substream *substream)
{
....
kfree(substream->runtime->private_data);
....
}
2.4 hw_params
當App在設置substream的buffer size, the period size, the format等硬件參數時,將會調用到hw_params函數。
在hw_params函數中可以設置許多的硬件參數,包括buffer的分配。buffer分配:
snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
2.5 hw_free
hw_free用來釋放在hw_params中釋放的資源,如buffer.hw_free總是在close之前調用。
2.6 prepare
當app在調用alsa lib API:snd_pcm_prepare時,prepare函數將被調用,在此函數中可以設置format type, sample rate等參數。與在hw_params中設置參數不同的是每次app調用snd_pcm_prepare時都會去設置參數,而snd_pcm_prepare可能是在recovery undrrun時調用。
prepare函數並非原子操作,因此必須使用 schedule-related functions保證安全性。
2.7 trigger
當PCM在start,stop,pause時,會調用到trigger函數。
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
/* do something to start the PCM engine */
break;
case SNDRV_PCM_TRIGGER_STOP:
/* do something to stop the PCM engine */
break;
default:
return -EINVAL;
}
SNDRV_PCM_TRIGGER_XXX 定義在 <sound/pcm.h>. 至少我們應該在trigger函數中實現 START 和STOP commands。
當pcm支持pause操作時,必須實現 PAUSE_PUSH and PAUSE_RELEASE commands,PAUSE_PUSH用來pause pcm,PAUSE_RELEASE用來restart pcm.
trigger函數是atomic 的,因此在其中的操作越少越好,通常只用來trigger DMA.
2.8 pointer
當PCM middleware 層(alsa-core)需要獲取當前的硬件指針(hardware position)時,就會調用pointer函數。pointer函數需要返回以frame為單位的hardware position(0~buffersize-1).
pointer通常在buffer-update 過程中調用,由中斷函數中的snd_pcm_period_elapsed觸發。即每次硬件中斷,就會調用snd_pcm_period_elapsed函數來通知alsa-core來讀取當前的hardware position,計算buffer中空余空間,喚醒sleep的polling thread.
