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为了减轻二次开发者的工作量,将本地音频采集也进行了集成: 功能特 ...
随机推荐
- clip-path属性深入理解与使用
clip-path CSS 属性可以创建一个只有元素的部分区域可以显示的剪切区域.区域内的部分显示,区域外的隐藏. clip-path的属性值可以是以下几种: 1.inset: 将元素剪裁为一个矩形, ...
- Helm干货!速度围观!
最近个人学习Helm,感觉Helm的功能很强大!分享一些干货给大家吧,希望有所帮助! 基本概念 Chart 一个Helm包,包含在K8S集群内,运行一个应用,工具或者服务所需要的所有的资源定义,类似于 ...
- 如何正确遵守 Python 代码规范
前言 无规矩不成方圆,代码亦是如此,本篇文章将会介绍一些自己做项目时遵守的较为常用的 Python 代码规范. 命名 大小写 模块名写法: module_name 包名写法: package_name ...
- Aspose.Words利用Word模板导出Word文档
今天工作中遇到了导出Word文档的问题,但是在搜索Aspose.Words 导出Word文档时发现网上的方法都是有头没尾的,有的只有一小段实例,让人看着摸不着头脑.借着https://www.cnbl ...
- Go语言核心36讲52
你好,我是郝林. 专栏到这里,就要结束了. 差不多在半年以前(2018年的第二个季度),极客时间的总编辑郭蕾找到我,说想让我写一个关于Go语言的技术专栏. 我那时候还在轻松筹担任大数据负责人,管理着四 ...
- windows server2016/2019在vmware上安装失败
问题:无法进入装机界面 现象一:打开虚拟机后进入虚拟bios界面,无法引导iso镜像 解决方法1:从虚拟机设置里,修改引导选项,如果需要修改启动盘则设置强制执行BIOS设置 下图为vsphere的截图 ...
- tcp网络交互的理解 以及代码实现
服务端 import socketserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind(("127.0. ...
- python安装dlib库报错
问题描述 我是debain 系的linux系统没遇到这个问题,在centos系统遇到的 Collecting dlib Downloading http://mirrors.cloud.aliyunc ...
- 初始化一个ArrayList的多种方式
序言 本片文章用于归纳java中初始化一个ArrayList的几种方式 add方法添加 最基础的方式就是我们在new一个ArrayList之后使用add方法添加元素 /** * 第一种方式,通过add ...
- PostgreSQL常用操作合辑:时间日期、系统函数、正则表达式、库表导入导出、元数据查询、自定义函数、常用案例
〇.参考地址 1.pg官方文档 http://www.postgres.cn/docs/9.6/index.html 2.腾讯云仓pg文档 https://cloud.tencent.com/docu ...