Linux下的音频采集_linux 音频采集-程序员宅基地

技术标签: alsa  一个小型视频监控系统  pcm  

写在前面的话:以下代码均是从自己的项目中摘出来的,类似调试打印,结构体都是自己定义的,需要修改后才能使用,需要源码的可直接到总结博客写一个自己的小型视频监控系统

 

开始写代码前,需要先了解一些基础的音频知识,有看到一篇很不错的基础讲解博客:https://blog.csdn.net/caoshangpa/article/details/51218597

这里我再简单把这几个重要的属性说明下:

1.采样率:声音信号是在物理学中是波,有相应的振幅,频率等,是一个连续信号,需要转化为离散信号才可被计算机处理(大学的数字信号处理有介绍这些)。采样率越高,音质越好,相应的占用空间越大。电话对讲中使用8kHZ采样率,可以满足人与人之间基本对话了。

2.采样长度(位数):每次采样的字节数,也是越大越清晰,但占用空间相应也会增大,现在常见的都是16位。

3.通道数: 单通道无法辨别声源位置,主要影响的是人的听觉体验,通道数越多,体验越好,相对应的占用空间也会越大,就使用而言,单声道和双声道均可以满足音频的日常使用

4.帧:对PCM而言,没什么用

5.周期:对设备读取或者访问的单位

6.交错模式:音频数据的存储方式左右交替存储

7.非交错模式:先存左声道,再存右声道

8.比特率:这里写个公式好了,比特率=采样率×通道数×采样长度,就是每秒钟要发送的比特位。

说到Linux下的音频采集,应该没人不知道ALSA。只是做简单的音频采集,不做什么特别高端的音频功能,alsa肯定是不二选择。alsa调用过程也很简单,就是把上面介绍的参数统统配置给驱动,然后让驱动把pcm流推给我们就好。

ALSA接口介绍有一篇很详细的文章:https://blog.csdn.net/yuzaipiaofei/article/details/90582554

英文还可以的建议直接看手册:https://www.alsa-project.org/alsa-doc/alsa-lib/

大致流程:打开设备->配置参数->读取音频数据

1.设备初始化(打开设备+配置参数)

typedef struct
{
    snd_pcm_t *handle;
    snd_pcm_uframes_t frames;
    int buffSize;
    char* readBuffer;
    void (*deal_pcm)(void*,int);
}AudioParam;

// 输入参数先不增加,采样率,采样长度,通道数,交错模式,周期都先写死
int audio_init(AudioParam *audioParam)
{ 
    snd_pcm_hw_params_t *params;
    unsigned int sampleRate = 8000;
    snd_pcm_uframes_t frames = 1024;
    int retValue = -1;

    // 摄像头只有麦克,只实现录音功能
    retValue = snd_pcm_open(&(audioParam->handle), "default", SND_PCM_STREAM_CAPTURE, 0);
    if(retValue < 0)
    {
        DBGLOG("can not open pcm device:%s\n", snd_strerror(retValue));
        return ERROR;
    }

    snd_pcm_hw_params_alloca(&params);

    // 使用默认参数
    snd_pcm_hw_params_any(audioParam->handle, params);
    snd_pcm_hw_params_set_access(audioParam->handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);    // 交错访问
    snd_pcm_hw_params_set_format(audioParam->handle, params, SND_PCM_FORMAT_S16_LE);    // 有符号16bit,小端 2bytes
    snd_pcm_hw_params_set_channels(audioParam->handle, params, 1);  // 单声道
    snd_pcm_hw_params_set_rate_near(audioParam->handle, params, &sampleRate, 0);    // 采样率8k
    snd_pcm_hw_params_set_period_size_near(audioParam->handle, params, &frames, 0);

    retValue = snd_pcm_hw_params(audioParam->handle, params);
    if(retValue < 0)
    {
        DBGLOG("set hw params error:%s\n", snd_strerror(retValue));
        return ERROR;
    }

    snd_pcm_hw_params_get_period_size(params, &(audioParam->frames), 0);
    audioParam->buffSize = audioParam->frames * 2; // 16bit = 2bytes / sample , 1 channel
    return OK;
}

