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. 将C#的bitmap格式转换为Halcon的图像格式

    /// <summary> /// Bitmap转HObject灰度图 /// </summary> /// <param name="bmp"> ...

  2. 方法的重载(Overload)+ println重载

    方法的重载(Overload) package cn.day01; /*方法的重载(Overload):多个方法的名称一样,但是参数列表不一样. * 好处:只需要记住唯一一个方法名称,就可以实现类似多 ...

  3. 记一次spark数据倾斜实践

    参考文章: 大数据项目--倾斜数据的分区优化 数据倾斜概念 什么是数据倾斜   大数据下大部分框架的处理原理都是参考mapreduce的思想:分而治之和移动计算,即提前将计算程序生成好然后发送到不同的 ...

  4. Kubernetes 1.25.4数据平面自带nginx负载均衡实现高可用

    1.环境准备 要点: 1.使用一个FQDN统一作为API Server的接入点: 2.加入集群之前,每个节点都将该FQDN解析至第一个Master: 3.加入集群之后,每个Master节点将该FQDN ...

  5. PHY驱动调试之 --- PHY控制器驱动(二)

    1. 前言 内核版本:linux 4.9.225,以freescale为例. 2. 概述 PHY芯片为OSI的最底层-物理层(Physical Layer),通过MII/GMII/RMII/SGMII ...

  6. Crypto - Caesar I

    原题链接:http://www.wechall.net/challenge/training/crypto/caesar/index.php 告诉我们这是个古凯撒密码,让我们解...我们百度下古凯撒密 ...

  7. 关于mysql数据库user表没有password字段

    解决 这个是因为mysql的版本问题,是mysql 5.7版本出现的,具体是mysql 5.7.x 开始变化的我不知道 新的字段变更为authentication_string 修改密码的方式还是和原 ...

  8. 《浅谈亚 log 数据结构在 OI 中的应用》阅读随笔

    这篇又长长长了! \(8435\to 8375\to 9729\) 早就馋这篇了!终于学了( 压位 Trie 确实很好写啊 但是总感觉使用范围不是很广的样子 似乎是见的题少 原文里都在用 \(\log ...

  9. Springboot优雅进行字段检验

    Springboot优雅进行字段检验 1.Controller VS Service 推荐与业务无关的放在controller层中进行校验,而与业务相关的放在service层中校验. 2.常用校验工具 ...

  10. 粘包、struct模块、进程并行与并发

    目录 粘包现象 struct模块 粘包代码实战 udp协议(了解) 并发编程理论 多道技术 进程理论 进程并行与并发 进程的三状态 粘包现象 1.服务端连续执行三次recv 2.客户端连续执行三次se ...