Linux音频采集和在国产化平台中遇到的坑(二)
Linux音频采集和在国产化平台中遇到的坑(二)
ALSA采集这条路走不通,只能尝试其他途径,这里通过PulseAudio的接口成功实现了采集麦克风和系统声音的功能。
linux PulseAudio音频采集
首先,PulseAudio跟ALSA不同的不同之处是,ALSA是内核级的,而PulseAudio则是用户层的服务,并且是作为Sound Server的形式,来管理应用程序的各种音频输入和输出,跟ALSA相同,大多数linux发行版都默认安装PulseAudio。我们这里的国产化芯片平台的银河麒麟自然也不例外。PulseAudio的结构图是这个样子的:

可以看到,PulseAudio作为服务,是位于ALSA上层的,可以让多个应用程序同时调用PulseAudio,由它内部做音频的mixer,这样可以避免由于ALSA的独占性而导致程序在不同的硬件环境下出现无法正常使用的情况。应用程序和PulseAudio之间的调用关系如下:

通常情况下,系统不会预装PulseAudio的开发包,这个时候我们需要安装一下,这样才能在代码中调用接口。
sudo apt-get install libpulse-dev
PulseAudio音频采集,是明显比ALSA复杂的多,每个应用程序,都考虑是作为一个PulseAudio的client端,与系统的PulseAudio服务进行连接,并且都需要维护一个线程来作为数据传递的循环队列。下面罗列一下种族要使用的几个函数:
#include <pulse/pulseaudio.h>
/***
 申请一个包含线程的事件循环
*/
pa_threaded_mainloop* pa_threaded_mainloop_new();
/***
 开启事件循环
 @return: 0表示成功,小于0表示错误码
*/
int pa_threaded_mainloop_start(pa_threaded_mainloop* m);
/***
 终止事件循环,在调用此函数前,必须确保事件循环已经解锁
*/
void pa_threaded_mainloop_stop(pa_threaded_mainloop* m);
/***
 阻塞并等待事件循环中消息被触发,注意,该函数返回并不一定是因为调用了pa_threaded_mainloop_signal()
 需要甄别这一点
*/
void pa_threaded_mainloop_wait(pa_threaded_mainloop* m);
/***
 触发消息
*/
void pa_threaded_mainloop_signal(pa_threaded_mainloop* m, int wait_for_accept);
#include <pulse/pulseaudio.h>
/***
 创建PulseAudio连接上下文
*/
pa_context* pa_context_new(pa_mainloop_api *mainloop, const char *name);
/***
 将context连接到指定的PulseAudio服务,如果server为NULL,则连接到系统默认服务。
 @return: 小于0表示错误
*/
int pa_context_connect(pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api);
/***
 终止事件循环,在调用此函数前,必须确保事件循环已经解锁
*/
void pa_context_disconnect(pa_context* c);
/***
 引用计数减1
*/
void pa_context_unref(pa_context* c);
/***
 返回当前上下文状态
*/
pa_context_state_t pa_context_get_state(const pa_context* c);
#include <pulse/pulseaudio.h>
/***
 在当前PulseAudio连接上,创建一个stream,用于输入或输出音频数据
*/
pa_stream* pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map);
/***
 将context连接到指定的PulseAudio服务,如果server为NULL,则连接到系统默认服务。
 @return: 小于0表示错误
*/
int pa_stream_connect_record(pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api);
/***
 从缓冲区中读取下一个采集的音频片段
*/
int pa_stream_peek(pa_stream *p, const void **data, size_t *nbytes);
/***
 放弃当前输入(采集)的音频片段
*/
void pa_stream_drop(pa_stream* s);
/***
 关闭输入输出流
*/
void pa_stream_disconnect(pa_stream* s);
/***
 引用计数减1
*/
void pa_stream_unref(pa_stream* s);
/***
 返回当前stream状态
*/
pa_context_state_t pa_stream_get_state(const pa_stream* s);
下面写个简单的例子演示下如何调用
- 创建事件循环,连接PulseAudio服务器,创建stream并设置参数。为了看起来更加直观,这里我删除了一些错误判断的代码。
bool PulseAudioCapture::Start(Observer* ob)
{
    observer_ = ob;
    SIMPLE_LOG("try open %s\n", device_name_.c_str());
    int ret = 0;
    const char* name = "HbsPulse";
    const char* stream_name = "HbsPulseStream";
    char* device = NULL;
    if (false == device_name_.empty())
    {
        device = (char*)device_name_.c_str();
    }
    const struct pa_sample_spec *pss = nullptr;
    pa_sample_format_t sam_fmt = AV_NE(PA_SAMPLE_S16BE, PA_SAMPLE_S16LE);
    const pa_sample_spec ss = { sam_fmt, sample_rate_, channel_count_ };
    pa_buffer_attr attr = { (uint32_t)-1 };
    pa_channel_map cmap;
    const pa_buffer_attr *queried_attr = nullptr;
    int stream_flag = 0;
    pa_channel_map_init_extend(&cmap, channel_count_, PA_CHANNEL_MAP_WAVEEX);
    mainloop_ = pa_threaded_mainloop_new();
    context_ = pa_context_new(pa_threaded_mainloop_get_api(mainloop_), name);
    pa_context_set_state_callback(context_, context_state_cb, this);
    pa_context_connect(context_, pulse_server_, /*0*/PA_CONTEXT_NOFLAGS, NULL);
    pa_threaded_mainloop_lock(mainloop_);
    pa_threaded_mainloop_start(mainloop_);
    for (;;)
    {
        pa_context_state_t state = pa_context_get_state(context_);
        if (state == PA_CONTEXT_READY)
            break;
        if (!PA_CONTEXT_IS_GOOD(state))
        {
            int ec = pa_context_errno(context_);
            SIMPLE_LOG("pulse context state bad: %d, err: %d\n", state, ec);
            goto unlock_and_fail;
        }
        /* Wait until the context is ready */
        pa_threaded_mainloop_wait(mainloop_);
    }
    SIMPLE_LOG("pulse context ready!\n");
    stream_ = pa_stream_new(context_, stream_name, &ss, &cmap);
    pa_stream_set_state_callback(stream_, stream_state_cb, this);
    pa_stream_set_read_callback(stream_, stream_read_cb, this);
    pa_stream_set_write_callback(stream_, stream_write_cb, this);
    pa_stream_set_latency_update_callback(stream_, stream_latency_update_cb, this);
    ret = pa_stream_connect_record(stream_, device, &attr,
        PA_STREAM_ADJUST_LATENCY|PA_STREAM_AUTO_TIMING_UPDATE);
    for (;;)
    {
        pa_stream_state_t state = pa_stream_get_state(stream_);
        if (state == PA_STREAM_READY)
            break;
        if (!PA_STREAM_IS_GOOD(state))
        {
            int ec = pa_context_errno(context_);
            SIMPLE_LOG("pulse stream state bad: %d, err: %d\n", state, ec);
            goto unlock_and_fail;
        }
        /* Wait until the stream is ready */
        pa_threaded_mainloop_wait(mainloop_);
    }
    pa_threaded_mainloop_unlock(mainloop_);
    SIMPLE_LOG("pulse audio start ok, fragsize: %d, framesize: %d\n", fragment_size_, pa_frame_size_);
    ThreadStart();
    return true;
unlock_and_fail:
    pa_threaded_mainloop_unlock(mainloop_);
    ClosePulse();
    return false;
}
- 读取音频数据
bool PulseAudioCapture::ReadData()
{
    int ret;
    size_t read_length;
    const void *read_data = NULL;
    pa_usec_t latency;
    int negative;
    ptrdiff_t pos = 0;
    pa_threaded_mainloop_lock(mainloop_);
    if (IsPulseDead())
    {
        SIMPLE_LOG("pulse is dead\n");
        goto unlock_and_fail;
    }
    while (pos < fragment_size_)
    {
        int r = pa_stream_peek(stream_, &read_data, &read_length);
        if (r != 0)
        {
            SIMPLE_LOG("pa_stream_peek: %d\n", r);
            goto unlock_and_fail;
        }
        if (read_length <= 0)
        {
            pa_threaded_mainloop_wait(mainloop_);
            if (IsPulseDead())
            {
                SIMPLE_LOG("pulse is dead\n");
                goto unlock_and_fail;
            }
        }
        else if (!read_data)
        {
            /* There's a hole in the stream, skip it. We could generate
            * silence, but that wouldn't work for compressed streams. */
            r = pa_stream_drop(stream_);
            if (r != 0)
            {
                SIMPLE_LOG("null data, pa_stream_drop: %d\n", r);
                goto unlock_and_fail;
            }
        }
        else
        {
            if (!pos)
            {
                if (pcm_buf_.empty())
                {
                    pcm_buf_.resize(fragment_size_);
                }
                //pcm_dts_ = av_gettime();
                pa_operation_unref(pa_stream_update_timing_info(stream_, NULL, NULL));
                if (pa_stream_get_latency(stream_, &latency, &negative) >= 0)
                {
                    if (negative)
                    {
                        pcm_dts_ += latency;
                    }
                    else
                        pcm_dts_ -= latency;
                }
                else
                {
                    SIMPLE_LOG("pa_stream_get_latency() failed\n");
                }
            }
            if (pcm_buf_.size() - pos < read_length)
            {
                if (pos)
                    break;
                pa_stream_drop(stream_);
                /* Oversized fragment??? */
                SIMPLE_LOG("Oversized fragment\n");
                goto unlock_and_fail;
            }
            memcpy(pcm_buf_.data() + pos, read_data, read_length);
            pos += read_length;
            pa_stream_drop(stream_);
        }
    }
SIMPLE_LOG("read pos: %d\n", pos);
    pa_threaded_mainloop_unlock(mainloop_);
    return true;
unlock_and_fail:
    pa_threaded_mainloop_unlock(mainloop_);
    return false;
}
选择音频设备的时候,音频设备名称,必须是通过PulseAudio相关接口查询出来的,对于音频采集设备,可以调用pa_context_get_source_info_list()函数。经过实验,通过PulseAudio来做音频采集,成功实现了在国产化平台的麒麟系统上采集麦克风和系统声音的功能,避免了之前使用ALSA代码在多声卡环境下所出现的各种麻烦。
另外,需要注意一点的是,这样通过PulseAudio采集出来的数据大小,可能并不是编码所需要的,还需要做一下数据缓冲。

