297|0

531

帖子

4

TA的资源

纯净的硅(高级)

【米尔-STM32MP257开发板试用体验】基于alsa的wav音频播放 [复制链接]

本帖最后由 qinyunti 于 2025-4-10 11:25 编辑

9c545045020749681ff9f459c19ff2b7

 

b站视频


 

  1. 前言
    前面我们进行了alsa音频应用开发测试,以及基于speex的音频回声消除和降噪算法测试,接下来将两者结合起来,实现实时对讲的Demo。先要来实现基于alsa的音频播放与采集。本文来实现基于alsa播放wav格式pcm音频。
  2. 实现
    新建alsa_play_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_min_8c-example.html
    打开设备与参数初始化
    int alsa_init(char* device, snd_pcm_t **handle, wav_t* wav)
    {
        int err;
    
        if ((err = snd_pcm_open(handle, device, SND_PCM_STREAM_PLAYBACK, 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,
                        wav->numchannels,
                        wav->samplerate,
                        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,wav->numchannels);
    	snd_pcm_hw_params_set_rate(*handle, hwparams,wav->samplerate,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);
    
        return 0;
    }

    播放
    int alsa_play(snd_pcm_t *handle, const void* buffer, size_t size)
    {
        snd_pcm_sframes_t frames;
        frames = snd_pcm_writei(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 MAX_CH 2
    
    int main(int argc, char* argv[])
    {    
        snd_pcm_t *handle = NULL;
        int res;
        FILE *wav_fd;
        int16_t wav_buf[NN*MAX_CH];
        uint8_t wav_head_buf[128]; /* 输入wav文件头缓存 */
        wav_t wav;
        int samps;  /* 采样点数 */
        int times;    /* 读取次数 */
        int sampleRate;
    
        if(argc != 3){
            printf("usage:alsa_play_wav dev wav\r\n");
            return -1;
        }
    
        wav_fd = fopen(argv[2], "rb");
        if(wav_fd < 0){
            printf("open file %s err\r\n",argv[2]);
            return -2;
        }
    
        if(sizeof(wav_head_buf) != fread(wav_head_buf, 1, sizeof(wav_head_buf), wav_fd)){
            printf("read file %s err\r\n",argv[2]);
            fclose(wav_fd);
            return -3;
        }
    
        if(0 != (res=wav_decode_head(wav_head_buf, &wav))){
            printf("decode file %s err %d\r\n",argv[2],res);
            fclose(wav_fd);
            return -4;
        }
        printf("[wav]\r\n");
        wav_print(&wav);
    
        samps = wav.datasize; 
        samps /= wav.blockalign;  /* 采样点数 =  数据大小 除以 blockalign */
        printf("\r\nsamps:%d\r\n",samps);
     
        sampleRate = wav.samplerate;
    
        res = alsa_init(argv[1], &handle, &wav);
        if(res != 0){
            fclose(wav_fd);
            printf("alsa_init err\r\n");
            return -5;
        }
    
        times = samps / NN;   /* 一次读取NN个点,读取times次 */
    
        fseek(wav_fd,wav.off,SEEK_SET);
        for(int i=0; i<times; i++)
        {
           if(NN*wav.numchannels != fread(wav_buf, sizeof(int16_t), NN*wav.numchannels, wav_fd)){
                printf("read file %s err\r\n",argv[2]);
                fclose(wav_fd);
                alsa_deinit(handle);
                return -6;
           }
           if(alsa_play(handle, wav_buf, NN) < 0){
            printf("play %s err\r\n");
            fclose(wav_fd);
            alsa_deinit(handle);
            return -7;
           }
        }
    
        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_play_wav.c -o alsa_play_wav -lasound
    导出到windows下
    cp alsa_play_wav /mnt/d
    导入到开发板
    wd_111054xx7p4zh9uh1z7aaa.png
    chmod +x alsa_play_wav
    准备wav文件
    下载一个mp3音乐,使用ffmpeg转为wav
    .\ffmpeg-7.1.1-full_build\bin\ffmpeg.exe -i zdcxf.mp3 -ac 1 -ar 16000 -ss 0 -t 20 spk1.wav
    .\ffmpeg-7.1.1-full_build\bin\ffmpeg.exe -i zdcxf.mp3 -ac 2 -ar 16000 -ss 0 -t 20 spk2.wav
    导入测试wav文件
    wd_111054vogxiyyxg1o15yxi.png
    测试
    ./alsa_play_wav default spk1.wav
    ./alsa_play_wav default spk2.wav
  4. 总结
以上实现了播放wav音频,后面就可以实现语音对讲的播放, 后面再实现采集音频,实现mic音频采集。


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

随便看看
查找数据手册?

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
快速回复 返回顶部 返回列表