Linux ALSA音频库(二) 环境测试+音频合成+语音切换 项目代码分享
1. 环境测试
alsa_test.c
#include
#include
// 官方测试代码, 运行后只要有一堆信息打印出来,即说明安装成功了。
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#include if(wavinfo[i].fp) fclose(wavinfo[i].fp); } lock = 0; snd_pcm_close(handle); free(buffer); handle = NULL; g_stop=0; return 0; }#include <string.h> #include #include #include #include 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(¶ms); //分配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 ) {
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"
本例子在项目中,功能使用良好。
.