linux音訊子系統 - pcm裝置
1.pcm裝置
脈衝編碼調製(Pulse Code Modulation,PCM),就是把一個時間連續,取值連續的模擬訊號變換成時間離散,取值離散的數字訊號後在通道中傳輸,這是基本原理。
根據此原理,在音訊領域的數字音訊就用pcm裝置來代表,pcm也是一種音訊格式,可以自定義通道數,取樣率,取樣精度;我們經常採用的I2S格式其實屬於pcm的一種,不過I2S規定了只有2通道。
音訊的取樣率(rate)一般採用44.1K,16K,48K等,取樣精度(format)一般都是8/16/24/32bit
在ALSA框架中,pcm就是控制音訊流的,區別於control
2.PCM裝置結構體
這部分重要的結構體主要有:
- struct snd_pcm
- struct snd_pcm_str
- struct snd_pcm_substream
這三者的關係可以用下圖來表示:
一個音訊裝置分播放和錄音兩個功能,對應到pcm就分PLAYBACK和CAPTURE,分別用結構體snd_pcm_str來表示,一個播放或者錄音裝置可以整合多個音訊流,每個音訊流用snd_pcm_substream結構體來表示
這三個結構體的邏輯連結關係如下圖:
3.pcm設備註冊
pcm設備註冊函式為:
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裝置加入到card中,然後card在註冊的時候呼叫pcm的註冊函式,把pcm註冊到系統中
3.1 建立pcm裝置,加入到card中
(sound/core/pcm.c)
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 struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register,--------------pcm註冊函式(card註冊時呼叫)
.dev_disconnect = snd_pcm_dev_disconnect,
};
if (snd_BUG_ON(!card))
return -ENXIO;
if (rpcm)
*rpcm = NULL;
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
if (pcm == NULL) {
snd_printk(KERN_ERR "Cannot allocate PCM\n");
return -ENOMEM;
}
pcm->card = card;
pcm->device = device;
pcm->internal = internal;
if (id)
strlcpy(pcm->id, id, sizeof(pcm->id));
# snd_pcm_new_stream主要是初始化snd_pcm_substream結構體
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
mutex_init(&pcm->open_mutex);
init_waitqueue_head(&pcm->open_wait);
if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {-----將pcm裝置加入到card的devices連結串列中
snd_pcm_free(pcm);
return err;
}
if (rpcm)
*rpcm = pcm;
return 0;
}
3.2 進行pcm裝置的註冊
(sound/core/pcm.c)
static int snd_pcm_dev_register(struct snd_device *device)
{
int cidx, err;
struct snd_pcm_substream *substream;
struct snd_pcm_notify *notify;
char str[16];
struct snd_pcm *pcm;
struct device *dev;
if (snd_BUG_ON(!device || !device->device_data))
return -ENXIO;
pcm = device->device_data;
mutex_lock(®ister_mutex);
err = snd_pcm_add(pcm);
if (err) {
mutex_unlock(®ister_mutex);
return err;
}
for (cidx = 0; cidx < 2; cidx++) {
int devtype = -1;
if (pcm->streams[cidx].substream == NULL || pcm->internal)
continue;
switch (cidx) {
case SNDRV_PCM_STREAM_PLAYBACK:-------------為device命名
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;
}
/* device pointer to use, pcm->dev takes precedence if
* it is assigned, otherwise fall back to card's device
* if possible */
dev = pcm->dev;
if (!dev)
dev = snd_card_get_device_link(pcm->card);
/* register pcm */
err = snd_register_device_for_dev(devtype, pcm->card,
pcm->device,
&snd_pcm_f_ops[cidx],-----pcm裝置檔案操作函式
pcm, str, dev);
if (err < 0) {
list_del(&pcm->list);
mutex_unlock(®ister_mutex);
return err;
}
snd_add_device_sysfs_file(devtype, pcm->card, pcm->device,
&pcm_attrs);-------pcm設備註冊
for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
snd_pcm_timer_init(substream);
}
list_for_each_entry(notify, &snd_pcm_notify_list, list)
notify->n_register(pcm);
mutex_unlock(®ister_mutex);
return 0;
}
4.pcm檔案操作ops
pcm這塊比較難的一點就是這些操作函數了,各種ioctl設定的引數需要對音訊技術這塊有深入瞭解,本文就不介紹了,因為core層的東西對於驅動開發來說很少改動(或者基本不改動),遇到問題再解決吧
(sound/core/pcm_native.c)
const struct file_operations snd_pcm_f_ops[2] = {
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
.aio_write = snd_pcm_aio_write,
.open = snd_pcm_playback_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_playback_poll,
.unlocked_ioctl = snd_pcm_playback_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,
.aio_read = snd_pcm_aio_read,
.open = snd_pcm_capture_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_capture_poll,
.unlocked_ioctl = snd_pcm_capture_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
}
};
4.1 open函式
snd_pcm_capture_open和snd_pcm_playback_open函式最後都會呼叫snd_pcm_open,這裡不詳細介紹open的過程了,只介紹下這邊引出來的另一個結構體:snd_pcm_runtime
這個結構體只是在執行的時候會動態建立,具體可以參考函式snd_pcm_attach_substream
此結構體主要是設定各種引數,儲存執行時的狀態等
struct snd_pcm_runtime {
/* -- Status -- */
struct snd_pcm_substream *trigger_master;
struct timespec trigger_tstamp; /* trigger timestamp */
int overrange;
snd_pcm_uframes_t avail_max;
snd_pcm_uframes_t hw_ptr_base; /* Position at buffer restart */
snd_pcm_uframes_t hw_ptr_interrupt; /* Position at interrupt time */
unsigned long hw_ptr_jiffies; /* Time when hw_ptr is updated */
unsigned long hw_ptr_buffer_jiffies; /* buffer time in jiffies */
snd_pcm_sframes_t delay; /* extra delay; typically FIFO size */
u64 hw_ptr_wrap; /* offset for hw_ptr due to boundary wrap-around */
/* -- HW params -- */
snd_pcm_access_t access; /* access mode */
snd_pcm_format_t format; /* SNDRV_PCM_FORMAT_* */
snd_pcm_subformat_t subformat; /* subformat */
unsigned int rate; /* rate in Hz */
unsigned int channels; /* channels */
snd_pcm_uframes_t period_size; /* period size */
unsigned int periods; /* periods */
snd_pcm_uframes_t buffer_size; /* buffer size */
snd_pcm_uframes_t min_align; /* Min alignment for the format */
size_t byte_align;
unsigned int frame_bits;
unsigned int sample_bits;
unsigned int info;
unsigned int rate_num;
unsigned int rate_den;
unsigned int no_period_wakeup: 1;
/* -- SW params -- */
int tstamp_mode; /* mmap timestamp is updated */
unsigned int period_step;
snd_pcm_uframes_t start_threshold;
snd_pcm_uframes_t stop_threshold;
snd_pcm_uframes_t silence_threshold; /* Silence filling happens when
noise is nearest than this */
snd_pcm_uframes_t silence_size; /* Silence filling size */
snd_pcm_uframes_t boundary; /* pointers wrap point */
snd_pcm_uframes_t silence_start; /* starting pointer to silence area */
snd_pcm_uframes_t silence_filled; /* size filled with silence */
union snd_pcm_sync_id sync; /* hardware synchronization ID */
/* -- mmap -- */
struct snd_pcm_mmap_status *status;
struct snd_pcm_mmap_control *control;
/* -- locking / scheduling -- */
snd_pcm_uframes_t twake; /* do transfer (!poll) wakeup if non-zero */
wait_queue_head_t sleep; /* poll sleep */
wait_queue_head_t tsleep; /* transfer sleep */
struct fasync_struct *fasync;
/* -- private section -- */
void *private_data;
void (*private_free)(struct snd_pcm_runtime *runtime);
/* -- hardware description -- */
struct snd_pcm_hardware hw;
struct snd_pcm_hw_constraints hw_constraints;
/* -- interrupt callbacks -- */
void (*transfer_ack_begin)(struct snd_pcm_substream *substream);
void (*transfer_ack_end)(struct snd_pcm_substream *substream);
/* -- timer -- */
unsigned int timer_resolution; /* timer resolution */
int tstamp_type; /* timestamp type */
/* -- DMA -- */
unsigned char *dma_area; /* DMA area */
dma_addr_t dma_addr; /* physical bus address (not accessible from main CPU) */
size_t dma_bytes; /* size of DMA area */
struct snd_dma_buffer *dma_buffer_p; /* allocated buffer */
#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
/* -- OSS things -- */
struct snd_pcm_oss_runtime oss;
#endif
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
struct snd_pcm_hwptr_log *hwptr_log;
#endif
};
5.change log
date | content | linux |
---|---|---|
2017.12.8 | origin | linux3.10 |