• Linux ALSA音频库(二) 环境测试+音频合成+语音切换 项目代码分享


    1. 环境测试

    alsa_test.c

    #include <alsa/asoundlib.h>
    #include <stdio.h>
    
    //  官方测试代码, 运行后只要有一堆信息打印出来,即说明安装成功了。
    
    int main() 
    {
        int val;
     
        printf("ALSA library version: %s
    ",
                           SND_LIB_VERSION_STR);
     
        printf("
    PCM stream types:
    ");
        for (val = 0; val <= SND_PCM_STREAM_LAST; val++)
                printf(" %s
    ",
                      snd_pcm_stream_name((snd_pcm_stream_t)val));
     
        printf("
    PCM access types:
    ");
        for (val = 0; val <= SND_PCM_ACCESS_LAST; val++)
        {
                printf(" %s
    ",
                      snd_pcm_access_name((snd_pcm_access_t)val));
        }
     
        printf("
    PCM formats:
    ");
        for (val = 0; val <= SND_PCM_FORMAT_LAST; val++)
            {
            if (snd_pcm_format_name((snd_pcm_format_t)val)!= NULL)
            {
                printf(" %s (%s)
    ",
                        snd_pcm_format_name((snd_pcm_format_t)val),
                        snd_pcm_format_description(
                                (snd_pcm_format_t)val));
            }
        }
     
        printf("
    PCM subformats:
    ");
        for (val = 0; val <= SND_PCM_SUBFORMAT_LAST;val++)
        {
                printf(" %s (%s)
    ",
                      snd_pcm_subformat_name((
                    snd_pcm_subformat_t)val),
                      snd_pcm_subformat_description((
                    snd_pcm_subformat_t)val));
        }
     
        printf("
    PCM states:
    ");
        for (val = 0; val <= SND_PCM_STATE_LAST; val++)
                printf(" %s
    ",
                       snd_pcm_state_name((snd_pcm_state_t)val));
     
        return 0;
    }

    makefile:

    .PHONY : rebuild clean
    
    
    CC:= mips-linux-gnu-gcc
    TARGET:= alsa_test.out
    
    
    OBJS:= alsa_test.o 
    
    INCLUDE += -I/usr/local/open_lib/include
    
    LIBS += -lpthread -L/usr/local/open_lib/lib -lasound
    
    
    $(TARGET) :$(OBJS)
        $(CC) $(LIBS) $^ -o $@ 
    
    
    $(OBJS):%.o:%.c
        $(CC) $(INCLUDE) -c $^ -o $@
        echo $(OBJS)
    
    
    clean:
        $(RM) $(OBJS)
        $(RM) $(TARGET)
        @echo "clean"
    
    rebuild : clean $(TARGET)
        @echo "rebuild"
    官方测试代码, 运行后只要有一堆信息打印出来,即说明安装成功了。

    2. 音频合成+语音切换 功能使用,单线程,不考虑多线程场景。

    alsa_test.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <alsa/asoundlib.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <sys/time.h>
    static char * name;
    struct wav_header
    {
        char rld[4]; //riff 标志符号
        int rLen; 
        char wld[4]; //格式类型(wave)
        char fld[4]; //"fmt"
    
        int fLen; //sizeof(wave format matex)
        
        short wFormatTag; //编码格式
        short wChannels; //声道数
        int nSamplesPersec ; //采样频率
        int nAvgBitsPerSample;//WAVE文件采样大小
        short wBlockAlign; //块对齐
        short wBitsPerSample; //WAVE文件采样大小
        
        char dld[4]; //”data“
        int wSampleLength; //音频数据的大小
    
    } wav_header, wav_header1, wav_header2, wav_header3;
    static pthread_mutex_t mutex;   // 等待停止
    static pthread_mutex_t mutex_play;  // 开启播放
    snd_pcm_t* handle; //PCI设备句柄
    static char * path1;
    static char * path2;
    static pthread_t id;
    static char g_stop=0;
    
    static int lock = 0;
    
    int set_pcm_play(const char* path1, const char* path2, const char* path3);
    int wait_stop_play(void);
    
    
    /****   立即关闭播放的方法  ****
            if(handle!=NULL)
            {
              g_stop=1;
              snd_pcm_drop(handle);
            }    
    ****    ****/
    
    #if 0
    int main(void)
    {
            system("stty -icanon");
    
            handle = NULL;    
    
            set_pcm_play("/etc/door_sound/201.wav", "/etc/door_sound/203.wav", "/etc/door_sound/201.wav");
        printf("-----OVER11111111111111----
    
    ");
    
    
            set_pcm_play("/etc/door_sound/201.wav", NULL, NULL);
            set_pcm_play("/etc/door_sound/203.wav", NULL, NULL);
            set_pcm_play("/etc/door_sound/201.wav", NULL, NULL);
        printf("-----OVER222222222222-------
    
    ");
    
    
            sleep(3);
    
        #if 0  // 存在的问题:set_pcm_play两句话, 前面一句话叫得很快 , 后面正常  
            set_pcm_play("/etc/door_sound/201.wav", "/etc/door_sound/203.wav", NULL);
            set_pcm_play("/etc/door_sound/201.wav", "/etc/door_sound/203.wav", NULL);
    
            set_pcm_play(NULL, NULL, "/etc/door_sound/203.wav"); // 这句话没有播放
        printf("-----OVER333333333333-------
    
    ");
        #else
            set_pcm_play("/etc/door_sound/201.wav", "/etc/door_sound/203.wav", NULL);
            set_pcm_play("/etc/door_sound/201.wav", "/etc/door_sound/203.wav", NULL);
    
            set_pcm_play("/etc/door_sound/203.wav", NULL, NULL);
        printf("-----OVER333333333333-------
    
    ");
        #endif
    
    
            //set_pcm_play("/TG/sound/333.wav", "/TG/sound/333.wav", "/TG/sound/333.wav");
            // set_pcm_play("/TG/sound/1.wav", NULL, NULL);
    
            return 0;
    }
    
    #else
    
    
    pthread_t Handle_stop_sound_thread;
    
    
    void *stop_sound_thread(void *arg)
    {
    
        sleep(2);
    
        if(handle!=NULL)
        {
            printf("即将关闭当前语音流A 
    ");
            g_stop=1; 
             // snd_pcm_drop(handle); 不要立即关停。  因为这会导致当前正在执行snd_pcm_writei()写入出错。置位一个标志g_stop已经足够。
        }    
    
        return NULL;
    }
    
    
    
    /*  ALSA使用教程 
    **  Demo演示 
    **  LMW  
    **  2020 - 07 -xx
    **使用了下alsa实现各语音提取合并(语音流A最多由三个wav文件合成) 、以及切换语音播报(关闭当前语音流A转而播报语音流B这样)、
    **
    **/
    
    int main(void)
    {
            system("stty -icanon");
    
            handle = NULL;    
    
    
    
            //这里新建个线程,延时2秒后关闭当前的语音播放
    
          if(pthread_create(&Handle_stop_sound_thread, NULL, stop_sound_thread, NULL))
          {
            perror("Create stop_sound_thread Err ");
        }
        else
        {
            pthread_detach(Handle_stop_sound_thread);
        }
    
    
            //这里搞个远大于2秒的语音播报
        printf("-准备播报语音流A..
    ");
            set_pcm_play("/etc/door_sound/254.wav", "/etc/door_sound/203.wav", "/etc/door_sound/201.wav");
        printf("-已关闭(中止)当前语音流A!
    
    ");
    
            //sleep(3);
        printf("-准备播报语音流B..
    ");
        set_pcm_play("/etc/door_sound/254.wav", "/etc/door_sound/203.wav", "/etc/door_sound/201.wav");
        printf("-语音流B顺利执行完毕!
    
    ");
    
    
            //sleep(3);
        printf("-准备播报语音流C..
    ");
        set_pcm_play("/etc/door_sound/254.wav", "/etc/door_sound/254.wav", "/etc/door_sound/254.wav");
        printf("-语音流C顺利执行完毕!
    
    ");
    
    
            return 0;
    }
    
    #endif
    
    
    int para_common_set(int channels, int frequency, 
              int bit, int datablock, snd_pcm_uframes_t *pframes)
    {
            int size = 0, dir=0;;
          snd_pcm_hw_params_t* params;   //硬件信息和PCM流配置
        unsigned int val;
            int ret;
            // 如果要做多线程处理,这里应该维护一个计数器cnt,使用mutex来保护该计数器
            // 打开音频设备时,只有第一次打开才执行snd_pcm_open,以后则只是cnt++
            // 同理,关闭设备时,执行计数器--操作,直到计数器减为0,才执行snd_pcm_close真正将设备关闭
            
            //因为我的程序并不涉及多线程操作音频设备,所以不做上述处理
            //单线程处理,这样够了:使用lock,确保该函数多次调用,只打开一次设备。
            if(!lock) 
            {
            
    
                lock = 1; //snd_pcm_close()内会清零该lock,以便下次再打开设备
    
              ret= snd_pcm_open(&handle, "default",    
                                SND_PCM_STREAM_PLAYBACK, 0);
                if(ret<0)
                {
                        printf("open PCM device failed
    ");
                        
                }
    
            }
    
                snd_pcm_hw_params_alloca(&params); //分配params结构体
    
    
                ret= snd_pcm_hw_params_any(handle, params);//初始化params
                if(ret<0)
                {
                        printf("snd_pcm_hw_params_any err
    ");
                        
                }
    
                ret= snd_pcm_hw_params_set_access(handle, params, 
                                SND_PCM_ACCESS_RW_INTERLEAVED); //初始化访问权限
                if(ret<0)
                {
                        printf("sed_pcm_hw_set_access err
    ");
                        
                }
    
            
            //采样位数
            switch(bit/8)
            {
                    case 1:snd_pcm_hw_params_set_format(handle, params,
                              SND_PCM_FORMAT_U8);
                            break ;
                    case 2:snd_pcm_hw_params_set_format(handle, params,
                              SND_PCM_FORMAT_S16_LE);
                            break ;
                    case 3:snd_pcm_hw_params_set_format(handle, params,
                              SND_PCM_FORMAT_S24_LE);
                            break ;
     
                    default:
                            printf("default 
    ");
                        break;
    
            }
    
    
          ret= snd_pcm_hw_params_set_channels(handle, params,    
                         channels); //设置声道,1表示单声>道,2表示立体声
            if(ret<0)
            {
                    printf("snd_pcm_hw_params_set_channels err
    ");
                    
            }
    
            val = frequency;
    
            ret= snd_pcm_hw_params_set_rate_near(handle, params,   
                        &val, &dir); //设置>频率
            if(ret<0)
            {
                    printf("snd_pcm_hw_params_set_rate_near err
    ");
                    
            }
    
            ret = snd_pcm_hw_params(handle, params);
            if(ret<0)
            {
                  printf("snd_pcm_hw_params err 
    ");
                
            }
    
            ret=snd_pcm_hw_params_get_period_size(params, pframes, 
                       &dir); /*获取周期长度*/
            if(ret<0)
            {
                    printf("snd_pcm_hw_params_get_period_size err
    ");
                    
            }
    
            // 一个数据块,一个数据块,依次读取。
            size = (*pframes) * datablock; /*代表数据块长度*/
    
            printf("--malloc(size), size = %d 
    ",size);
    
            return size;
    }
    
    #define max_num  5
    
    struct wav_file_info {
    
        FILE* fp;
    
        int size;
    
        snd_pcm_uframes_t frames;
    
    
        short channels;
        int frequency;
        short bit;
        short datablock; 
    
    
    }wavinfo[max_num];
    
    
    /*
            path1 = malloc(50);
            path2 = malloc(50);
    
            sprintf(path1,"/TG/sound/%d.wav", 1);
            sprintf(path2,"/TG/sound/%d.wav", 2);
    
    */
    
    
    //void Get_fp_and_wavheader(const char* path, FILE **pfp, struct wav_header* phead_info)
    FILE * Get_fp_and_wavheader(const char* path, struct wav_header* phead_info)
    {
                    if(NULL != path) {
                    
                    printf("path = %s 
    ", path);
    
                    FILE *fp=fopen(path,"rb");
    
                    if(NULL==fp)
                    {
                        printf("open wav failed:
    ");
                        return NULL;
                    }
    
                    printf("fp = %p 
    ", fp);
                    fread(phead_info, 1, sizeof(struct wav_header),fp);
    
                    return fp;
                    /*  
            printf("文件大小rLen:   %d
    ", phead_info->rLen);
            printf("声道数:        %d
    ", phead_info->wChannels);
            printf("采样频率:      %d
    ", phead_info->nSamplesPersec);
            printf("采样的位数:     %d
    ", phead_info->wBitsPerSample);
            printf("wSampleLength=%d
    ", phead_info->wSampleLength);
            */
    
                }
    }
    
    
    // 对fopen的理解:
    // 即使3次fopen同一个文件,也不要紧,会返回3个不同的fp,但是指向同一个文件.
    int set_pcm_play(const char* path1, const char* path2, const char* path3)
    {
            int ret;
            unsigned int size = 0;
                    unsigned int size1 = 0, size2 = 0, size3 = 0;
                    int num=0;
            
            int dir=0;
            snd_pcm_uframes_t frames, frames1, frames2, frames3;
            char *buffer;
    
            int channels;
            int frequency;
            int bit;
            int datablock;
                    
            
            FILE *fp1 = NULL, *fp2 = NULL, *fp3 = NULL;  
                    FILE* fpCur[max_num] = {0};
    
                    const char* file_paths[3] = {0};
    
                    file_paths[0] = path1;
                    file_paths[1] = path2;
                    file_paths[2] = path3;            
    
    
    
                    if(NULL != path3) {
    
                        fp3 = Get_fp_and_wavheader(path3, &wav_header3);
    
                        if(fp3 != NULL) {
    
              channels = wav_header3.wChannels;
              frequency= wav_header3.nSamplesPersec;
              bit      = wav_header3.wBitsPerSample;
              datablock= wav_header3.wBlockAlign; 
    
                        #if 0 
                            printf("fp3 datablock:   %d
    ",   datablock);
                            printf("声道数:        %d
    ", channels);
                            printf("采样频率:      %d
    ", frequency);
                            printf("采样的位数:     %d
    ", bit);
                            printf("wSampleLength=%d
    
    
    ", wav_header3.wSampleLength);
                        #endif
    
    
                        wavinfo[2].channels = channels;
                        wavinfo[2].frequency = frequency;
                        wavinfo[2].bit = bit;
                        wavinfo[2].datablock = datablock;
    
                        size3 = para_common_set(channels, frequency, bit, datablock, &frames3);
                        //获取各自的rames。
                        wavinfo[2].size = size3; 
                        wavinfo[2].frames = frames3;
                        }
                    }
    
    
                    if(NULL != path2) {
                        
                        fp2 = Get_fp_and_wavheader(path2, &wav_header2);
    
                        if(fp2 != NULL) {
    
                            channels = wav_header2.wChannels;
                            frequency= wav_header2.nSamplesPersec;
                            bit      = wav_header2.wBitsPerSample;
                            datablock= wav_header2.wBlockAlign; 
    
                        #if 0 
                            printf("fp2 datablock:   %d
    ",   datablock);
                            printf("声道数:        %d
    ", channels);
                            printf("采样频率:      %d
    ", frequency);
                            printf("采样的位数:     %d
    ", bit);
                            printf("wSampleLength=%d
    
    
    ", wav_header2.wSampleLength);
                        #endif
    
                        wavinfo[1].channels = channels;
                        wavinfo[1].frequency = frequency;
                        wavinfo[1].bit = bit;
                        wavinfo[1].datablock = datablock;
    
                            size2 = para_common_set(channels, frequency, bit, datablock, &frames2);
                            //获取各自的rames。
                            wavinfo[1].size = size2; 
                            wavinfo[1].frames = frames2;
                        }
                    }
    
    
                    if(NULL != path1) {
    
            #if 1
              // 对fopen的理解:
                        // 即使3次fopen同一个文件,也不要紧,会返回3个不同的fp,但是指向同一个文件.
                      fp1 = Get_fp_and_wavheader(path1, &wav_header1);
            #else
            char *path111 = malloc(50);
                     sprintf(path111,"/TG/sound/%d.wav", 1);
                    fp1=fopen(path111,"rb");
            if(fp1==NULL)
            {
              printf("open wav1 failed:
    ");
              return -1;
            }
    
            fread(&wav_header1,1,sizeof(wav_header1),fp1);
            #endif
    
    
            // 实际上给入的每个音频文件的采样率啥的都是一样的,但是每个音频文件的大小可能不一样,
                    // 所以需要单独针对每个音频文件设置这里,获取各自的frames。
                        if(fp1 != NULL) {
    
                            printf("fp1 = %p 
    ", fp1);
                            channels = wav_header1.wChannels;
                            frequency= wav_header1.nSamplesPersec;
                            bit      = wav_header1.wBitsPerSample;
                            datablock= wav_header1.wBlockAlign; 
    
                        #if 0 
                            printf("fp1 datablock:   %d
    ",   datablock);
                            printf("声道数:        %d
    ", channels);
                            printf("采样频率:      %d
    ", frequency);
                            printf("采样的位数:     %d
    ", bit);
                            printf("wSampleLength=%d
    
    
    ", wav_header1.wSampleLength);
                        #endif
    
                        wavinfo[0].channels = channels;
                        wavinfo[0].frequency = frequency;
                        wavinfo[0].bit = bit;
                        wavinfo[0].datablock = datablock;
    
                            size1 = para_common_set(channels, frequency, bit, datablock, &frames1);
                            //获取各自的rames。
                            wavinfo[0].size = size1; 
                            wavinfo[0].frames = frames1;
                        //    printf("size1 = %d 
    ",size1);
                        //    printf("wavinfo[0].size = %d 
    ", wavinfo[0].size);
    
                        }
    
                    }
    
    
                    wavinfo[0].fp = fp1;
                    wavinfo[1].fp = fp2;
                    wavinfo[2].fp = fp3;
    
            #if 0    
                    printf("wavinfo[0].fp = %d 
    ", wavinfo[0].fp);
                    printf("fp1 = %d 
    ", fp1);                
            
            #endif
    
                    if(size1 > size2) {
                        size = size1;
                    }
                    else {
                        size = size2;
                    }
            
                    if(size > size3) {
                    
                    }
                    else {
                        size = size3;
                    }
    
                    printf("size = %d 
    ", size);
    
       
                    buffer = (char*)malloc(size);
    
            #if 1
            // fseek(fp1, 58, SEEK_SET);
    
                    //printf("wavinfo[0].fp = %d 
    ", wavinfo[0].fp);
                    //printf("fp1 = %d 
    ", fp1);    
    
                    if(wavinfo[0].fp) {                
                  fseek(wavinfo[0].fp, 44, SEEK_SET); //定位歌曲到数据区
                    }
                    if(wavinfo[1].fp) {               
                        printf("--2 
    ");
                  fseek(wavinfo[1].fp, 44, SEEK_SET); //定位歌曲到数据区
                    }
                    if(wavinfo[2].fp) {                
                        printf("--3 
    ");
                  fseek(wavinfo[2].fp, 44, SEEK_SET); //定位歌曲到数据区
                    }
            #endif
    
                    int index = 0;
                //    printf("-wavinfo[0].fp = %d 
    ", wavinfo[0].fp);
                //    printf("-wavinfo[1].fp = %d 
    ", wavinfo[1].fp);
                //    printf("-wavinfo[2].fp = %d 
    ", wavinfo[2].fp);
    
              while ((g_stop == 0) && (wavinfo[index].fp))
            {
                                    num++;
    
                    memset(buffer, 0x00, size);
                                    
        
                                    if(wavinfo[index].fp) {
    
                  
                                         // 一个数据块,一个数据块,依次读取。
                                        ret = fread(buffer, 1, wavinfo[index].size, wavinfo[index].fp);
                                        if(ret == 0) {
    
                                                printf("曲目读取 end
    ");
     
                          fclose(wavinfo[index].fp);
                                                wavinfo[index].fp = NULL;
    
                                                index++;
    
                                                continue;  
                                                //printf("--not reached here 
    ");
                                                
                                        }
                                        else if (ret != size) {
                                                printf("left: read %d bytes
    ", ret);
                                        }
    
                                        //printf("-play %d
    ", ret);
    
                                        // 写音频数据到PCM设备 
                                        //struct timeval time1,time2;
                                        //gettimeofday(&time1,NULL); 
    
    
                                        ret = snd_pcm_writei(handle, buffer, wavinfo[index].frames);
                                        //snd_pcm_prepare(handle);
                                        //gettimeofday(&time2,NULL);
                                        //printf("time=%d
    ",            
                                            (time2.tv_sec-time1.tv_sec)*1000000+time2.tv_usec-time1.tv_usec);
                                        if (ret == -EPIPE)
                                        {
                                            printf("underrun occurred
    ");
                                            //完成硬件参数设置,使设备准备好 
                                            snd_pcm_prepare(handle); // 继续
                                        }
                                        else if (ret < 0)
                                        {
                                                printf("error from writei: %s
    ",snd_strerror(ret));
                                        }
                                        else if (ret != (int)wavinfo[index].frames) 
                                        {
                                                printf("short write, write %d frames
    ", ret);
                                        }
    
                                    }
    
    
              }
    
                    snd_pcm_drain(handle); // 清空pcm内部音频数据流缓存
                    snd_pcm_drop(handle);  // 暂停
    
                    int i=0;
                    for(i=0; i<max_num; i++)
                    {
    
                      if(wavinfo[i].fp)
                            fclose(wavinfo[i].fp);
                    }
    
                    lock = 0;
                    snd_pcm_close(handle);
                    free(buffer);
                    handle = NULL;
                    g_stop=0;
            return 0;
    
    }

    makefile:

    .PHONY : rebuild clean
    
    
    CC:= mips-linux-gnu-gcc
    TARGET:= alsa_test.out
    
    
    OBJS:= alsa_test.o 
    
    # 老旧知识点: -I -L -l 详解  
    #-I /home/hello/include   表示将/home/hello/include目录作为第一个寻找头文件的目录
    #-L /home/hello/lib   表示将/home/hello/lib目录作为第一个寻找库文件的目录
    #-lworld      表示在上面的lib的路径中寻找libworld.so动态库文件(如果gcc编译选项中加入了“-static”表示寻找libworld.a静态库文件)
    
    INCLUDE += -I /usr/local/open_lib/include
    
    LIBS += -lpthread -L/usr/local/open_lib/lib -lasound
    
    
    $(TARGET) :$(OBJS)
        $(CC) $(LIBS) $^ -o $@ 
    
    
    $(OBJS):%.o:%.c
        $(CC) $(INCLUDE) -c $^ -o $@
        echo $(OBJS)
    
    
    clean:
        $(RM) $(OBJS)
        $(RM) $(TARGET)
        @echo "clean"
    
    rebuild : clean $(TARGET)
        @echo "rebuild"

    本例子在项目中,功能使用良好。

    .

    /************* 社会的有色眼光是:博士生、研究生、本科生、车间工人; 重点大学高材生、普通院校、二流院校、野鸡大学; 年薪百万、五十万、五万; 这些都只是帽子,可以失败千百次,但我和社会都觉得,人只要成功一次,就能换一顶帽子,只是社会看不见你之前的失败的帽子。 当然,换帽子决不是最终目的,走好自己的路就行。 杭州.大话西游 *******/
  • 相关阅读:
    JVM 规范
    通过jmap查看jvm采用的垃圾收集器
    Nginx做前端Proxy时TIME_WAIT过多的问题
    nginx访问http自动跳转到https
    mysql5.7启动slave报错 ERROR 1872 (HY000): Slave failed to initialize relay log info structure from the repository
    nginx检查报错 error while loading shared libraries: libprofiler.so.0: cannot open shared object file: No such file or directory
    Nginx+Center OS 7.2 开机启动设置(转载)
    windows下安装pycharm并连接Linux的python环境
    jenkins结合脚本实现代码自动化部署及一键回滚至上一版本
    centos7-安装mysql5.6.36
  • 原文地址:https://www.cnblogs.com/happybirthdaytoyou/p/13836191.html
Copyright © 2020-2023  润新知