零基础入门多媒体音频(6)-alsa(2)

本文详细解释了LinuxALSA中的PCM中间层在音频驱动中的作用,涉及硬件定义、子流管理、回调函数和操作结构的设置,以及如何通过SNDRV_PCM_INFO标志位描述硬件特性。
摘要由CSDN通过智能技术生成

PCM接口
ALSA的PCM中间层非常有用,每个驱动只需要实现底层的功能来访问硬件。要使用PCM层,你需要先引用 <sound/pcm.h>头文件。此外,如果你要使用和hw_param相关的函数,<sound/pcm_params.h>也是必须的。

每个声卡设备最多拥有4个PCM实例。一个PCM实例对应一个PCM设备文件。实例数量的约束来自linux设备序号可用的位大小。当64bit的设备序号开始使用时,我们就可以支持更多的PCM实例。

PCM实例包含PCM回放和录制流。每个PCM流包含一个或更多的PCM子流。一些声卡支持多路回放功能。比如,emu10k1 支持32路立体声播放子流。这种情况下,每次打开动作,一路空闲的子流会被自动选择并打开。如果只有一路子流存在并且已经打开过,那么后续的打开动作将会阻塞或者返回错误码EAGAIN 。但在驱动中不需要关注这些细节。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)
{
        /* the hardware-specific codes will be here */
        ....
        return 0;
}

/* hw_free callback */
static int snd_mychip_pcm_hw_free(struct snd_pcm_substream *substream)
{
        /* the hardware-specific codes will be here */
        ....
        return 0;
}

/* 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,
        .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,
        .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_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
                                       &chip->pci->dev,
                                       64*1024, 64*1024);
        return 0;
}

调用snd_pcm_new()函数可以获得PCM实例。可以为PCM定义一个构造函数,比如说:


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;
        ...
        return 0;
}

snd_pcm_new()函数包含六个参数。第一个参数时PCM要绑定的声卡。也就是上篇文章提到并获得的snd_card实例。第二个参数是ID字符串。第三个参数是新PCM的索引。第四个和第五个参数是播放和录制子流的个数。当没有播放和录制子流可用的时候,对应的参数传0.

如果芯片支持多路播放或者录制子流,你可以指定更大的数。但在open,close,回调等函数内部要做好处理。当你需要知道在处理哪个子流时,你可以从每个回调函数的参数中获取,参考下面的示例:
struct snd_pcm_substream *substream;
int index = substream->number;

当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);

一个典型的操作结构体如下:

static struct snd_pcm_ops snd_mychip_playback_ops = {
        .open =        snd_mychip_pcm_open,
        .close =       snd_mychip_pcm_close,
        .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,
};


操作结构体的包含了所有回调函数。
设置完操作结构体后,可以预分配缓冲区(buffer)并设置管理分配模式。执行下面的代码即可。
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
                               &chip->pci->dev,
                               64*1024, 64*1024);


这段代码会默认会分配最多64kB的buffer。另外,pcm->info_flags还可以包含更多的信息。 <sound/asound.h>中定义的SNDRV_PCM_INFO_XXX类型的宏都可以添加到flag中。

运行时指针---------PCM的主要信息
当PCM子流被打开,一个PCM运行时就被创建好并分配给子流。通过 substream->runtime就可以访问了。你要用来控制PCM的信息都可以通过运行时指针获得。Hw_params和sw_params配置的拷贝,缓冲区指针。Mmap记录。,自旋锁等。

<sound/pcm.h>文件定义了运行时实例。下面是截取的一部分代码。

struct _snd_pcm_runtime {
        /* -- Status -- */
        struct snd_pcm_substream *trigger_master;
        snd_timestamp_t 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*/

        /* -- 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 */
        unsigned int tick_time;               /* tick time */
        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;

        /* -- SW params -- */
        struct timespec tstamp_mode;  /* mmap timestamp is updated */
        unsigned int period_step;
        unsigned int sleep_min;               /* min ticks to sleep */
        snd_pcm_uframes_t start_threshold;
        /*
         * The following two thresholds alleviate playback buffer underruns; when
         * hw_avail drops below the threshold, the respective action is triggered:
         */
        snd_pcm_uframes_t stop_threshold;     /* - stop playback */
        snd_pcm_uframes_t silence_threshold;  /* - pre-fill buffer with silence */
        snd_pcm_uframes_t silence_size;       /* max size of silence pre-fill; when >= boundary,
                                               * fill played area with silence immediately */
        snd_pcm_uframes_t boundary;   /* pointers wrap point */

        /* internal data of auto-silencer */
        snd_pcm_uframes_t silence_start; /* starting pointer to silence area */
        snd_pcm_uframes_t silence_filled; /* size filled with silence */

        snd_pcm_sync_id_t sync;               /* hardware synchronization ID */

        /* -- mmap -- */
        volatile struct snd_pcm_mmap_status *status;
        volatile struct snd_pcm_mmap_control *control;
        atomic_t mmap_count;

        /* -- locking / scheduling -- */
        spinlock_t lock;
        wait_queue_head_t sleep;
        struct timer_list tick_timer;
        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;

        /* -- timer -- */
        unsigned int timer_resolution;        /* timer resolution */

        /* -- 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
};

每个声卡驱动的回调函数,这些数据都是制度的。只有PCM中间层可以修改更新他们。唯一的例外时硬件描述中的DMA buffer信息以及私有数据。此外,如果你使用标准管理buffer分配模式,你不需要设置DMA buffer信息。

硬件描述信息
硬件描述符(结构体snd_pcm_hardware)定义了基础的硬件配置信息。首先,你需要在PCM open的回调函数里面定义它。运行时实例持有的是一份这个描述的拷贝,而不是指针。在open回调函数里面。如果你需要,你可以修改这份拷贝的描述符 (runtime->hw)。比如,在一些芯片模式下,最大声道数支持1,依然可以使用同样的硬件描述符并且在后续的代码中修改channels_max。

struct snd_pcm_runtime *runtime = substream->runtime;
...
runtime->hw = snd_mychip_playback_hw; /* common definition */
if (chip->model == VERY_OLD_ONE)
        runtime->hw.channels_max = 1;

我们的硬件描述符通常是下面这个样子的。
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,
};

Info字段保存PCM的类型和能力。<sound/asound.h> 文件中SNDRV_PCM_INFO_XXX类型的宏定义了info字段的类型。是否支持mmap和支持什么交织类型必须指定。SNDRV_PCM_INFO_MMAP 标志为表示驱动支持MMAP,SNDRV_PCM_INFO_INTERLEAVED 标志位表示支持交织PCM,SNDRV_PCM_INFO_NONINTERLEAVED 表示支持非交织PCM。如果交织和非交织都支持,则可以两者都设置。
上面的实例代码中, OSS mmap mode指定了BLOCK_TRANSFER 和MMAP_VALID 。这两个标志位一般都是同时指定的。只有当驱动真正支持mmap的时候才能设置MMAP_VALID 。

SNDRV_PCM_INFO_PAUSE 和SNDRV_PCM_INFO_RESUME表示PCM支持pause/resume。如果设置了这两个标志位,那么在trigger 要做响应的处理。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值