0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

OpenHarmony 3.2 Beta Audio——音频渲染

OpenAtom OpenHarmony ? 来源:未知 ? 2023-03-11 16:40 ? 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

点击蓝字 ╳ 关注我们


开源项目 OpenHarmony
是每个人的 OpenHarmony

巴延兴

深圳开鸿数字产业发展有限公司

资深OS框架开发工程师

一、简介

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(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.创建AudioRenderer
std::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<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 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】欢迎添加关注!文章转载请注明出处。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 鸿蒙
    +关注

    关注

    60

    文章

    2643

    浏览量

    44247
  • OpenHarmony
    +关注

    关注

    30

    文章

    3858

    浏览量

    18808

原文标题:OpenHarmony 3.2 Beta Audio——音频渲染

文章出处:【微信号:gh_e4f28cfa3159,微信公众号:OpenAtom OpenHarmony】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    XR空间音频革命:苹果、三星推出新技术,ASAF成Vision Pro最佳搭档

    Audio)格式:Apple Spatial Audio Format(ASAF,苹果空间音频格式),可以用来打造真正沉浸式的音频体验。 ? ASAF 通过确保使用声学提示来
    的头像 发表于 07-20 00:11 ?2011次阅读

    LE-Audio是什么?

    近年来,随着蓝牙技术的快速发展,无线通信领域的应用变得越来越广泛。然而,在对音频质量和功耗不断追求的同时,蓝牙技术也需要不断创新和改进。在这方面,LE-Audio(低功耗音频)作为一项新兴技术
    发表于 06-28 21:32

    开源鸿蒙6.0Beta1版本发布!触觉智能将率先适配RK3566/RK3568/RK3576等芯片平台芯片

    开放原子开源鸿蒙(OpenAtomOpenHarmony,简称“开源鸿蒙”或“OpenHarmony”)6.0Beta1版本正式发布。相比5.1.0Release版本进一步增强ArkUI组件能力
    的头像 发表于 06-20 14:05 ?528次阅读
    开源鸿蒙6.0<b class='flag-5'>Beta</b>1版本发布!触觉智能将率先适配RK3566/RK3568/RK3576等芯片平台芯片

    蓝牙LE Audio技术简介和优势分析

    蓝牙LE Audio,也称为低功耗音频(Bluetooth Low Energy Audio),是蓝牙技术家族中的最新成员,专门为音频传输而设计。它继承了蓝牙低功耗(Bluetooth
    的头像 发表于 05-21 16:08 ?932次阅读

    CPU渲染、GPU渲染、XPU渲染详细对比:哪个渲染最快,哪个效果最好?

    动画渲染动画3D渲染技术需要应对复杂的计算任务和精细的图像处理,作为渲染技术人员,选择合适的渲染模式,会直接影响制作效率和成品质量。在主流的渲染
    的头像 发表于 04-15 09:28 ?569次阅读
    CPU<b class='flag-5'>渲染</b>、GPU<b class='flag-5'>渲染</b>、XPU<b class='flag-5'>渲染</b>详细对比:哪个<b class='flag-5'>渲染</b>最快,哪个效果最好?

    STM32如何移植Audio框架?

    最近在学习音频解码,想用一下Audio框架。 1、这个该如何移植到自己创建的BSP并对接到device框架中?看了官方移植文档没有对没有对该部分的描述。 2、我只想实现一个简单的播放功能,只用一个DAC芯片(比如CS4344)是否就能达到我的需求?
    发表于 04-01 08:08

    PCB设计丨AUDIO音频接口

    音频接口(audio interface)是连接麦克风、其他声源和计算机的设备,它可以在模拟信号和数字信号之间起到桥梁连接的作用。主要作用是将模拟信号转换为数字信号,并将其传输到计算机中进行处理
    发表于 03-19 14:31

    PCB设计丨AUDIO音频接口

    音频接口(audio interface)是连接麦克风、其他声源和计算机的设备,它可以在模拟信号和数字信号之间起到桥梁连接的作用。主要作用是将模拟信号转换为数字信号,并将其传输到计算机中进行处理
    的头像 发表于 03-19 14:30 ?3223次阅读
    PCB设计丨<b class='flag-5'>AUDIO</b><b class='flag-5'>音频</b>接口

    LE Audio Combo模组智能座舱的应用

    ,凭借其先进的LE Audio技术和经典蓝牙支持,为智能座舱提供了卓越的解决方案,满足了用户在音质、连接稳定性上的高标准需求。 LE AudioCombo模组:引领智能座舱音频体验的未来 蓝牙技术的每次迭代都带来了巨大的性能提升,而蓝牙5.2版本中引入的LE
    的头像 发表于 03-07 14:02 ?489次阅读

    【北京迅为】itop-3568 开发板openharmony鸿蒙烧写及测试-第2章OpenHarmony v3.2-Beta4版本测试

    【北京迅为】itop-3568 开发板openharmony鸿蒙烧写及测试-第2章OpenHarmony v3.2-Beta4版本测试
    的头像 发表于 03-05 10:53 ?492次阅读
    【北京迅为】itop-3568 开发板<b class='flag-5'>openharmony</b>鸿蒙烧写及测试-第2章<b class='flag-5'>OpenHarmony</b> v<b class='flag-5'>3.2-Beta</b>4版本测试

    大联大品佳集团推出基于达发科技(Airoha)产品的LE Audio耳机方案

    的LE Audio耳机方案的展示板图 LE Audio(低功耗音频)技术标准的诞生为业内带来低复杂性通信编解码器(LC3)、多重串流音频、助听器功能扩展和广播
    的头像 发表于 02-22 11:06 ?741次阅读
    大联大品佳集团推出基于达发科技(Airoha)产品的LE <b class='flag-5'>Audio</b>耳机方案

    e络盟大幅扩充PUI Audio产品系列以强化音频产品组合

    安富利旗下全球电子元器件产品与解决方案分销商e络盟大幅扩充了 PUI Audio产品种类。作为音频、触觉反馈及传感器解决方案领域的全球创新者和供应商,PUI Audio产品的加入进一步丰富e络盟的产品组合。新扩展的产品线使客户能
    的头像 发表于 02-18 16:29 ?571次阅读
    e络盟大幅扩充PUI <b class='flag-5'>Audio</b>产品系列以强化<b class='flag-5'>音频</b>产品组合

    请问cc3200 audio boosterpack音频采集是不是底噪很大?

    基于TLV320AIC3254的音频开发办,我烧入wifi_audio_app例程(例程中关掉板载咪头输入,并将音量调到最大),另两入输入接口没有接音频信号,但是板子一直吱吱吱的响,是板子本身底噪就这么大吗?
    发表于 10-25 06:28

    TPS6595 Audio Codec输出音频偶发混入7Khz杂波是怎么回事?

    主芯片是DM3730, 音频使用的是TPS65950的Audio 外设。 DM3730使用MCBSP输出8Khz音频数据,通过I2C设置 TPS65950相关寄存器。 采用Audio
    发表于 10-15 07:08

    【龙芯2K0300蜂鸟板试用】OpenHarmony代码

    fetch origin OpenHarmony-3.2-Release:OpenHarmony-3.2-Release git switch OpenHarmony-3.2
    发表于 09-18 11:42