点击蓝字 ╳ 关注我们
开源项目 openharmony是每个人的 openharmony
巴延兴
深圳开鸿数字产业发展有限公司
资深os框架开发工程师
一、简介
audio是多媒体子系统中的一个重要模块,其涉及的内容比较多,有音频的渲染、音频的采集、音频的策略管理等。本文主要针对音频渲染功能进行详细地分析,并通过源码中提供的例子,对音频渲染进行流程的梳理。
二、目录
foundation/multimedia/audio_frameworkaudio_framework
├── frameworks
│ ├── js #js 接口
│ │ └── napi
│ │ └── audio_renderer #audio_renderer napi接口
│ │ ├── include
│ │ │ ├── audio_renderer_callback_napi.h
│ │ │ ├── renderer_data_request_callback_napi.h
│ │ │ ├── renderer_period_position_callback_napi.h
│ │ │ └── renderer_position_callback_napi.h
│ │ └── src
│ │ ├── audio_renderer_callback_napi.cpp
│ │ ├── audio_renderer_napi.cpp
│ │ ├── renderer_data_request_callback_napi.cpp
│ │ ├── renderer_period_position_callback_napi.cpp
│ │ └── renderer_position_callback_napi.cpp
│ └── native #native 接口
│ └── audiorenderer
│ ├── build.gn
│ ├── include
│ │ ├── audio_renderer_private.h
│ │ └── audio_renderer_proxy_obj.h
│ ├── src
│ │ ├── audio_renderer.cpp
│ │ └── audio_renderer_proxy_obj.cpp
│ └── test
│ └── example
│ └── audio_renderer_test.cpp
├── interfaces
│ ├── inner_api #native实现的接口
│ │ └── native
│ │ └── audiorenderer #audio渲染本地实现的接口定义
│ │ └── include
│ │ └── audio_renderer.h
│ └── kits #js调用的接口
│ └── js
│ └── audio_renderer #audio渲染napi接口的定义
│ └── include
│ └── audio_renderer_napi.h
└── services #服务端
└── audio_service
├── build.gn
├── client #ipc调用中的proxy端
│ ├── include
│ │ ├── audio_manager_proxy.h
│ │ ├── audio_service_client.h
│ └── src
│ ├── audio_manager_proxy.cpp
│ ├── audio_service_client.cpp
└── server #ipc调用中的server端
├── include
│ └── audio_server.h
└── src
├── audio_manager_stub.cpp
└── audio_server.cpp
三、音频渲染总体流程
四、native接口使用
在openatom openharmony(以下简称“openharmony”)系统中,音频模块提供了功能测试代码,本文选取了其中的音频渲染例子作为切入点来进行介绍,例子采用的是对wav格式的音频文件进行渲染。wav格式的音频文件是wav头文件和音频的原始数据,不需要进行数据解码,所以音频渲染直接对原始数据进行操作,文件路径为:foundation/multimedia/audio_framework/frameworks/native/audiorenderer/test/example/audio_renderer_test.cppbool testplayback(int argc, char *argv[]) const
{
file* wavfile = fopen(path, rb);
//读取wav文件头信息
size_t bytesread = fread(&wavheader, 1, headersize, wavfile);
//设置audiorenderer参数
audiorendereroptions rendereroptions = {};
rendereroptions.streaminfo.encoding = audioencodingtype::encoding_pcm;
rendereroptions.streaminfo.samplingrate = static_cast(wavheader.samplespersec);
rendereroptions.streaminfo.format = getsampleformat(wavheader.bitspersample);
rendereroptions.streaminfo.channels = static_cast(wavheader.numofchan);
rendereroptions.rendererinfo.contenttype = contenttype;
rendereroptions.rendererinfo.streamusage = streamusage;
rendereroptions.rendererinfo.rendererflags = 0;
//创建audiorender实例
unique_ptr audiorenderer = audiorenderer::create(rendereroptions);
shared_ptr cb1 = make_shared();
//设置音频渲染回调
ret = audiorenderer->setrenderercallback(cb1);
//initrender方法主要调用了audiorenderer实例的start方法,启动音频渲染
if (!initrender(audiorenderer)) {
audio_err_log(audiorenderertest: init render failed);
fclose(wavfile);
return false;
}
//startrender方法主要是读取wavfile文件的数据,然后通过调用audiorenderer实例的write方法进行播放
if (!startrender(audiorenderer, wavfile)) {
audio_err_log(audiorenderertest: start render failed);
fclose(wavfile);
return false;
}
//停止渲染
if (!audiorenderer->stop()) {
audio_err_log(audiorenderertest: stop failed);
}
//释放渲染
if (!audiorenderer->release()) {
audio_err_log(audiorenderertest: release failed);
}
//关闭wavfile
fclose(wavfile);
return true;
}首先读取wav文件,通过读取到wav文件的头信息对audiorendereroptions相关的参数进行设置,包括编码格式、采样率、采样格式、通道数等。根据audiorendereroptions设置的参数来创建audiorenderer实例(实际上是audiorendererprivate),后续的音频渲染主要是通过audiorenderer实例进行。创建完成后,调用audiorenderer的start方法,启动音频渲染。启动后,通过audiorenderer实例的write方法,将数据写入,音频数据会被播放。
五、调用流程
1.创建audiorendererstd::unique_ptr audiorenderer::create(const std::string cachepath,
const audiorendereroptions &rendereroptions, const appinfo &appinfo)
{
contenttype contenttype = rendereroptions.rendererinfo.contenttype;
streamusage streamusage = rendereroptions.rendererinfo.streamusage;
audiostreamtype audiostreamtype = audiostream::getstreamtype(contenttype, streamusage);
auto audiorenderer = std::make_unique(audiostreamtype, appinfo);
if (!cachepath.empty()) {
audio_debug_log(set application cache path);
audiorenderer->setapplicationcachepath(cachepath);
}
audiorenderer->rendererinfo_.contenttype = contenttype;
audiorenderer->rendererinfo_.streamusage = streamusage;
audiorenderer->rendererinfo_.rendererflags = rendereroptions.rendererinfo.rendererflags;
audiorendererparams params;
params.sampleformat = rendereroptions.streaminfo.format;
params.samplerate = rendereroptions.streaminfo.samplingrate;
params.channelcount = rendereroptions.streaminfo.channels;
params.encodingtype = rendereroptions.streaminfo.encoding;
if (audiorenderer->setparams(params) != success) {
audio_err_log(setparams failed in renderer);
audiorenderer = nullptr;
return nullptr;
}
return audiorenderer;
}首先通过audiostream的getstreamtype方法获取音频流的类型,根据音频流类型创建audiorendererprivate对象,audiorendererprivate是audiorenderer的子类。紧接着对audiorenderer进行参数设置,其中包括采样格式、采样率、通道数、编码格式。设置完成后返回创建的audiorendererprivate实例。
2.设置回调int32_t audiorendererprivate::setrenderercallback(const std::shared_ptr &callback)
{
rendererstate state = getstatus();
if (state == renderer_new || state == renderer_released) {
return err_illegal_state;
}
if (callback == nullptr) {
return err_invalid_param;
}
// save reference for interrupt callback
if (audiointerruptcallback_ == nullptr) {
return error;
}
std::shared_ptr cbinterrupt =
std::static_pointer_cast(audiointerruptcallback_);
cbinterrupt->savecallback(callback);
// save and set reference for stream callback. order is important here.
if (audiostreamcallback_ == nullptr) {
audiostreamcallback_ = std::make_shared();
if (audiostreamcallback_ == nullptr) {
return error;
}
}
std::shared_ptr cbstream =
std::static_pointer_cast(audiostreamcallback_);
cbstream->savecallback(callback);
(void)audiostream_->setstreamcallback(audiostreamcallback_);
return success;
}参数传入的回调主要涉及到两个方面:一方面是audiointerruptcallbackimpl中设置了我们传入的渲染回调,另一方面是audiostreamcallbackrenderer中也设置了渲染回调。
3.启动渲染bool audiorendererprivate::start(statechangecmdtype cmdtype) const
{
audio_info_log(audiorenderer::start);
rendererstate state = getstatus();
audiointerrupt audiointerrupt;
switch (mode_) {
case interruptmode:
audiointerrupt = sharedinterrupt_;
break;
case interruptmode:
audiointerrupt = audiointerrupt_;
break;
default:
break;
}
audio_info_log(audiorenderer: %{public}d, streamtype: %{public}d, sessionid: %{public}d,
mode_, audiointerrupt.streamtype, audiointerrupt.sessionid);
if (audiointerrupt.streamtype == stream_default || audiointerrupt.sessionid == invalid_session_id) {
return false;
}
int32_t ret = audiopolicymanager::getinstance().activateaudiointerrupt(audiointerrupt);
if (ret != 0) {
audio_err_log(audiorendererprivate::activateaudiointerrupt failed);
return false;
}
return audiostream_->startaudiostream(cmdtype);
}audiopolicymanager::getinstance().activateaudiointerrupt这个操作主要是根据audiointerrupt来进行音频中断的激活,这里涉及了音频策略相关的内容,后续会专门出关于音频策略的文章进行分析。这个方法的核心是通过调用audiostream的startaudiostream方法来启动音频流。
bool audiostream::startaudiostream(statechangecmdtype cmdtype)
{
int32_t ret = startstream(cmdtype);
resettime_ = true;
int32_t retcode = clock_gettime(clock_monotonic, &basetimestamp_);
if (rendermode_ == render_mode_callback) {
isreadytowrite_ = true;
writethread_ = std::make_unique(&audiostream::writecbtheadloop, this);
} else if (capturemode_ == capture_mode_callback) {
isreadytoread_ = true;
readthread_ = std::make_unique(&audiostream::readcbthreadloop, this);
}
isfirstread_ = true;
isfirstwrite_ = true;
state_ = running;
audio_info_log(startaudiostream success);
if (audiostreamtracker_) {
audio_debug_log(audiostream:calling update tracker for running);
audiostreamtracker_->updatetracker(sessionid_, state_, rendererinfo_, capturerinfo_);
}
return true;
}audiostream的startaudiostream主要的工作是调用startstream方法,startstream方法是audioserviceclient类中的方法。audioserviceclient类是audiostream的父类。接下来看一下audioserviceclient的startstream方法。int32_t audioserviceclient::startstream(statechangecmdtype cmdtype)
{
int error;
lock_guard lockdata(datamutex);
pa_operation *operation = nullptr;
pa_threaded_mainloop_lock(mainloop);
pa_stream_state_t state = pa_stream_get_state(pastream);
streamcmdstatus = 0;
statechangecmdtype_ = cmdtype;
operation = pa_stream_cork(pastream, 0, pastreamstartsuccesscb, (void *)this);
while (pa_operation_get_state(operation) == pa_operation_running) {
pa_threaded_mainloop_wait(mainloop);
}
pa_operation_unref(operation);
pa_threaded_mainloop_unlock(mainloop);
if (!streamcmdstatus) {
audio_err_log(stream start failed);
resetpaaudioclient();
return audio_client_start_stream_err;
} else {
audio_info_log(stream started successfully);
return audio_client_success;
}
}startstream方法中主要是调用了pulseaudio库的pa_stream_cork方法进行流启动,后续就调用到了pulseaudio库中了。pulseaudio库我们暂且不分析。
4.写入数据int32_t audiorendererprivate::write(uint8_t *buffer, size_t buffersize)
{
return audiostream_->write(buffer, buffersize);
}通过调用audiostream的write方式实现功能,接下来看一下audiostream的write方法。size_t audiostream::write(uint8_t *buffer, size_t buffer_size)
{
int32_t writeerror;
streambuffer stream;
stream.buffer = buffer;
stream.bufferlen = buffer_size;
iswriteinprogress_ = true;
if (isfirstwrite_) {
if (renderprebuf(stream.bufferlen)) {
return err_write_failed;
}
isfirstwrite_ = false;
}
size_t byteswritten = writestream(stream, writeerror);
iswriteinprogress_ = false;
if (writeerror != 0) {
audio_err_log(writestream fail,writeerror:%{public}d, writeerror);
return err_write_failed;
}
return byteswritten;
}write方法中分成两个阶段,首次写数据,先调用renderprebuf方法,将prebuf_的数据写入后再调用writestream进行音频数据的写入。size_t audioserviceclient::writestream(const streambuffer &stream, int32_t &perror)
{
size_t cachedlen = writetoaudiocache(stream);
if (!acache.isfull) {
perror = error;
return cachedlen;
}
pa_threaded_mainloop_lock(mainloop);
const uint8_t *buffer = acache.buffer.get();
size_t length = acache.totalcachesize;
error = pawritestream(buffer, length);
acache.readindex += acache.totalcachesize;
acache.isfull = false;
if (!error && (length >= 0) && !acache.isfull) {
uint8_t *cachebuffer = acache.buffer.get();
uint32_t offset = acache.readindex;
uint32_t size = (acache.writeindex - acache.readindex);
if (size > 0) {
if (memcpy_s(cachebuffer, acache.totalcachesize, cachebuffer + offset, size)) {
audio_err_log(update cache failed);
pa_threaded_mainloop_unlock(mainloop);
perror = audio_client_write_stream_err;
return cachedlen;
}
audio_info_log(rearranging the audio cache);
}
acache.readindex = 0;
acache.writeindex = 0;
if (cachedlen < stream.bufferlen) {
streambuffer str;
str.buffer = stream.buffer + cachedlen;
str.bufferlen = stream.bufferlen - cachedlen;
audio_debug_log(writing pending data to audio cache: %{public}d, str.bufferlen);
cachedlen += writetoaudiocache(str);
}
}
pa_threaded_mainloop_unlock(mainloop);
perror = error;
return cachedlen;
}writestream方法不是直接调用pulseaudio库的写入方法,而是通过writetoaudiocache方法将数据写入缓存中,如果缓存没有写满则直接返回,不会进入下面的流程,只有当缓存写满后,才会调用下面的pawritestream方法。该方法涉及对pulseaudio库写入操作的调用,所以缓存的目的是避免对pulseaudio库频繁地做io操作,提高了效率。
六、总结
本文主要对openharmony 3.2 beta多媒体子系统的音频渲染模块进行介绍,首先梳理了audio render的整体流程,然后对几个核心的方法进行代码的分析。整体的流程主要通过pulseaudio库启动流,然后通过pulseaudio库的pa_stream_write方法进行数据的写入,最后播放出音频数据。
音频渲染主要分为以下几个层次:(1)audiorenderer的创建,实际创建的是它的子类audiorendererprivate实例。(2)通过audiorendererprivate设置渲染的回调。(3)启动渲染,这一部分代码最终会调用到pulseaudio库中,相当于启动了pulseaudio的流。(4)通过pulseaudio库的pa_stream_write方法将数据写入设备,进行播放。
对openharmony 3.2 beta多媒体系列开发感兴趣的读者,也可以阅读我之前写过几篇文章:《openharmony 3.2 beta多媒体系列——视频录制》《openharmony 3.2 beta源码分析之medialibrary》《openharmony 3.2 beta多媒体系列——音视频播放框架》《openharmony 3.2 beta多媒体系列——音视频播放gstreamer》。
原文标题:openharmony 3.2 beta audio——音频渲染
文章出处:【微信公众号:openatom openharmony】欢迎添加关注!文章转载请注明出处。
慧荣科技(Silicon Motion) 图形显示 SoC 实现 4K 高清显示及低功耗
方正科技:现有客户订单已超出珠海高密承接能力
反激式开关电源变压器设计原理
如何解决MR16 LED照明的闪烁问题
AGV无人搬运车对物流自动化的重要性的分析
OpenHarmony 3.2 Beta Audio——音频渲染
英特尔公司在拉斯维加斯举行的消费电子展上大放异彩
羽博无烟电烤炉怎么样
教你买扫地机器人看穿营销语言不被忽悠
华为P10Plus/iPhone7Plus/HTC U Ultra/魅族Pro6Plus对比哪个更值得买? 详细介绍
直击谣言:清华女生破解北斗卫星的真相
大多数电脑不加入触摸屏的原因是什么
Caterpillar卡特彼勒蓄电池(中国)有限公司-销售部
云基础设施及云数据服务商城地香江发布2021年报
用于DK-LM3S9B96开发套件的新型Stellaris
华为是5G革命性技术的先锋
特斯拉停止与比亚迪合作?比亚迪回应
简单的曲线跟踪器电路解析
特斯拉二季度新能源车生产交付破记录 股价飙升7%
密码学在区块链中有着怎样的作用