302|2

531

帖子

4

TA的资源

纯净的硅(高级)

【米尔-STM32MP257开发板试用体验】基于ALSA采集音频到WAV文件 [复制链接]

本帖最后由 qinyunti 于 2025-4-10 16:42 编辑

7edf9b496398a6cdd0c5a9a470bf557d

 

b站视频


 

  1. 前言
    前面我们进行了alsa音频应用开发测试,以及基于speex的音频回声消除和降噪算法测试,接下来将两者结合起来,实现实时对讲的Demo。先要来实现基于alsa的音频播放与采集。前一篇已经实现了wav音频播放,本文来实现基于alsa录音到wav文件。
  2. 实现
    新建alsa_rec_wav.c文件。
    2.1 wav 解析
    Wav格式解析参考公众号文章
    https://mp.weixin.qq.com/s/Lmf1rAi-U4B-ZbZbMsuwqA
    Wav解析与添加头代码如下
    #include <stdint.h>
    #include <stdio.h>
    #include <string.h>
    /* WAV解析 */
    #define CHUNK_RIFF "RIFF"
    #define CHUNK_WAVE "WAVE"
    #define CHUNK_FMT "fmt "
    #define CHUNK_DATA "data"
    
    typedef struct
    {
        uint32_t off;
        uint32_t chunksize;
        uint16_t audioformat;
        uint16_t numchannels;
        uint32_t samplerate;
        uint32_t byterate;
        uint16_t blockalign;
        uint16_t bitspersample;
        uint32_t datasize;
    }wav_t;
    
    static int wav_decode_head(uint8_t* buffer, wav_t* wav)
    {
        uint8_t* p = buffer;
        uint32_t chunksize;
        uint32_t subchunksize;
        if(0 != memcmp(p,CHUNK_RIFF,4))
        {
            return -1;
        }
        p += 4;
        chunksize = (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);
        wav->chunksize = chunksize;
        p += 4;
        if(0 != memcmp(p,CHUNK_WAVE,4))
        {
            return -2;
        }
        p += 4;
    
        do
        {
            if(0 == memcmp(p,CHUNK_FMT,4))
            {
                p += 4;
                subchunksize = (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);
                p += 4;
                /* 解析参数 */
                wav->audioformat = (uint16_t)p[0] | ((uint16_t)p[1]<<8);
                if((wav->audioformat == 0x0001) || (wav->audioformat == 0xFFFE))
                {
                    p += 2;
                    wav->numchannels = (uint16_t)p[0] | ((uint16_t)p[1]<<8);
                    p += 2;
                    wav->samplerate = (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);
                    p += 4;
                    wav->byterate = (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);
                    p += 4;
                    wav->blockalign = (uint16_t)p[0] | ((uint16_t)p[1]<<8);
                    p += 2;
                    wav ->bitspersample = (uint16_t)p[0] | ((uint16_t)p[1]<<8);
                    p += 2;
    
                    if(subchunksize >16)
                    {
                        /* 有ext区域 */
                        uint16_t cbsize = (uint16_t)p[0] | ((uint16_t)p[1]<<8);
                        p += 2;
                        if(cbsize > 0)
                        {
                            /* ext数据 2字节有效bits wValidBitsPerSample ,4字节dwChannelMask 16字节SubFormat */
                            p += 2;
                            p += 4;
                            /* 比对subformat */
                            p += 16;       
                        }
                    }
                }
                else
                {
                    p += subchunksize;
                }
            }
            else if(0 == memcmp(p,CHUNK_DATA,4))
            {
                p += 4;
                subchunksize = (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);
                wav->datasize = subchunksize;
                p += 4;
                wav->off = (uint32_t)(p- buffer);
                return 0;
            }
            else
            {
                p += 4;
                subchunksize = (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);
                p += 4;
                p += subchunksize;
            }
        }while((uint32_t)(p - buffer) < (chunksize + 8));
        return -3;
    }
    
    /* 填充44字节的wav头 */
    static void wav_fill_head(uint8_t* buffer, int samples, int chnum, int freq)
    {
        /*
         * 添加wav头信息
         */
        uint32_t chunksize = 44-8+samples*chnum*16/8;
        uint8_t* p = (uint8_t*)buffer;
        uint32_t bps = freq*chnum*16/8;
        uint32_t datalen = samples*chnum*16/8;
        p[0] = 'R';
        p[1] = 'I';
        p[2] = 'F';
        p[3] = 'F';
        p[4] = chunksize & 0xFF;
        p[5] = (chunksize>>8) & 0xFF;
        p[6] = (chunksize>>16) & 0xFF;
        p[7] = (chunksize>>24) & 0xFF;
        p[8] = 'W';
        p[9] = 'A';
        p[10] = 'V';
        p[11] = 'E';
    
        p[12] = 'f';
        p[13] = 'm';
        p[14] = 't';
        p[15] = ' ';
    
        p[16] = 16;  /* Subchunk1Size */
        p[17] = 0;
        p[18] = 0;
        p[19] = 0;
    
        p[20] = 1;  /* PCM */
        p[21] = 0;
    
        p[22] = chnum; /* 通道数 */
        p[23] = 0;
    
        p[24] = freq & 0xFF;
        p[25] = (freq>>8) & 0xFF;
        p[26] = (freq>>16) & 0xFF;
        p[27] = (freq>>24) & 0xFF; 
    
        p[28] = bps & 0xFF;      /* ByteRate */
        p[29] = (bps>>8) & 0xFF;
        p[30] = (bps>>16) & 0xFF;
        p[31] = (bps>>24) & 0xFF; 
    
        p[32] = chnum*16/8; /* BlockAlign */
        p[33] = 0;
    
        p[34] = 16;  /* BitsPerSample */
        p[35] = 0;
    
        p[36] = 'd';
        p[37] = 'a';
        p[38] = 't';
        p[39] = 'a';
    
        p[40] = datalen & 0xFF;
        p[41] = (datalen>>8) & 0xFF;
        p[42] = (datalen>>16) & 0xFF;
        p[43] = (datalen>>24) & 0xFF; 
    }
    
    void wav_print(wav_t* wav)
    {
       printf("off:%d\r\n",wav->off); 
       printf("chunksize:%d\r\n",wav->chunksize); 
       printf("audioformat:%d\r\n",wav->audioformat); 
       printf("numchannels:%d\r\n",wav->numchannels); 
       printf("samplerate:%d\r\n",wav->samplerate); 
       printf("byterate:%d\r\n",wav->byterate); 
       printf("blockalign:%d\r\n",wav->blockalign); 
       printf("bitspersample:%d\r\n",wav->bitspersample); 
       printf("datasize:%d\r\n",wav->datasize); 
    }
    

    2.2 alsa采集音频
    参考
    https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html
    打开设备与参数初始化
    int alsa_init(char* device, snd_pcm_t **handle, int ch, int freq)
    {
        int err;
    
        if ((err = snd_pcm_open(handle, device, SND_PCM_STREAM_CAPTURE, 0)) < 0) {  /* 最后一个参数0 阻塞模式 1非阻塞模式 */
            printf("Playback open error: %s\n", snd_strerror(err));
            return -1;
        }
    
        snd_pcm_sframes_t frames;
        if ((err = snd_pcm_set_params(*handle,
                        SND_PCM_FORMAT_S16_LE,
                        SND_PCM_ACCESS_RW_INTERLEAVED,
                        ch,
                        freq,
                        0,
                        100000)) < 0) {   /* 0.5sec */
            printf("Playback open error: %s\n", snd_strerror(err));
            snd_pcm_close(*handle);
            return -2;
        }
    
    	snd_pcm_hw_params_t *hwparams = NULL;
    	snd_pcm_hw_params_malloc(&hwparams);
    	snd_pcm_hw_params_any(*handle, hwparams);
    	snd_pcm_hw_params_set_access(*handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
    	snd_pcm_hw_params_set_format(*handle, hwparams,SND_PCM_FORMAT_S16_LE);
    	snd_pcm_hw_params_set_channels(*handle, hwparams,ch);
    	snd_pcm_hw_params_set_rate(*handle, hwparams,freq,0);
    	snd_pcm_hw_params_set_period_size(*handle, hwparams, NN, 0);
    	snd_pcm_hw_params_set_buffer_size(*handle, hwparams, 4*NN);
    	snd_pcm_hw_params(*handle, hwparams);
    	snd_pcm_hw_params_free(hwparams);
    
        snd_pcm_start(*handle);
        return 0;
    }

    采集
    int alsa_rec(snd_pcm_t *handle, void* buffer, size_t size)
    {
        int timeout = 0;
        snd_pcm_sframes_t frames;
        do{
            frames = snd_pcm_avail_update(handle);
            if(frames < size){
                usleep(1000);
                timeout++;
                if(timeout >= 1000){
                    printf("rec timeout\n");
                    return -1;
                }
            }
        }while(frames < size);
    
        frames = snd_pcm_readi(handle, buffer, (snd_pcm_uframes_t)size);  /* 注意这里的size为frames即点数 而不是字节大小 */
        if (frames < 0){
            frames = snd_pcm_recover(handle, frames, 0);
    
            printf("snd_pcm_recover: %s\n", snd_strerror(frames));
        }
        if (frames < 0) {
            printf("snd_pcm_writei failed: %s\n", snd_strerror(frames));
            return -1;
        }
        if (frames > 0 && frames < (long)sizeof(buffer)){
            printf("Short write (expected %li, wrote %li)\n", (long)sizeof(buffer), frames);
            return -2;
        }
        return 0;
    }
    

    完成关闭
    int alsa_deinit(snd_pcm_t *handle)
    {
        int err;
        /* pass the remaining samples, otherwise they're dropped in close */
        err = snd_pcm_drain(handle);
        if (err < 0){
            printf("snd_pcm_drain failed: %s\n", snd_strerror(err));
        }
        snd_pcm_close(handle);
        return 0;
    }

    2.3 读取音频写入wav文件
    #include <alsa/asoundlib.h>
    #define NN 128
    #define CH_MAX 2
    
    int main(int argc, char* argv[])
    {    
        snd_pcm_t *handle = NULL;
        int res;
        FILE *wav_fd;
        int16_t wav_buf[NN*CH_MAX];
        uint8_t wav_head_buf[44]; /* wav文件头缓存 */
        wav_t wav;
        int ch;
        int samps;  /* 采样点数 */
        int times;    /* 读取次数 */
        int sampleRate;
    
        if(argc != 6){
            printf("usage:alsa_rec_wav dev wav ch freq samps\r\n");
            return -1;
        }
        ch = atoi(argv[3]);
        sampleRate = atoi(argv[4]);
        samps = atoi(argv[5]);
    
        printf("ch=%d,freq=%d,samps=%d\r\n",ch,sampleRate,samps);
        wav_fd = fopen(argv[2], "wb+");
        if(wav_fd < 0){
            printf("open file %s err\r\n",argv[2]);
            return -2;
        }
    
        times = samps / NN;   /* 一次读取NN个点,读取times次 */
    
        printf("fill head\r\n");
        wav_fill_head(wav_head_buf, times*NN, ch, sampleRate);  /* 输出文件头 */
        if(44 != fwrite(wav_head_buf, 1, 44, wav_fd)){
           printf("write file %s err\n",argv[2]);
           fclose(wav_fd);
           return -3;
        }
    
        printf("alsa_init\r\n");
        res = alsa_init(argv[1], &handle, ch, sampleRate);
        if(res != 0){
            fclose(wav_fd);
            printf("alsa_init err\r\n");
            return -4;
        }
    
        printf("alsa_rec\r\n");
        for(int i=0; i<times; i++)
        {
            if(alsa_rec(handle, wav_buf, NN) < 0){  /* 注意这里最后一个参数是frames为单位 不需要乘以ch */
                printf("rec %s err\r\n");
                fclose(wav_fd);
                alsa_deinit(handle);
                return -5;
            }
    
           if(NN*ch != fwrite(wav_buf, sizeof(int16_t), NN*ch, wav_fd)){
                printf("write file %s err\r\n",argv[2]);
                fclose(wav_fd);
                alsa_deinit(handle);
                return -6;
           }
        }
    
        fclose(wav_fd);
        alsa_deinit(handle);
        return 0;
    }
    

     

  3. 测试
    编译
    source /opt/st/myd-ld25x/4.2.4-snapshot/environment-setup-cortexa35-ostl-linux
    $CC alsa_rec_wav.c -o alsa_rec_wav -lasound
    导出到windows下
    cp alsa_rec_wav /mnt/d
    导入到开发板
    wd_163539j1zp58ddgtlgala8.png
    chmod +x alsa_rec_wav
    测试
    先按照单通道和双通道录音
    ./alsa_rec_wav default rec1.wav 1 16000 128000
    ./alsa_rec_wav default rec2.wav 2 16000 128000
    wd_163539v2w9q59v95r0zir2.png
    然后播放
    ./alsa_play_wav default rec1.wav
    ./alsa_play_wav default rec2.wav
    wd_163539fpcjj2jps626klhq.png
  4. 总结
以上实现了录音到wav文件,已经实现播放与采集,后面就可以实现语音对讲并添加实时音频处理算法实现对讲回声消除与降噪的Demo。

最新回复

这应该是流式采集声音吧?   详情 回复 发表于 2025-4-11 14:27

回复
举报

7588

帖子

2

TA的资源

版主

这应该是流式采集声音吧?

点评

用的alsa  详情 回复 发表于 2025-4-11 15:13

回复

531

帖子

4

TA的资源

纯净的硅(高级)

wangerxian 发表于 2025-4-11 14:27 这应该是流式采集声音吧?

用的alsa


回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网 14

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表