打开设备使用函数snd_pcm_open,这里我们主要关心第二个函数入参,我只接了摄像头自己的带的麦,直接用default,就是摄像头自己麦的输出,但如果有多个麦克的话,则需要确认下哪个是默认麦克,因为虚拟机有alsa-lib,使用aplay -l,可以查看设备,我接了两个麦克后,会新增一个设备,如:

[~]$aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: AudioPCI [Ensoniq AudioPCI], device 0: ES1371/1 [ES1371 DAC2/ADC]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: AudioPCI [Ensoniq AudioPCI], device 1: ES1371/2 [ES1371 DAC1]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 2: Microphone [USB Microphone], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

此时我们可以指定使用哪个麦克,将“default”换为“hw:0,0”或者“hw:2,0”即可指定麦克输入。只有一个的情况下直接default最为简单,此处我只是多接一个麦测试下。因为摄像头只有麦克,所以只是作为输入源使用,没有写alsa播放的代码,采样率,采样长度,通道数等也都是一次性写死的,前期方便设置的来做,感兴趣的可以把这些参数作为函数入参去测试下,正好了解下音频的属性。这里强调一下使用snd_pcm_hw_params_set_period_size_near来设置一次中断返回的数据单元(以帧为单位),每帧的数据量跟声道数,采样长度有关系。驱动会根据配置的值找一个相邻的或者就是原始值,即可能不是配置的值,故要重新snd_pcm_hw_params_get_period_size来获取驱动返回的周期,来申请接收缓冲区,这里我设置的是单声道,有符号16bit采样长度,所以申请的缓冲区大小为驱动返回的大小乘以2

2.读取音频数据

int audio_read_pcm(AudioParam *audioParam)
{
    int retValue = -1;

    if(NULL == audioParam || NULL == audioParam->handle || audioParam->frames <= 0)
    {
        DBGLOG("audio_read_pcm input error!\n");
        return ERROR;
    }

    
    audioParam->readBuffer = (char*)malloc(audioParam->buffSize);
    if(NULL == audioParam->readBuffer)
    {
        DBGLOG("audio_read_pcm malloc error!\n");
        return ERROR;
    }
    
    retValue = snd_pcm_readi(audioParam->handle, audioParam->readBuffer, audioParam->frames);
    if(-EPIPE == retValue)
    {
        DBGLOG("overrun occurred!\n");
        snd_pcm_prepare(audioParam->handle);
    }
    else if(retValue < 0)
    {
        DBGLOG("read error: %s\n", snd_strerror(retValue));
        return ERROR;
    }
    else if(retValue != (int)audioParam->frames)
    {
        DBGLOG("read %d less than frames[%d]\n", retValue, (int)audioParam->frames);
        return ERROR;
    }
    else
    {
        audioParam->deal_pcm(audioParam->readBuffer, audioParam->buffSize);
    }
    return OK;
}

void audio_close(AudioParam *audioParam)
{
    if(NULL != audioParam->handle)
    {
        snd_pcm_drain(audioParam->handle);
        snd_pcm_close(audioParam->handle);
    }

    if(NULL != audioParam->readBuffer)
    {
        free(audioParam->readBuffer);
        audioParam->readBuffer = NULL;
    }
}

读取操作就比较简单了,调用接口,收到是之前配置的周期处理单元数据就可以直接处理数据了。需要注意的一点是前面初始化时候snd_pcm_hw_params_set_access设置的是交错模式则使用snd_pcm_readi,设置的是非交错模式则使用snd_pcm_readn。返回值是-EPIPE则说明是数据管道异常,可能是正好瞬时性能不足导致之类的,可以使用snd_pcm_prepare恢复。

 

