1. 程式人生 > 實用技巧 >Linux ALSA音訊庫(二) 環境測試+音訊合成+語音切換 專案程式碼分享

Linux ALSA音訊庫(二) 環境測試+音訊合成+語音切換 專案程式碼分享

1. 環境測試

alsa_test.c

#include <alsa/asoundlib.h>
#include <stdio.h>

//  官方測試程式碼, 執行後只要有一堆資訊打印出來,即說明安裝成功了。

int main() 
{
    int val;
 
    printf("ALSA library version: %s\n",
                       SND_LIB_VERSION_STR);
 
    printf("\nPCM stream types:\n");
    for (val = 0; val <= SND_PCM_STREAM_LAST; val++)
            printf(
" %s\n", snd_pcm_stream_name((snd_pcm_stream_t)val)); printf("\nPCM access types:\n"); for (val = 0; val <= SND_PCM_ACCESS_LAST; val++) { printf(" %s\n", snd_pcm_access_name((snd_pcm_access_t)val)); } printf("\nPCM formats:\n"
); for (val = 0; val <= SND_PCM_FORMAT_LAST; val++) { if (snd_pcm_format_name((snd_pcm_format_t)val)!= NULL) { printf(" %s (%s)\n", snd_pcm_format_name((snd_pcm_format_t)val), snd_pcm_format_description( (snd_pcm_format_t)val)); } } printf(
"\nPCM subformats:\n"); for (val = 0; val <= SND_PCM_SUBFORMAT_LAST;val++) { printf(" %s (%s)\n", snd_pcm_subformat_name(( snd_pcm_subformat_t)val), snd_pcm_subformat_description(( snd_pcm_subformat_t)val)); } printf("\nPCM states:\n"); for (val = 0; val <= SND_PCM_STATE_LAST; val++) printf(" %s\n", 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----\n\n");


        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-------\n\n");


        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-------\n\n");
    #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-------\n\n");
    #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 \n");
        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..\n");
        set_pcm_play("/etc/door_sound/254.wav", "/etc/door_sound/203.wav", "/etc/door_sound/201.wav");
    printf("-已關閉(中止)當前語音流A!\n\n");

        //sleep(3);
    printf("-準備播報語音流B..\n");
    set_pcm_play("/etc/door_sound/254.wav", "/etc/door_sound/203.wav", "/etc/door_sound/201.wav");
    printf("-語音流B順利執行完畢!\n\n");


        //sleep(3);
    printf("-準備播報語音流C..\n");
    set_pcm_play("/etc/door_sound/254.wav", "/etc/door_sound/254.wav", "/etc/door_sound/254.wav");
    printf("-語音流C順利執行完畢!\n\n");


        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\n");
                    
            }

        }

            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\n");
                    
            }

            ret= snd_pcm_hw_params_set_access(handle, params, \
                            SND_PCM_ACCESS_RW_INTERLEAVED); //初始化訪問許可權
            if(ret<0)
            {
                    printf("sed_pcm_hw_set_access err\n");
                    
            }

        
        //取樣位數
        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 \n");
                    break;

        }


      ret= snd_pcm_hw_params_set_channels(handle, params,    \
                     channels); //設定聲道,1表示單聲>道,2表示立體聲
        if(ret<0)
        {
                printf("snd_pcm_hw_params_set_channels err\n");
                
        }

        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\n");
                
        }

        ret = snd_pcm_hw_params(handle, params);
        if(ret<0)
        {
              printf("snd_pcm_hw_params err \n");
            
        }

        ret=snd_pcm_hw_params_get_period_size(params, pframes, \
                   &dir); /*獲取週期長度*/
        if(ret<0)
        {
                printf("snd_pcm_hw_params_get_period_size err\n");
                
        }

        // 一個數據塊,一個數據塊,依次讀取。
        size = (*pframes) * datablock; /*代表資料塊長度*/

        printf("--malloc(size), size = %d \n",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 \n", path);

                FILE *fp=fopen(path,"rb");

                if(NULL==fp)
                {
                    printf("open wav failed:\n");
                    return NULL;
                }

                printf("fp = %p \n", fp);
                fread(phead_info, 1, sizeof(struct wav_header),fp);

                return fp;
                /*  
        printf("檔案大小rLen:   %d\n", phead_info->rLen);
        printf("聲道數:        %d\n", phead_info->wChannels);
        printf("取樣頻率:      %d\n", phead_info->nSamplesPersec);
        printf("取樣的位數:     %d\n", phead_info->wBitsPerSample);
        printf("wSampleLength=%d\n", 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\n",   datablock);
                        printf("聲道數:        %d\n", channels);
                        printf("取樣頻率:      %d\n", frequency);
                        printf("取樣的位數:     %d\n", bit);
                        printf("wSampleLength=%d\n\n\n", 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\n",   datablock);
                        printf("聲道數:        %d\n", channels);
                        printf("取樣頻率:      %d\n", frequency);
                        printf("取樣的位數:     %d\n", bit);
                        printf("wSampleLength=%d\n\n\n", 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:\n");
          return -1;
        }

        fread(&wav_header1,1,sizeof(wav_header1),fp1);
        #endif


        // 實際上給入的每個音訊檔案的取樣率啥的都是一樣的,但是每個音訊檔案的大小可能不一樣,
                // 所以需要單獨針對每個音訊檔案設定這裡,獲取各自的frames。
                    if(fp1 != NULL) {

                        printf("fp1 = %p \n", fp1);
                        channels = wav_header1.wChannels;
                        frequency= wav_header1.nSamplesPersec;
                        bit      = wav_header1.wBitsPerSample;
                        datablock= wav_header1.wBlockAlign; 

                    #if 0 
                        printf("fp1 datablock:   %d\n",   datablock);
                        printf("聲道數:        %d\n", channels);
                        printf("取樣頻率:      %d\n", frequency);
                        printf("取樣的位數:     %d\n", bit);
                        printf("wSampleLength=%d\n\n\n", 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 \n",size1);
                    //    printf("wavinfo[0].size = %d \n", wavinfo[0].size);

                    }

                }


                wavinfo[0].fp = fp1;
                wavinfo[1].fp = fp2;
                wavinfo[2].fp = fp3;

        #if 0    
                printf("wavinfo[0].fp = %d \n", wavinfo[0].fp);
                printf("fp1 = %d \n", fp1);                
        
        #endif

                if(size1 > size2) {
                    size = size1;
                }
                else {
                    size = size2;
                }
        
                if(size > size3) {
                
                }
                else {
                    size = size3;
                }

                printf("size = %d \n", size);

   
                buffer = (char*)malloc(size);

        #if 1
        // fseek(fp1, 58, SEEK_SET);

                //printf("wavinfo[0].fp = %d \n", wavinfo[0].fp);
                //printf("fp1 = %d \n", fp1);    

                if(wavinfo[0].fp) {                
              fseek(wavinfo[0].fp, 44, SEEK_SET); //定位歌曲到資料區
                }
                if(wavinfo[1].fp) {               
                    printf("--2 \n");
              fseek(wavinfo[1].fp, 44, SEEK_SET); //定位歌曲到資料區
                }
                if(wavinfo[2].fp) {                
                    printf("--3 \n");
              fseek(wavinfo[2].fp, 44, SEEK_SET); //定位歌曲到資料區
                }
        #endif

                int index = 0;
            //    printf("-wavinfo[0].fp = %d \n", wavinfo[0].fp);
            //    printf("-wavinfo[1].fp = %d \n", wavinfo[1].fp);
            //    printf("-wavinfo[2].fp = %d \n", 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\n");
 
                      fclose(wavinfo[index].fp);
                                            wavinfo[index].fp = NULL;

                                            index++;

                                            continue;  
                                            //printf("--not reached here \n");
                                            
                                    }
                                    else if (ret != size) {
                                            printf("left: read %d bytes\n", ret);
                                    }

                                    //printf("-play %d\n", 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\n",            \
                                        (time2.tv_sec-time1.tv_sec)*1000000+time2.tv_usec-time1.tv_usec);
                                    if (ret == -EPIPE)
                                    {
                                        printf("underrun occurred\n");
                                        //完成硬體引數設定,使裝置準備好 
                                        snd_pcm_prepare(handle); // 繼續
                                    }
                                    else if (ret < 0)
                                    {
                                            printf("error from writei: %s\n",snd_strerror(ret));
                                    }
                                    else if (ret != (int)wavinfo[index].frames) 
                                    {
                                            printf("short write, write %d frames\n", 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"

本例子在專案中,功能使用良好。

.