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(¶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<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"
本例子在项目中,功能使用良好。
.
Linux ALSA音频库(二) 环境测试+音频合成+语音切换 项目代码分享的更多相关文章
- Linux ALSA 音频库 配置和使用
ALSA应用库是核心功能,而alsa-utils是一些工具功能集合库.单纯地播放一个wav文件,使用alsa-utils即可,如果还需要合成音频.调试音频质量,那么就需要ALSA应用库. 欲安装使用A ...
- 基于Linux ALSA音频驱动的wav文件解析及播放程序 2012
本设计思路:先打开一个普通wav音频文件,从定义的文件头前面的44个字节中,取出文件头的定义消息,置于一个文件头的结构体中.然后打开alsa音频驱动,从文件头结构体取出采样精度,声道数,采样频率三个重 ...
- 嵌入式驱动开发之---Linux ALSA音频驱动(一)
本文的部分内容参考来自DroidPhone的博客(http://blog.csdn.net/droidphone/article/details/6271122),关于ALSA写得很不错的文章,只是少 ...
- 构建微服务开发环境7————使用Github管理项目代码的版本
[内容指引] 1.注册GitHub帐号: 2.下载Github Desktop客户端: 3.macOS安装Github Desktop客户端: 4.windows安装Github Desktop客户端 ...
- linux alsa音频中采样率fs、比特率BCLK 、主时钟MCLK关系
转:https://blog.csdn.net/lugandong/article/details/72468831 一.拿512fs说话: 看图知道采样的位深是32bit(位),左右声道各占了8*3 ...
- Linux ALSA音频PCM播放编程
使用ALSA播放两个频率的单音,并使用GNU Radio中的Audio Source和FFT来观测声音的频谱. #include <alsa/asoundlib.h> #include & ...
- 基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(三)
作品已经完成,先上源码: https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip 全文包含三篇,这是第三篇,主要讲述接收端程序的原理和过程. 第一 ...
- Postman 使用技巧之多环境测试及接口依赖关系处理
一.前言 在日常开发中,除了正常的单元测试,某些情况我们还需要测试 HTTP 接口,团队中目前使用的是「 Postman 」这款 API调试 . HTTP 请求工具.通常我们将经常要测试的接口按照项目 ...
- Linux实战教学笔记41:企业级SVN版本管理与大型代码上线方案
第1章 SVN服务实战应用指南 1.1 SVN介绍 1.1.1 什么是SVN(Subversion)? Svn(subversion)是近年来崛起的非常优秀的版本管理工具,与CVS管理工具一样,SVN ...
随机推荐
- Django 仿ajax传递数据(Django十)
之前用form表单传递数据,没有遇到任何问题 具体见:https://blog.csdn.net/qq_38175040/article/details/104867747 然后现在我想用ajax传递 ...
- vue3 报错解决:找不到模块‘xxx.vue’或其相应的类型声明。(Vue 3 can not find module)
最近在用 vue3 写一个小组件库,在 ts 文件中引入 .vue 文件时出现以下报错: 报错原因:typescript 只能理解 .ts 文件,无法理解 .vue文件 解决方法:在项目根目录或 sr ...
- [Binder深入学习一]Binder驱动——基础数据结构
具体代码路径: kernel/drivers/staging/android/binder.c kernel/drivers/staging/android/binder.h /* * binder_ ...
- adb命令—monkey篇
monkey 目录 monkey 1.Monkey介绍 2.Monkey是用来做什么的 3.Monkey程序介绍 下面就是一些Monkey命令了 1.Monkey介绍 顾名思义,Monkey就是猴子, ...
- JDK15真的来了,一起来看看它的新特性
目录 简介 JDK15的新特性 JEP 385 Deprecate RMI Activation for Removal JEP 371 Hidden Classes JEP 339 Edwards- ...
- 【转】Locust-工具核心原理分析
Locust工具在市场上不如Loadrunner / JMeter流行,使用的范围也没有那么广,但不可否认其是一款很不错的工具.我个人觉得Locust使用不是那么广泛,主要是因为一下方式: Locus ...
- 一篇带你熟悉ansible-playbook剧本
#playbook介绍 #playbook简单介绍 playbook翻译过来就是剧本,以yml/yaml为后缀结尾的一个文本文件 #playbook组成:分为两部分play(定义主机的角色)和task ...
- JVM性能调优(2) —— 垃圾回收器和回收策略
一.垃圾回收机制 1.为什么需要垃圾回收 Java 程序在虚拟机中运行,是会占用内存资源的,比如创建的对象.加载的类型数据等,而且内存资源都是有限的.当创建的对象不再被引用时,就需要被回收掉,释放内存 ...
- XXE漏洞介绍 & XXE漏洞攻击 & 修复建议
介绍XXE漏洞 XML外部实体注入(XML External Entity)简称XXE漏洞,XML用于标记电子文件使其具有结构性的标记语言,可以用来标记数据.定义数据类型,是-种允许用户对自己的标记语 ...
- 一键生成数据库文档,堪称数据库界的Swagger,有点厉害
最近部门订单业务调整,收拢其他业务线的下单入口,做个统一大订单平台.需要梳理各业务线的数据表,但每个业务线库都有近百张和订单相关的表,挨个表一个一个字段的弄脑瓜子嗡嗡的. 为了不重复 CV 操作,抱着 ...