测试Demo:

void save_pcm(void* start, int length)
{
    char fileName[64] = {0};
    int fd = -1;

    snprintf(fileName, sizeof(fileName), "./test/pcm/test.pcm");
    printf("%s\n", fileName);
    fd = open(fileName, O_RDWR | O_CREAT | O_APPEND , 0666);
    if(fd < 0)
        printf("open:%d %s\n", errno, strerror(errno));
    write(fd, start, length);
    close(fd);
}


int get_file_size(void)
{
    struct  stat devStat;
    memset(&devStat, 0, sizeof(devStat));
    stat("./test/pcm/test.pcm", &devStat);
    return devStat.st_size;
}

void start_audio_capture(void)
{
    AudioParam audioParam;
    memset(&audioParam, 0, sizeof(audioParam));
    audioParam.deal_pcm = save_pcm;

    if(audio_init(&audioParam) < 0)
    {
        DBGLOG("audio_init error\n");
        return;
    }

    while(get_file_size() < 50*1024)
    {
        if(audio_read_pcm(&audioParam) < 0)
        {
            DBGLOG("audio_read_pcm error!\n");
            return;
        }
    }
    

    audio_close(&audioParam);
}

这里写了个小的测试demo,直接将采集的pcm存入文件,读取50k的数据,可以用Adobe audition或者cool edit等工具查看pcm是否正常。

相较于v4l2的调用,alsa的调用其实还是比较简单的,现在有了pcm可以准备下一步工作——编码。

 

 

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_41813395/article/details/114559866

智能推荐

WCE Windows hash抓取工具 教程_wce.exe -s aaa:win-9r7tfgsiqkf:0000000000000000000-程序员宅基地

文章浏览阅读6.9k次。WCE 下载地址:链接:https://share.weiyun.com/5MqXW47 密码:bdpqku工具界面_wce.exe -s aaa:win-9r7tfgsiqkf:00000000000000000000000000000000:a658974b892e

各种“网络地球仪”-程序员宅基地

