使用alsa-lib实现录音

虽然网上已经有了很多的参考资料,但是不是很详细,而且注释也很少。有些看起来似懂非懂,所以在我这里提供了一版详细的实现代码,方便大家一起学习。

头文件的声明在 alsa_record.h

#ifndef COM_HXIONG_ALSA_RECORD_H
#define COM_HXIONG_ALSA_RECORD_H

//宏定义
#define RECORD_FAIL   -1
#define RECORD_SUCCESS 0
#define RECORD_SAMPLE_RATE      8000
#define RECORD_BUFFER_TIME_MAX  500000
#define RECORD_DEVICE_NAME      "hw:0,1"


//wav 格式的文件头
typedef struct{
    char chunk_id[4];   /* "RIFF" */
    char chunk_size[4]; /* file total size - 8(byte) */
    char format[4];    /* "WAVE" */

    /* sub-chunk "fmt" */
    char sub_chunk_id[4];   /* "fmt " */
    char sub_chunk_size[4]; /* 16 for PCM */
    char audio_format[2];   /* PCM = 1*/
    char audio_channels[2];   /* Mono = 1, Stereo = 2, etc. */
    char samplerate[4];    /* 8000, 44100, etc. */
    char byterate[4];  /* = SampleRate * NumChannels * BitsPerSample/8 */
    char block_align[2];    /* = NumChannels * BitsPerSample/8 */
    char bits_persample[2]; /* 8bits, 16bits, etc. */
    //char fmt_chunk_end[2];  /* 附加的两个数据,值为0 */

    /* sub-chunk "data" */
    char sub_chunk2_id[4];   /* "data" */
    char sub_chunk2_size[4]; /* data size */
} wav_format_t;



//自定义的结构体参数,目的时为了把command 中的参数最一层转换,到do_alsa_record 会再转成record_handle_t 中参数
typedef struct{
        int duration;  //录制的时长
        int format;    //录制的pcm格式
        int rate;      //采样率
        int channel;   //channel 个数
        // 如有需要其他参数可以继续添加
} record_params_t;
//path 为录制的wav音频文件的保存路径
int do_alsa_record(char* path,record_params_t* params);

#endif

真正实现录音功能的c文件在 alsa_record.c

#include 
#include 
#include 
#include 
#include 
#include 
#include   //alsa-lib相关的头文件
#include "alsa_record.h"

//wav 的文件头定义,一些固定的参数先赋值
static wav_format_t wav_format={
    {'R','I','F','F'}, //RIFF
    {0,0,0,0}, //之所以定义成char,是因为保存int 的方式不一样,比如1,可存为00 00 00 01 ,或者01 00 00 00
    {'W','A','V','E'},  //WAVE
    {'f','m','t',' '},  //fmt 后面有一个空格
    {16,0,0,0}, //一般为00000010H,若为00000012H则说明数据头携带附加信息,这里设成10h,如果设成12h,需要加上fmt_chunk_end
    {1,0}, //PCM = 1
    {2,0}, //Mono = 1, Stereo = 2
    {0,0,0,0},  //samplerate
    {0,0,0,0},  //byterate
    {4,0},  //NumChannels * BitsPerSample/8
    {16,0},  //8bits, 16bits, etc.
    {'d','a','t','a'}, //data
    {0,0,0,0}  //data size
};

//在c文件内部定义,可以认为是录制参数的集合
typedef struct{
   snd_pcm_t *pcm;  //alsa pcm handle
   snd_pcm_format_t format; //pcm 数据的格式
   unsigned int channels;  //channel
   unsigned int rate;      //采样率
   snd_pcm_uframes_t chunk_size; //简单理解为一个channel 1秒产生的frame个数
   size_t bits_per_sample;   //一个sample包含的bit 数
   size_t chunk_bytes;    //简单理解为1秒产生的pcm数据大小
   unsigned char* buffer; //用于保存录制数据的缓存buffer
} record_handle_t;

//pcm format 定义在alsa-lib/include/pcm.h
static snd_pcm_format_t get_format_from_params(record_params_t* params){
    if(params!=NULL){  //传入的params不为空,
        switch (params->format) {  //这里只是简单的转换,后面可以根据需求制定转换规则
        case 0:
            return SND_PCM_FORMAT_S8;
        case 1:
            return SND_PCM_FORMAT_U8;
        case 2:
            return SND_PCM_FORMAT_S16_LE;
        case 3:
            return SND_PCM_FORMAT_S16_BE;
        default:  return SND_PCM_FORMAT_S16_LE;//默认返回
        }
    }
    return SND_PCM_FORMAT_S16_LE; //默认返回
}