合作请加WX:hbstream或叩叩:229375788。(转载请注明作者和出处)
Linux音频采集和在国产化平台中遇到的坑(二)的更多相关文章
- 驳Linux不娱乐 堪比Win平台中十款播放器
		播放器在我们日常生活中扮演着非常重要的角色,在Windows操作系统中,播放器被应用的非常广泛,不但我们可以听音乐,甚至还可以听广播,制作铃声,下载音乐等等.而在Linux发行版中,缺少娱乐性一直性W ... 
- iOS音频采集过程中的音效实现
		1.背景 在移动直播中, 声音是主播和观众互动的重要途径之一, 为了丰富直播的内容,大家都会想要在声音上做一些文章, 在采集录音的基础上玩一些花样. 比如演唱类的直播间中, 主播伴随着背景音乐演唱. ... 
- (四)WebRTC手记之本地音频采集
		转自:http://www.cnblogs.com/fangkm/p/4374668.html 上一篇博文介绍了本地视频采集,这一篇就介绍下音频采集流程,也是先介绍WebRTC原生的音频采集,再介绍C ... 
- WebRTC手记之本地音频采集
		转载请注明出处:http://www.cnblogs.com/fangkm/p/4374668.html 上一篇博文介绍了本地视频采集,这一篇就介绍下音频采集流程,也是先介绍WebRTC原生的音频采集 ... 
