linux下lame&alsa进行音频流操作(二)alsa知识介绍和libasound使用

说明

  在Linux平台, 设计到音频的开发,必然离不开liasound,无论是混音器的参数设置,还是录音、播放音频等,liasound都提供了支持。这篇文章介绍的时播放音频部分,即 PCM 部分

1. 预备知识

1.1 音频相关知识

  1. 采样率(每秒采样次数)\帧率, 表示每一秒对声音的波形模拟量取样的次数,频率越高,音质越好
  2. 字节率(每秒采样字节数),与采样率、通道数和单位采样字节数综合相关, 实际上等于这三个数据的乘积除以8 (8位代表1字节)
  3. 通道数(1: 单通道, 2: 立体声),就是我们平常听音乐时使用的耳机,如果是单通道数据, 我们会听到两个耳机内声音时一模一样, 如果是双通道,一些音质比较高的音乐或者影视甚至游戏,在两个耳机里面听到的声音会不一样,能给人一种有距离的感觉,所以叫立体声, 一些音质更高的播放设备和音乐文件中也会包含双通道以外的音频信息, 是播放出来的音频效果更逼真
  4. 单位采样数据位数, 指的是每次对声音波形的模拟量采样记录时, 用来记录数据的数据位数, 数据位数越高,数据表示的模拟量越精细, 音质越好

1.2 基本概念

  1. 样本长度(sample):样本是记录音频数据最基本的单位,常见的有8位和16位。
  2. 通道数(channel):该参数为1表示单声道,2则是立体声。
  3. 桢(frame):桢记录了一个声音单元,其长度为样本长度与通道数的乘积。
  4. 采样率(rate):每秒钟采样次数,该次数是针对桢而言。
  5. 周期(period):音频设备一次处理所需要的桢数,对于音频设备的数据访问以及音频数据的存储,都是以此为单位。
  6. 交错模式(interleaved):是一种音频数据的记录方式,在交错模式下,数据以连续桢的形式存放,即首先记录完桢1的左声道样本和右声道样本(假设为立体声格式),再开始桢2的记录。而在非交错模式下,首先记录的是一个周期内所有桢的左声道样本,再记录右声道样本,数据是以连续通道的方式存储。不过多数情况下,我们只需要使用交错模式就可以了。
  7. period(周期):硬件中中断间的间隔时间。它表示输入延时。
  8. 声卡接口中有一个指针来指示声卡硬件缓存区中当前的读写位置。只要接口在运行,这个指针将循环地指向缓存区中的某个位置。
    frame size = sizeof(one sample) * nChannels  
    //alsa中配置的缓存(buffer)和周期(size)大小在runtime中是以帧(frames)形式存储的。
    period_bytes = frames_to_bytes(runtime, runtime->period_size); 
    bytes_to_frames()
    

2. ALSA编程接入

  1. 头文件
#include <alsa/asoundlib.h>
  1. 需要用到的几个结构体
snd_pcm_t
snd_pcm_hw_params_t
snd_pcm_sw_params_t
  1. 如何使用
  • 首先使用snd_pcm_t来打开音频设备,用来播放音频或者录音
int main(int agc, char **argv)
{
        snd_pcm_t *pcm;
        snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0); // 这里是以音频播放模式打开
        // or 
        // snd_pcm_open(&pcm, "default", SND_PCM_STREAM_CAPTURE, 0); 以录音模式打开
        //...
        return 0;
}
  • 使用snd_pcm_hw_params_t配置硬件参数
int main(int agc, char **argv)
{
        //...
        snd_pcm_hw_params_t *hwparams;
        snd_pcm_hw_params_malloc(&hwparams); // 或者 snd_pcm_hw_params_alloca(&hwparams); 
        snd_pcm_hw_params_any(pcm, hwparams); // 使用pcm设备初始化hwparams

        snd_pcm_hw_params_set_xxx(pcm, hwparams, ...); // 通过一系列的set函数来设置硬件参数中的基本参数
                // 如, 通道数,采样率, 采样格式等等
        // ...
        snd_pcm_hw_params(pcm, hwparams);
        snd_pcm_hw_params_free(hwparams); // 释放不再使用的hwparams空间
        //...
        return 0;
}
  • 使用snd_pcm_sw_params_t配置一些高级软件参数, 比如使用中断模式等等
    使用方法与snd_pcm_hw_params_t基本差不多, 这里不再赘述
  • 调用snd_pcm_prepare(pcm)来使音频设备准备好接收pcm数据
int main(int argc, char **argv) 
{
        // ...
        if(snd_pcm_prepare(pcm) < 0)  
        {
                snd_pcm_close(pcm);
                exit(1);
        }
        // ...
        return 0;
}
  • 循环调用snd_pcm_writei(pcm, buffer, frame)来往音频设备写数据
    循环写数据时需要注意以下几个问题:
    1. snd_pcm_writei(pcm, buffer, frame)中使用的frame不是buffer数据的字节数, 而是帧数(采样数), 返回值也是帧数
    2. 帧数与字节数之间需要通过 snd_pcm_bytes_to_frames(bytes)和snd_pcm_frames_to_bytes(frames)来做转换
    3. 每次调用完snd_pcm_writei(pcm, buffer, frames)后, 需要做适当延时, 延时时间为, 理论上已经写入帧数所需时间与实际调用该函数所花时间的差值
    4. 每次调用snd_pcm_writei(pcm, buffer, frames)返回后, 下次buffer的起始地址需要向前进 (frame * 通道数 * 采样位数 / 8)字节,而剩余的帧数只需要在上一次总帧数基础上减去已写入的帧数
    5. 建议每次准备1秒的数据到缓冲区, 太大会浪费缓冲区, 太小又需要频繁请求数据

综合使用案例

点击查看github项目

上一篇:ASP.NET MVC学前篇之扩展方法、链式编程


下一篇:Linux系统下录音方法