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);

下面写个简单的例子演示下如何调用

  1. 创建事件循环,连接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;
}
  1. 读取音频数据
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音频采集和在国产化平台中遇到的坑(二)的更多相关文章

  1. 驳Linux不娱乐 堪比Win平台中十款播放器

    播放器在我们日常生活中扮演着非常重要的角色,在Windows操作系统中,播放器被应用的非常广泛,不但我们可以听音乐,甚至还可以听广播,制作铃声,下载音乐等等.而在Linux发行版中,缺少娱乐性一直性W ...

  2. iOS音频采集过程中的音效实现

    1.背景 在移动直播中, 声音是主播和观众互动的重要途径之一, 为了丰富直播的内容,大家都会想要在声音上做一些文章, 在采集录音的基础上玩一些花样. 比如演唱类的直播间中, 主播伴随着背景音乐演唱. ...

  3. (四)WebRTC手记之本地音频采集

    转自:http://www.cnblogs.com/fangkm/p/4374668.html 上一篇博文介绍了本地视频采集,这一篇就介绍下音频采集流程,也是先介绍WebRTC原生的音频采集,再介绍C ...

  4. WebRTC手记之本地音频采集

    转载请注明出处:http://www.cnblogs.com/fangkm/p/4374668.html 上一篇博文介绍了本地视频采集,这一篇就介绍下音频采集流程,也是先介绍WebRTC原生的音频采集 ...

  5. DirectShow音频采集声音不连续问题分析与解决办法经验总结

    最近广州大雨不断,并且多数无前兆,突然就来场大雨,给同学们降降温,说来本也是好事,但有时候下的真不是时候,最近这段时间都是即将下班了,大雨就来了,昨晚快下班前又出现了大雨,北方人总爱忘带雨伞,这不就被 ...

  6. Linux音频编程指南

    Linux音频编程指南 虽然目前Linux的优势主要体现在网络服务方面,但事实上同样也有着非常丰富的媒体功能,本文就是以多媒体应用中最基本的声音为对象,介绍如何在Linux平台下开发实际的音频应用程序 ...

  7. Linux音频驱动学习之:(1)ASOC分析

    一.音频架构概述 (1)ALSA是Advanced Linux Sound Architecture 的缩写,目前已经成为了linux的主流音频体系结构,想了解更多的关于ALSA的这一开源项目的信息和 ...

  8. Linux音频编程指南(转)

    转自: http://www.ibm.com/developerworks/cn/linux/l-audio/ Linux音频编程指南 虽然目前Linux的优势主要体现在网络服务方面,但事实上同样也有 ...

  9. Linux音频编程

    1. 背景 在<Jasper语音助理介绍>中, 介绍了Linux音频系统, 本文主要介绍了Linux下音频编程相关内容. 音频编程主要包括播放(Playback)和录制(Record), ...

  10. EasyPlayerPro Windows播放器进行本地对讲喊话音频采集功能实现

    需求 在安防行业应用中,除了在本地看到摄像机的视频和进行音频监听外,还有一个重要的功能,那就是对讲. EasyPlayerPro-win为了减轻二次开发者的工作量,将本地音频采集也进行了集成: 功能特 ...

随机推荐

  1. 【lwip】07-链路层收发以太网数据帧源码分析

    目录 前言 7.1 链路层概述 7.2 MAC地址的基本概念 7.3 以太网帧结构 7.4 以太网帧结构 7.5 以太网帧报文数据结构 7.6 发送以太网数据帧 7.7 接收以太网数据帧 7.8 虚拟 ...

  2. 用 vue3 中的 reduce(累加器) 随机生成100个字母,放入数组中,统计每个字母出现的次数

    一.首先不用 reduce() 来实现  代码如下: <template lang=""> <div> <h1>统计每个字母出现的次数,不使用r ...

  3. [C# 中的序列化与反序列化](.NET 源码学习)

    [C# 中的序列化与反序列化](.NET 源码学习) 关键词:序列化(概念与分析)    三种序列化(底层原理 源码)    Stream(底层原理 源码)    反射(底层原理 源码) 假如有一天我 ...

  4. 解决can't compare offset-naive and offset-aware datetimes报错

    问题描述 在比较 <class 'datetime.datetime'> 类型时,抛出异常 原因 俩个做比较的,一个具有时区,一个不具有时区 解决 如果可以确认俩个时间都是本地时间可以将时 ...

  5. 安装mySql 出现 one more product requirements have not been satisified

    安装mySql 出现 one more product requirements have not been satisified 原因是缺少一些依赖环境. 在弹出的对话框中点击 否. 然后点击执行, ...

  6. 【实时数仓】Day00:数据流程、课程内容、框架结构、知识点总结

    一.数据流程 1.离线数仓 2.实时数仓 二.课程内容 1.数据采集层(ODS) 2.DWD层与DIM层数据准备 3.DWM层业务实现 4.DWS层业务实现 5.ClickHouse 6.数据可视化接 ...

  7. Python 大数据量文本文件高效解析方案代码实现

    大数据量文本文件高效解析方案代码实现 测试环境 Python 3.6.2 Win 10 内存 8G,CPU I5 1.6 GHz 背景描述 这个作品来源于一个日志解析工具的开发,这个开发过程中遇到的一 ...

  8. AcWing342. 道路与航线

    原题链接 解题思路 这题用\(SPFA\)会被卡,所以我们不能用\(SPFA\) 但是观察数据我们可以发现对于道路,\(0≤C_i≤10^{5}\) 所以对于每个连通块(内部不存在航线),我们可以用\ ...

  9. CTFshow——funnyrsa1的wp理解

    题目如下: 题目分析: 拿到题,发现给的e不常规,p1和p2相等,有两个不同n,两个不同c和两个不同e.给定两个密文的情况下,通常需要找到两者之间存在的关系,"合并"密文求解才能得 ...

  10. mybatis 之定义拦截器 控制台SQL的打印

    类型 先说明Mybatis中可以被拦截的类型具体有以下四种: 1.Executor:拦截执行器的方法.2.ParameterHandler:拦截参数的处理.3.ResultHandler:拦截结果集的 ...