- DirectShow音频采集声音不连续问题分析与解决办法经验总结
		最近广州大雨不断,并且多数无前兆,突然就来场大雨,给同学们降降温,说来本也是好事,但有时候下的真不是时候,最近这段时间都是即将下班了,大雨就来了,昨晚快下班前又出现了大雨,北方人总爱忘带雨伞,这不就被 ... 
- Linux音频编程指南
		Linux音频编程指南 虽然目前Linux的优势主要体现在网络服务方面,但事实上同样也有着非常丰富的媒体功能,本文就是以多媒体应用中最基本的声音为对象,介绍如何在Linux平台下开发实际的音频应用程序 ... 
- Linux音频驱动学习之:(1)ASOC分析
		一.音频架构概述 (1)ALSA是Advanced Linux Sound Architecture 的缩写,目前已经成为了linux的主流音频体系结构,想了解更多的关于ALSA的这一开源项目的信息和 ... 
- Linux音频编程指南(转)
		转自: http://www.ibm.com/developerworks/cn/linux/l-audio/ Linux音频编程指南 虽然目前Linux的优势主要体现在网络服务方面,但事实上同样也有 ... 
- Linux音频编程
		1. 背景 在<Jasper语音助理介绍>中, 介绍了Linux音频系统, 本文主要介绍了Linux下音频编程相关内容. 音频编程主要包括播放(Playback)和录制(Record), ... 
