linux音频子系统

来源:互联网 发布:淘宝代销上架编辑技巧 编辑:程序博客网 时间:2024/06/05 04:00

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
原创粉丝点击