1.pcm设备

脉冲编码调制(Pulse Code Modulation,PCM),就是把一个时间连续,取值连续的模拟信号变换成时间离散,取值离散的数字信号后在信道中传输,这是基本原理。

根据此原理,在音频领域的数字音频就用pcm设备来代表,pcm也是一种音频格式,可以自定义通道数,采样率,采样精度;我们经常采用的I2S格式其实属于pcm的一种,不过I2S规定了只有2通道。

音频的采样率(rate)一般采用44.1K,16K,48K等,采样精度(format)一般都是8/16/24/32bit

android 调整pcm采样时间 pcm的采样速率_linux音频

在ALSA框架中,pcm就是控制音频流的,区别于control

2.PCM设备结构体

这部分重要的结构体主要有:

  • struct snd_pcm
  • struct snd_pcm_str
  • struct snd_pcm_substream

这三者的关系可以用下图来表示:

android 调整pcm采样时间 pcm的采样速率_linux音频_02

一个音频设备分播放和录音两个功能,对应到pcm就分PLAYBACK和CAPTURE,分别用结构体snd_pcm_str来表示,一个播放或者录音设备可以集成多个音频流,每个音频流用snd_pcm_substream结构体来表示

这三个结构体的逻辑链接关系如下图:

android 调整pcm采样时间 pcm的采样速率_音频子系统_03

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