- EasyPlayerPro Windows播放器进行本地对讲喊话音频采集功能实现
		需求 在安防行业应用中,除了在本地看到摄像机的视频和进行音频监听外,还有一个重要的功能,那就是对讲. EasyPlayerPro-win为了减轻二次开发者的工作量,将本地音频采集也进行了集成: 功能特 ... 
随机推荐
- 部署redis-cluster
			1.环境准备 ☆ 每个Redis 节点采用相同的相同的Redis版本.相同的密码.硬件配置 ☆ 所有Redis服务器必须没有任何数据 #所有主从节点执行: [root@ubuntu2004 ~]#ba ... 
- 安装zabbix-agent2之ansible-playbook
			zabbix被监控端安装zabbix-agent2之ansible-playbook --- - name: install agent hosts: all vars: server_host: & ... 
- Multi-Channel PCIe QDMA Subsystem
			可交付资料: 详细的用户手册 Design File:Post-synthesis EDIF netlist or RTL Source Timing and layout constraints,T ... 
- letcode刷题记录-day03-罗马转整数
			题目 罗马转整数 题目描述 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M. 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 ... 
- 开箱即用 yyg-cli(脚手架工具):快速创建 vue3 组件库和vue3 全家桶项目
			1 yyg-cli 是什么 yyg-cli 是优雅哥开发的快速创建 vue3 项目的脚手架.在 npm 上发布了两个月,11月1日进行了大升级,发布 1.1.0 版本:支持创建 vue3 全家桶项目和 ... 
- csp2022第一轮游记
			DAY -7? 学校没买桶装水!我一时半会不去打水,真的渴.果不其然开始咳嗽了.DAY -1 隔壁班同学主动申请停课了,我也跟来复习,这天主要的成果是把选择题错误控制到2-3题,顺便整理了一点笔记. ... 
- 2022春每日一题:Day 28
			题目:最大上升子序列和 就是最长上升子序列的改版,贡献由1改为a[i]其他全部不变 代码: #include <cstdio> #include <cstdlib> #incl ... 
- centos8 telnet安装
			1. 装包 yum -y install telnet telnet-server 2. 启服务 systemctl enable telnet.socket --now 3. 防火墙开放端口 fir ... 
- npm卸载"Tracker idealTree already exists"
			问题 使用npm卸载babel插件的时候执行命令npm uninstall babel...出现如下报错 npm ERR! Tracker "idealTree" already ... 
- 【Serverless】Unity快速集成认证服务实现邮件登录
			概述: 认证服务可以为您的应用快速构建安全可靠的用户认证系统,您只需在应用中访问认证服务的相关能力,而不需要关心云侧的设施和实现. 本次将带来如何使用Unity编辑器快速集成认证服务SDK并实现邮箱 ... 
