OpenHarmony 3.2 Beta Audio——音频渲染
一、简介
Audio是多媒体子系统中的一个重要模块,其涉及的内容比较多,有音频的渲染、音频的采集、音频的策略管理等。本文主要针对音频渲染功能进行详细地分析,并通过源码中提供的例子,对音频渲染进行流程的梳理。
二、目录
foundation/multimedia/audio_framework
audio_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.cpp
bool 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<AudioSamplingRate>(wavHeader.SamplesPerSec);
rendererOptions.streamInfo.format = GetSampleFormat(wavHeader.bitsPerSample);
rendererOptions.streamInfo.channels = static_cast<AudioChannel>(wavHeader.NumOfChan);
rendererOptions.rendererInfo.contentType = contentType;
rendererOptions.rendererInfo.streamUsage = streamUsage;
rendererOptions.rendererInfo.rendererFlags = 0; //创建AudioRender实例
unique_ptr<AudioRenderer> audioRenderer = AudioRenderer::Create(rendererOptions); shared_ptr<AudioRendererCallback> cb1 = make_shared<AudioRendererCallbackTestImpl>();
//设置音频渲染回调
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. 创建AudioRenderer
std::unique_ptr<AudioRenderer> 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<AudioRendererPrivate>(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<AudioRendererCallback> &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<AudioInterruptCallbackImpl> cbInterrupt =
std::static_pointer_cast<AudioInterruptCallbackImpl>(audioInterruptCallback_);
cbInterrupt->SaveCallback(callback); // Save and Set reference for stream callback. Order is important here.
if (audioStreamCallback_ == nullptr) {
audioStreamCallback_ = std::make_shared<AudioStreamCallbackRenderer>();
if (audioStreamCallback_ == nullptr) {
return ERROR;
}
}
std::shared_ptr<AudioStreamCallbackRenderer> cbStream =
std::static_pointer_cast<AudioStreamCallbackRenderer>(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::SHARE_MODE:
audioInterrupt = sharedInterrupt_;
break;
case InterruptMode::INDEPENDENT_MODE:
audioInterrupt = audioInterrupt_;
break;
default:
break;
}
AUDIO_INFO_LOG("AudioRenderer::Start::interruptMode: %{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<std::thread>(&AudioStream::WriteCbTheadLoop, this);
} else if (captureMode_ == CAPTURE_MODE_CALLBACK) {
isReadyToRead_ = true;
readThread_ = std::make_unique<std::thread>(&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<mutex> 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——音频渲染的更多相关文章
- Android下基于PCM的音频渲染
环境准备 请按照我之前的文章-Android下基于SDL的位图渲染,安装必要的开发环境. 实践篇 这里主要参考Beginning SDL 2.0(6) 音频渲染及wav播放,只不过将源从WAV文件改成 ...
- HMS Core音频编辑服务音源分离与空间音频渲染,助力快速进入3D音频的世界
从单声道.立体声.环绕声发展到三维声,音频回放技术的迭代演进是为了还原真实世界的声音.其中,三维声技术使用信号处理的方法对到达两耳的声音信号进行模拟,将声场还原为三维空间,更接近真实世界.凭借这个技术 ...
- html5 audio音频播放全解析
序 html5开启了一个新时代,因为它让浏览器本身变得不那么被动,audio api就是一个典型的列子,在html5还没确定之前,如果想要在网页上听音乐看视频唯一的办法就是用flash意思是当你没有给 ...
- [小程序开发] 微信小程序audio音频播放组件+api_wx.createAudioContext
引言: audio是微信小程序中的音频组件,可以轻松实现小程序中播放/停止音频等自定义动作. 附上微信小程序audio组件的相关属性说明:https://mp.weixin.qq.com/debug/ ...
- HTML5 学习07——Video(视频)Audio(音频)
<video> 元素:提供了 播放.暂停和音量控件来控制视频. width 和 height 属性:控制视频的尺寸 <video> 与</video> 标签之间插入 ...
- iphone在微信中audio 音频无法自动播放
问题: Html5的audio 音频在电脑端和android端都可以实现自动播放,在iphone上无法实现,下面针对的是微信浏览器里面的解决方法 html代码: <div id="au ...
- H5 <audio> 音频标签自定义样式修改以及添加播放控制事件
H5 <audio> 音频标签自定义样式修改以及添加播放控制事件 Dandelion_drq 关注 2017.08.28 14:48* 字数 331 阅读 2902评论 3喜欢 3 说明: ...
- HTML5: HTML5 Audio(音频)
ylbtech-HTML5: HTML5 Audio(音频) 1.返回顶部 1. HTML5 Audio(音频) HTML5 提供了播放音频文件的标准. 互联网上的音频 直到现在,仍然不存在一项旨在网 ...
- OpenHarmony 3.1 Beta版本关键特性解析——OpenHarmony图形框架
(以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点) 李煜 华为技术有限公司 崔坤华为技术有限公司 众所周知,动画是系统和应用与用户交互的重要环节.动画效果的好坏会直接影响 ...
- 基于对象的实时空间音频渲染丨Dev for Dev 专栏
本文为「Dev for Dev 专栏」系列内容,作者为声网音频算法工程师 李嵩. 随着元宇宙概念的引入,空间音频这项技术慢慢映入大家的眼帘.关于空间音频的基础原理,我们做过一期科普视频 -- 「空间音 ...
随机推荐
- 项目实战:Qt+OSG三维点云引擎(支持原点,缩放,单独轴或者组合多轴拽拖旋转,支持导入点云文件)
需求 开发基于osg的三维点云引擎模块. 1.基于x,y,z坐标轴. 2.可设置原点,设置缩放比例. 3.可设置y轴和z轴单位. 4.三轴中,XY为2D图的水平.竖直方向:Z轴,对应高度图 ...
- 1.Go 的基本数据类型
Go 的基本数据类型
- 文心一言 VS 讯飞星火 VS chatgpt (204)-- 算法导论15.3 3题
三.考虑矩阵链乘法问题的一个变形:目标改为最大化矩阵序列括号化方案的标量乘法运算次数,而非最小化.此问题具有最优子结构性质吗?需要写代码的时候,请用go语言. 文心一言,代码正常运行: 首先,我们要明 ...
- 【Azure Key Vault】客户端获取Key Vault机密信息全部失败问题分析
问题描述 在应用中获取存储在Azure Key Vault的机密信息,全部失败. 报错日志内容如下: [reactor-http-epoll-4] [reactor.netty.http.client ...
- 【Azure Developer】Go语言调用Azure SDK如何登录到中国区Azure环境
问题描述 在 "使用 Azure SDK for Go 进行 Azure 身份验证" 文章中的 Go 示例代码进行登录Azure时,默认指向的是Globa Azure.当只修改AA ...
- 【Azure API 管理】APIM的容量指标(Capacity)数据异常高的情况记录
问题描述 APIM从标准版降级到基础版,在没有用户使用的情况,Capacity的指标平均显示在80%以上. 这是什么异常情况呢? 问题分析 APIM的容量指标(Capacity)是 API 管理实例中 ...
- gorm整理
目录 1. 约定 2. 结构体标签 3. 创建记录 4. 更新 5.删除 6. 查询 7.关联 8.链式操作 9.范围 10.多个立即执行方法的注意事项 11.错误处理 12.钩子 13.事务 14. ...
- 用 NetworkX + Gephi + Nebula Graph 分析<权力的游戏>人物关系(下篇)
在上一篇[1]中,我们通过 NetworkX 和 Gephi 展示了<权力的游戏>中的人物关系.在本篇中,我们将展示如何通过 NetworkX 访问图数据库 Nebula Graph. N ...
- 1、dubbo的简介
Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案.简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候 ...
- 关于使用Kotlin开发SpringBoot项目使用@Transactional和@Autowired的报错问题
原文地址: 关于使用Kotlin开发SpringBoot项目使用@Transactional和@Autowired的报错问题 - Stars-One的杂货小窝 问题描述 最近在开发一个订单模块,需要出 ...