文章浏览阅读4.5k次。Weather Globe(Mackiev)Google Earth(Google)Virtual Earth(Microsoft)World Wind(NASA)Skyline Globe(Skylinesoft)ArcGISExplorer(ESRI)国内LTEarth(灵图)、GeoGlobe(吉奥)、EV-Globe(国遥新天地) 软件名称: 3D Weather Globe(http:/_网络地球仪

程序员的办公桌上,都出现过哪些神奇的玩意儿 ~_程序员展示刀,产品经理展示枪-程序员宅基地

文章浏览阅读1.9w次,点赞113次,收藏57次。我要买这些东西,然后震惊整个办公室_程序员展示刀,产品经理展示枪

霍尔信号、编码器信号与电机转向-程序员宅基地

文章浏览阅读1.6w次,点赞7次,收藏63次。霍尔信号、编码器信号与电机转向从电机出轴方向看去,电机轴逆时针转动,霍尔信号的序列为编码器信号的序列为将霍尔信号按照H3 H2 H1的顺序组成三位二进制数,则霍尔信号翻译成状态为以120°放置霍尔为例如不给电机加电,使用示波器测量三个霍尔信号和电机三相反电动势,按照上面所说的方向用手转动电机得到下图① H1的上升沿对应电机q轴与H1位置电角度夹角为0°,..._霍尔信号

个人微信淘宝客返利机器人搭建教程_怎么自己制作返利机器人-程序员宅基地

文章浏览阅读7.1k次,点赞5次,收藏36次。个人微信淘宝客返利机器人搭建一篇教程全搞定天猫淘宝有优惠券和返利,仅天猫淘宝每年返利几十亿,你知道么?技巧分享:在天猫淘宝京东拼多多上挑选好产品后,按住标题文字后“复制链接”,把复制的淘口令或链接发给机器人,复制机器人返回优惠券口令或链接,再打开天猫或淘宝就能领取优惠券啦下面教你如何搭建一个类似阿可查券返利机器人搭建查券返利机器人前提条件1、注册微信公众号(订阅号、服务号皆可)2、开通阿里妈妈、京东联盟、拼多多联盟一、注册微信公众号https://mp.weixin.qq.com/cgi-b_怎么自己制作返利机器人

【团队技术知识分享 一】技术分享规范指南-程序员宅基地

文章浏览阅读2.1k次,点赞2次,收藏5次。技术分享时应秉持的基本原则:应有团队和个人、奉献者(统筹人)的概念,同时匹配团队激励、个人激励和最佳奉献者激励;团队应该打开工作内容边界,成员应该来自各内容方向;评分标准不应该过于模糊,否则没有意义,应由客观的基础分值以及分团队的主观综合结论得出。应有心愿单激励机制,促进大家共同聚焦到感兴趣的事情上;选题应有规范和框架,具体到某个小类,这样收获才有目标性,发布分享主题时大家才能快速判断是否是自己感兴趣的;流程和分享的模版应该有固定范式,避免随意的格式导致随意的内容,评分也应该部分参考于此;参会原则,应有_技术分享

随便推点

O2OA开源企业办公开发平台:使用Vue-CLI开发O2应用_vue2 oa-程序员宅基地

文章浏览阅读1k次。在模板中,我们使用了标签,将由o2-view组件负责渲染,给o2-view传入了两个参数:app="内容管理数据"和name="所有信息",我们将在o2-view组件中使用这两个参数,用于展现“内容管理数据”这个数据应用下的“所有信息”视图。在o2-view组件中,我们主要做的事是,在vue组件挂载后,将o2的视图组件,再挂载到o2-view组件的根Dom对象。当然,这里我们要在我们的O2服务器上创建好数据应用和视图,对应本例中,就是“内容管理数据”应用下的“所有信息”视图。..._vue2 oa

[Lua]table使用随笔-程序员宅基地

文章浏览阅读222次。table是lua中非常重要的一种类型,有必要对其多了解一些。

JAVA反射机制原理及应用和类加载详解-程序员宅基地

文章浏览阅读549次,点赞30次,收藏9次。我们前面学习都有一个概念,被private封装的资源只能类内部访问,外部是不行的,但这个规定被反射赤裸裸的打破了。反射就像一面镜子,它可以清楚看到类的完整结构信息,可以在运行时动态获取类的信息,创建对象以及调用对象的属性和方法。

Linux-LVM与磁盘配额-程序员宅基地

文章浏览阅读1.1k次,点赞35次,收藏12次。Logical Volume Manager,逻辑卷管理能够在保持现有数据不变的情况下动态调整磁盘容量,从而提高磁盘管理的灵活性/boot分区用于存放引导文件,不能基于LVM创建PV(物理卷):基于硬盘或分区设备创建而来,生成N多个PE,PE默认大小4M物理卷是LVM机制的基本存储设备,通常对应为一个普通分区或整个硬盘。创建物理卷时,会在分区或硬盘的头部创建一个保留区块,用于记录 LVM 的属性,并把存储空间分割成默认大小为 4MB 的基本单元(PE),从而构成物理卷。

车充产品UL2089安规测试项目介绍-程序员宅基地

文章浏览阅读379次,点赞7次,收藏10次。4、Dielecteic voltage-withstand test 介电耐压试验。1、Maximum output voltage test 输出电压试验。6、Resistance to crushing test 抗压碎试验。8、Push-back relief test 阻力缓解试验。7、Strain relief test 应变消除试验。2、Power input test 功率输入试验。3、Temperature test 高低温试验。5、Abnormal test 故障试验。

IMX6ULL系统移植篇-系统烧写原理说明_正点原子 imx6ull nand 烧录-程序员宅基地

文章浏览阅读535次。镜像烧写说明_正点原子 imx6ull nand 烧录