音频系统,Alsa 里面的buff 是怎么计算的?

嵌入式Linux

共 3239字,需浏览 7分钟

 ·

2020-09-02 21:40

相关文章

(干货)Ai音箱和Linux音频驱动小谈
Linux ALSA 图解
我在MTK平台下调试音频ALSA

我们知道声音是模拟信号,模拟信号转成数字信号就一定有大小,既然有大小,那我们就需要开辟内存来保存这些数据。

---- 我们知道,视频流的一帧就是一张图像 ------ 但是音频不一样,音频的一帧不能表示是一句话,因为你不知道一个人说话的时间长度。

java读取音频代码

int bufferLen = mSampleRateInHz * channels * 2 / 10; // 100ms data
                //int bufferLen = 2048;

                if ((channels == 1) && (mSampleRateInHz == 32000) && (mAudioSource == 6) && (mMaxChannels > 0))
                { // get raw data
                    bufferLen = 16000 * mMaxChannels * 2 / 10; // 100ms data
                }
                byte[] buffer = new byte[bufferLen];
                mAudioRecorder = new AudioRecord(mAudioSource, mSampleRateInHz, mChannelConfig,
                            AudioFormat.ENCODING_PCM_16BIT, bufferLen * 10); //1 sec buffer
                  while(true)
                  {
                   size = mAudioRecorder.read(buffer, 0, bufferLen); //100ms
                  }

tinycap 里面的 pcm_read 代码

int pcm_read(struct pcm *pcm, void *data, unsigned int count)
{
    struct snd_xferi x;

    if (!(pcm->flags & PCM_IN))
        return -EINVAL;

    x.buf = data;
    x.frames = count / (pcm->config.channels *
                        pcm_format_to_bits(pcm->config.format) / 8);

    for (;;) {
        if (!pcm->running) {
            if (pcm_start(pcm) < 0) {
                fprintf(stderr, "start error");
                return -errno;
            }
        }
        if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
            pcm->prepared = 0;
            pcm->running = 0;
            if (errno == EPIPE) {
                    /* we failed to make our window -- try to restart */
                pcm->underruns++;
                continue;
            }
            return oops(pcm, errno, "cannot read stream data");
        }
        return 0;
    }
}

提炼下重点

我们在里面看的一个英文单词 framesframes 相当一帧的数据。

----但是这个一帧跟视频流里面的一张图像又不是一个概念,这里的一帧相当于声音里面的最小计量单位。


音频帧图解

解释一下上面的图片

假设我们使用的是一个立体声 16位 16k的音频流,不管是录音还是播放都一样,那么

  • 立体声 = 2通道
  • 1次ADC转换样本数据是 16bits = 2bytes
  • 1个帧 代表 所有通道的ADC转换数据。那么我们现在是双通道,所以
    • 1帧 = (通道数) * (样本大小bytes) = 2 * 2 = 4bytes
  • 为了能支持2 * 16k的采样率,系统必须支持如下的速度
    • bsp_rate = (通道数) * (1个样本长度) * (采样率) = 1帧 * 采样率 =
      2 * 2 *16k = 64000bytes/sec(秒)

假设现在 alsa每秒中断DMA一次。那么我们每秒都需要64000bytes数据准备好,才能满足一个 双通道 16 位 16k的音频流。

  • 如果半秒中断一次,那么每次中断就是 64000bytes/ 2 = 32000bytes
  • 如果我们100ms 产生一次中断,那么每次中断就是 64000bytes / 10 = 6400bytes

我们可以通过设置period size 来控制pcm中断的产生。

反推一下
---- 如果我们设置一个16位双通道16k的音频流, 并且每次都有1600帧数据
---- 4 byte * 1600frams = 6400字节
---- 一次中断会需要6400字节的数据
----那么他就是100ms中断一次「看上面的推断」

alsa会自己适应实际的buffer_size 和period_size,根据请求的通道数,和他们其他的一些属性。

把音频格式转换成bits的代码

unsigned int pcm_format_to_bits(enum pcm_format format)
{
    switch (format) {
    case PCM_FORMAT_S32_LE:
    case PCM_FORMAT_S24_LE:
        return 32;
    case PCM_FORMAT_S24_3LE:
        return 24;
    default:
    case PCM_FORMAT_S16_LE:
        return 16;
    };
}

我们会使用这个函数拿到对应格式的音频bit「正常是16bit 和 32bit」,但是,我们读写数据是字节对齐bytes对齐的,这也是我们看到很多地方有除以8这个操作的原因。

 x.buf = data;
 x.frames = count / (pcm->config.channels *
                        pcm_format_to_bits(pcm->config.format) / 8);

测试的小程序

---- 用来录音的测试程序

#include 

#define UNUSED(x) (void)(x)

int main(int argc, char **argv) {
    UNUSED(argc);
    UNUSED(argv);
    char *cmd = "tinycap /sdcard/1.pcm -D 0 -d 3 -r 16000 -c 2 -b 16";
    char buf[256];
    FILE *fp = popen(cmd, "r");
    for (int i=0; i<16; i++) {
        int result = fread(buf, 1, sizeof(buf), fp);
        printf("read %d bytes\n", result);
    }
    pclose(fp);
    return 0;
}

这个程序会打开声卡 0 第 3 个pcm通路去录音。
我们这里没有设置「-p」这个属性,加上这个属性之后,就会知道在1秒内的中断次数,从而知道1秒内的音频大小了。当然了,我们不设置,也会有一个默认值的。


  推荐阅读:
    专辑|Linux文章汇总
    专辑|程序人生
    专辑|C语言


嵌入式Linux
微信扫描二维码,关注我的公众号 
浏览 19
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报