//简单的参数转换规则,可以根据需要自行修改
static unsigned int get_channel_from_params(record_params_t* params){
    return (params->channel==2)?2:1; //默认返回 1
}

//这里只是简单的转换,后面可以根据需求制定转换规则
static unsigned int get_rate_from_params(record_params_t* params){
    return (unsigned int)(params->raterate; //不能小于8000
}

static int init_recorder(record_handle_t* handle,record_params_t* params){
    if(handle==NULL){
        return RECORD_FAIL;
    }
    //1、打开pcm录音设备
    if(snd_pcm_open(&(handle->pcm), RECORD_DEVICE_NAME, SND_PCM_STREAM_CAPTURE, 0) < 0) {
        printf("snd_pcm_open fail.\n");
        return RECORD_FAIL;
    }
    //2、录音相关参数设置
    snd_pcm_hw_params_t *hwparams;
    //构建参数结构体
    snd_pcm_hw_params_alloca(&hwparams);

    //3、获取参数
    if(snd_pcm_hw_params_any(handle->pcm, hwparams) < 0) {
        printf("snd_pcm_hw_params_any fail.\n");
        goto init_recorder_fail;
    }

    //4、设置相关参数
    if(snd_pcm_hw_params_set_access(handle->pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
        printf("snd_pcm_hw_params_set_access fail.\n");
        goto init_recorder_fail;
    }

    handle->format = get_format_from_params(params);

    //设置pcm数据格式
    if(snd_pcm_hw_params_set_format(handle->pcm, hwparams, handle->format) < 0) {
        printf("snd_pcm_hw_params_set_format fail.\n");
        goto init_recorder_fail;
    }

    handle->channels=get_channel_from_params(params);

    //设置channel
    if(snd_pcm_hw_params_set_channels(handle->pcm, hwparams, handle->channels) < 0) {
        printf("snd_pcm_hw_params_set_channels fail.\n");
        goto init_recorder_fail;
    }

    handle->rate = get_rate_from_params(params);
    unsigned int sample_rate = handle->rate;
    //设置采样率
    if(snd_pcm_hw_params_set_rate_near(handle->pcm, hwparams,&sample_rate, 0) < 0) {
        printf("snd_pcm_hw_params_set_channels fail.\n");
        goto init_recorder_fail;
    }

    if(handle->rate != sample_rate){
        printf("The rate %d Hz is not supported, Using %d Hz instead.\n",handle->rate,sample_rate);
    }

    //
    unsigned int buffer_time;
    unsigned int period_time ;
    if (snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time, 0) < 0) {
        printf("snd_pcm_hw_params_get_buffer_time_max fail.\n");
        goto init_recorder_fail;
    }

    if (buffer_time > RECORD_BUFFER_TIME_MAX){
        buffer_time = RECORD_BUFFER_TIME_MAX;
    }
    period_time = buffer_time / 4;


    if (snd_pcm_hw_params_set_buffer_time_near(handle->pcm, hwparams, &buffer_time, 0) < 0) {
        printf("snd_pcm_hw_params_set_buffer_time_near fail.\n");
        goto init_recorder_fail;
    }

    if (snd_pcm_hw_params_set_period_time_near(handle->pcm, hwparams, &period_time, 0) < 0) {
        printf("snd_pcm_hw_params_set_period_time_near fail.\n");
        goto init_recorder_fail;
    }

    //5、设置参数给alsa
    if (snd_pcm_hw_params(handle->pcm, hwparams) < 0) {
        printf("snd_pcm_hw_params fail.\n");
        goto init_recorder_fail;
    }

    //获取相关参数
    snd_pcm_uframes_t buffer_size;
    snd_pcm_hw_params_get_period_size(hwparams, &(handle->chunk_size), 0);
    snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
    if (handle->chunk_size == buffer_size) {
        printf("Can't use period equal to buffer size (%lu == %lu).\n",handle->chunk_size,buffer_size);
        goto init_recorder_fail;
    }

    //根据设置计算出audio 的chunk 大小
    handle->bits_per_sample = snd_pcm_format_physical_width(handle->format);
    handle->chunk_bytes = (handle->chunk_size*handle->bits_per_sample * handle->channels)/8;

    //申请所需的内存
    handle->buffer = (unsigned char*)malloc(handle->chunk_bytes);
    if(!handle->buffer){
        printf("malloc buffer fail.\n");
        goto init_recorder_fail;
    }

    return RECORD_SUCCESS;

init_recorder_fail:
    snd_pcm_close(handle->pcm);  //参数设置失败,就关闭
    return RECORD_FAIL;
}

static int destroy_recorder(record_handle_t* handle){
    if(handle==NULL){
        printf("destroy_recorder handle is null.\n");
        return RECORD_FAIL;
    }
    free(handle->buffer);  //释放内存
    snd_pcm_drain(handle->pcm);
    snd_pcm_close(handle->pcm);
    return RECORD_SUCCESS;
}

static int create_path(const char *path)
{
    char *start;
    mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;

    if (path[0] == '/')
        start = strchr(path + 1, '/');
    else
        start = strchr(path, '/');

    while (start) {
         char *buffer = strdup(path);
         buffer[start-path] = 0x00;

         if (mkdir(buffer, mode) == -1 && errno != EEXIST) {
             printf("creating directory %s fail.\n", buffer);
             free(buffer);
             return -1;
          }
          free(buffer);
          start = strchr(start + 1, '/');
    }
    return 0;
}

static int safe_open(const char *name)
{
    int fd;

    fd = open(name, O_WRONLY | O_CREAT, 0644);
    if (fd == -1) {
        if (errno != ENOENT)
            return -1;
        if (create_path(name) == 0)
            fd = open(name, O_WRONLY | O_CREAT, 0644);
    }
    return fd;
}

static int number_to_string(char* dest,size_t src,int len){
    if(dest==NULL)
        return RECORD_FAIL;
    int index=0;
    while(index>8; //右移
       index++;  //下标加1
    }
    return RECORD_SUCCESS;
}


static int do_record(record_handle_t* handle,int fd,int duration){

    //计算音频数据长度(秒数 * 采样率 * 帧长度)
    size_t total_size =duration*handle->rate*handle->bits_per_sample*handle->channels/8;
    printf("do_record total_size is %d.\n",total_size);

    snd_pcm_t* pcm=handle->pcm;
    unsigned char* buffer=handle->buffer;
    size_t size = handle->chunk_size;
    size_t frame_byte = handle->bits_per_sample*handle->channels/8;
    size_t count = 0;
    int ret = 0;

    //封装好wav的文件头
    count = sizeof(wav_format_t);
    size_t tmp=count+total_size -8; //计算chunk size大小
    number_to_string(wav_format.chunk_size,tmp,4);
    number_to_string(wav_format.audio_channels,handle->channels,2);
    number_to_string(wav_format.samplerate,handle->rate,4);
    tmp=handle->rate*frame_byte;
    number_to_string(wav_format.byterate,tmp,4);
    number_to_string(wav_format.block_align,frame_byte,2);
    number_to_string(wav_format.bits_persample,handle->bits_per_sample,2);
    number_to_string(wav_format.sub_chunk2_size,total_size,4);
    //写入wav 文件头
    if(write(fd,&wav_format,count)!=count){
        printf("write count(%d) fail.\n",count);
        return RECORD_FAIL;
    }

    while (total_size>0) { //写入的数据超过total size就结束
       ret = snd_pcm_readi(pcm, buffer, size);
       if (ret == -EAGAIN ) {
            snd_pcm_wait(pcm, 1000);
       } else if (ret == -EPIPE) {
            snd_pcm_prepare(pcm);
            printf("snd_pcm_readi return EPIPE.\n");
       } else if (ret == -ESTRPIPE) {
            printf("snd_pcm_readi return ESTRPIPE.\n");
       } else if (ret < 0) {
            printf("snd_pcm_readi return fail.\n");
            return RECORD_FAIL;
       }

       if(ret>0){
           count = ret*frame_byte;
           if(write(fd,buffer,count)!=count){
               printf("write count(%d) fail.\n",count);
               return RECORD_FAIL;
           }
           total_size -= count;
       }
    }

    return RECORD_SUCCESS;
}

int do_alsa_record(char* path,record_params_t* params){
    record_handle_t record_handle={0};
    if(init_recorder(&record_handle,params)==RECORD_FAIL){
        printf("init_recorder fail.\n");
        return RECORD_FAIL;
    }

    int fd = safe_open(path);  
    if(fd>0){
       do_record(&record_handle,fd,params->duration);
       close(fd);  //at last,we must close fd
    }else{
       printf("file(%s) open fail.\n",path);
    }

    destroy_recorder(&record_handle);
    return RECORD_SUCCESS;
}
代码的实现思路很简单,打开pcm设备,设置好相应参数后,通过alsa-lib拿到pcm数据,封装好wav文件头后往文件中写入pcm数据, 相应的注释已经写在代码里面了。


你可能感兴趣的:(alsa)