一、简介

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");}//关闭wavFilefclose(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 callbackif (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——音频渲染相关推荐

  1. HMS Core音频编辑服务音源分离与空间音频渲染,助力快速进入3D音频的世界

    从单声道.立体声.环绕声发展到三维声,音频回放技术的迭代演进是为了还原真实世界的声音.其中,三维声技术使用信号处理的方法对到达两耳的声音信号进行模拟,将声场还原为三维空间,更接近真实世界.凭借这个技术 ...

  2. audio音频播放标签样式优化自定义

    自定义audio音频播放样式实现进度监听,这是效果图 v-audio.vue <template><!-- audio音频组件 src:音频路径 longTime:音频时长, sou ...

  3. 优酷播放体验优化实战(四)--“三高”音频渲染引擎设计

    一. 背景 随着高清在用户观影过程中的深度普及,人们已经不仅仅满足于视的享受,更需要听的保证.如何稳定保障音质,甚至增加更多的音效玩法需要一套强大的系统将数据传输.音频实时处理技术.音频输出有效地整合 ...

  4. Snowflake 获 2021 年度数据库称号;Linus 成为 Linux 项目中最强 committer;OpenHarmony 3.1 Beta 版发布 | 开源日报

    整理 | 宋彤彤 责编 | 屠敏 开源吞噬世界的趋势下,借助开源软件,基于开源协议,任何人都可以得到项目的源代码,加以学习.修改,甚至是重新分发.关注「开源日报」,一文速览国内外今日的开源大事件吧! ...

  5. 音视频学习 AudioTrack、OpenSL ES 音频渲染

    前言 在讲解音频渲染之前,需要对音频的基础知识有所了解,所以该篇分为基础概念和AudioTrack 以及 OpenSL ES Demo 实例讲解,这样有助于更好的理解 Android 中音频渲染. 音 ...

  6. js控制audio音量_js控制html5 audio音频暂停播放

    js控制html5 audio音频暂停播放 音乐控制 音乐 播放/暂停 重新播放 function rbf(){ var audio = document.getElementById('music1 ...

  7. html5 audio音频播放全解析

    html5开启了一个新时代,因为它让浏览器本身变得不那么被动,audio api就是一个典型的列子,在html5还没确定之前,如果想要在网页上听音乐看视频唯一的办法就是用flash 意思是当你没有给浏 ...

  8. html5 audio 资源,HTML5 Audio(音频)

    原标题:HTML5 Audio(音频) HTML5 提供了播放音频文件的标准. 互联网上的音频 直到现在,仍然不存在一项旨在网页上播放音频的标准. 今天,大多数音频是通过插件(比如 Flash)来播放 ...

  9. 10.Audio音频

    1.Audio音频 1.今天,大多数音频是通过插件(比如 Flash)来播放的.然而,并非所有浏览器都拥有同样的插件. 2.HTML5 规定了在网页上嵌入音频元素的标准,即使用 <audio&g ...

最新文章

  1. 基于Windows下使用Docker 部署Redis
  2. Java 散点图 数据库 代码_java – 来自数据库的jfreechart中的散点图
  3. 4.QT中进程操作,线程操作
  4. 0.为什么要学习Vue?
  5. Android源码解析--SwipeMenuListView仿QQ聊天左滑
  6. leetcode 705. 设计哈希集合
  7. 前端学习(3142):react-hello-react之父组件render
  8. FreeSql (三十五)CodeFirst 自定义特性
  9. SQL Server 输出 XML
  10. 6G 研发启动,普通人如何借势加薪?
  11. 九章算术卷第二 粟米
  12. 【大数据编程笔记】大数据背景,案例,概念
  13. python plot linestyle 线型颜色及线条控制(linestyle、marker、color)
  14. AWS 挂了 11 个小时:因多处光缆被挖断
  15. 深度解析 mPaaS 小程序一站式研发
  16. sql语句优化的几种方法
  17. [工具] Mac下一键APK逆向环境
  18. 计算机 统计学考研,统计学考研科目有哪些
  19. Android实战之淘宝领券(二)
  20. 使用kind快速搭建本地k8s集群

热门文章

  1. 大数据专业来袭!前京东大学大数据院长亲自为你授课!
  2. UGREEN 绿联 USB 2.0转串口DB9 打印线 驱动安装教程
  3. 独立设置mac的鼠标和触控板滚动方向
  4. 差分隐私 java_差分隐私(一) Differential Privacy 简介
  5. Kubernetes之Ingress
  6. 3dMaxs下载链接
  7. 区块链时代正开启 中原精英探讨区块链未来 ----2018华中首届区块链论坛郑州开幕
  8. 某yin黑盒得到as,cp,mas参数
  9. Springboot简易聊天室
  10. 麓言信息学UI设计必须掌握的知识体系和职业规划