1. 程式人生 > >linux音訊子系統 - pcm裝置

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(&register_mutex);
    err = snd_pcm_add(pcm);
    if (err) {
        mutex_unlock(&register_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(&register_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(&register_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