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

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

3天内不再提示

OpenHarmony 3.2 Beta Audio:音频渲染

电子发烧友开源社区 来源:未知 2023-03-11 14:15 次阅读

一、简介

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

(左右移动查看全部内容)

三、音频渲染总体流程

8946c0e8-bfd2-11ed-bfe3-dac502259ad0.png

四、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方法,将数据写入,音频数据会被播放。

五、调用流程

895ca62e-bfd2-11ed-bfe3-dac502259ad0.png

1、创建AudioRenderer

std::unique_ptrAudioRenderer::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_guardlockdata(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方法将数据写入设备,进行播放。


更多热点文章阅读
  • OS内核及视窗分论坛详解之OpenHarmony 3D显示支持
  • 应用模型开发指南上新介绍
  • 技术构筑万物智联,第一届OpenHarmony技术峰会圆满举行
  • OpenHarmony L1(3.0)串口功能开发
  • 小白指南:手把手教你用低代码开发一个应用页面

提示:本文由电子发烧友论坛发布,转载请注明来源。如需社区合作及入群交流,请添加微信EEFans0806,或者发邮箱liuyong@huaqiu.com。


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

文章出处:【微信公众号:电子发烧友开源社区】欢迎添加关注!文章转载请注明出处。


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

    关注

    33

    文章

    546

    浏览量

    32356
  • 开源社区
    +关注

    关注

    0

    文章

    92

    浏览量

    342

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

文章出处:【微信号:HarmonyOS_Community,微信公众号:电子发烧友开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    OpenHarmony Sheet 表格渲染引擎

    基于 Canvas 实现的高性能 Excel 表格引擎组件 [OpenHarmonySheet]。 由于大部分前端项目渲染层是使用框架根据排版模型树结构逐层渲染的,整棵渲染树也是与排版
    发表于 01-05 16:32

    OpenHarmony开源GPU库Mesa3D适配说明

    本文档主要讲解在OpenHarmony中,Mesa3D的适配方法及原理说明。 环境说明: OHOS版本: 适用3.2-Beta3及以上 内核版本: linux-5.10 硬件环境
    发表于 12-25 11:38

    Unity中国、Cocos为OpenHarmony游戏生态插上腾飞的翅膀

    需求,结合本土文化元素和市场特色,为创作提供全面支持。团结引擎已兼容适配了中国科技生态内的众多软硬件平台,并联合基金会共同研发适配,已经完成了OpenHarmony3.2Release和 4.0 beta
    发表于 10-23 16:15

    HarmonyOS音频开发指导:使用AudioRenderer开发音频播放功能

    : audio.StreamUsage.STREAM_USAGE_MEDIA, // 音频流使用类型 rendererFlags: 0 // 音频渲染器标志 } private au
    发表于 10-23 14:21

    润开鸿DAYU200及DAYU210联袂通过OpenHarmony 3.2 Release版本兼容性测评

    近日,润开鸿HH-SCDAYU200及HH-SCDAYU210两款开发平台同时通过OpenHarmony 3.2 Release版本兼容性测评,在引领技术共建、有力推动OpenHarmony新版本
    发表于 09-22 11:11

    润开鸿DAYU200及DAYU210联袂通过OpenHarmony 3.2 Release版本兼容性测评

    3.2 Release版本兼容性测评,在引领技术共建、有力推动OpenHarmony新版本先行示范的同时,为基于3.2 Release版本开发商用富设备及相关行业应用提供了成熟、便捷的硬件平台
    的头像 发表于 09-21 21:10 584次阅读
    润开鸿DAYU200及DAYU210联袂通过<b class='flag-5'>OpenHarmony</b> <b class='flag-5'>3.2</b> Release版本兼容性测评

    【触觉智能 Purple Pi OH 开发板体验】OpenHarmony音频播放应用

    1 注意事项 DevEco Studio 4.0 Beta2(Build Version: 4.0.0.400) OpenHarmony SDK API 9 创建工程类型选择Application
    发表于 09-11 10:46

    OpenHarmony 4.0 Beta2新版本发布,邀您体验

    2023年8月3日,OpenAtom OpenHarmony(简称“OpenHarmony”)发布了Beta2版本,相较于历史版本我们持续完善ArkUI、文件管理、媒体、窗口、安全等系统能力、提升
    发表于 08-25 09:49

    HarmonyOS/OpenHarmony应用开发-ArkTS语言渲染控制if/else条件渲染

    ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态,使用if、else和else if渲染对应状态下的UI内容。说明:从API version 9开始,该接口支持在ArkTS卡片中使用。一
    发表于 08-21 14:29

    OpenHarmony 4.0 Beta1发布,邀您体验

    点击蓝字 ╳ 关注我们 开源项目 OpenHarmony 是每个人的 OpenHarmony 初夏之际,OpenAtom OpenHarmony(简称“OpenHarmony”) 4.
    的头像 发表于 06-08 18:30 398次阅读

    OpenHarmony 4.0 Beta1发布,邀您体验

    初夏之际,OpenAtom OpenHarmony(简称“OpenHarmony”) 4.0 Beta1版本如期而至。4.0 Beta1版本在3.2
    发表于 06-08 14:14

    OpenHarmony 3.2 Release新特性解读之驱动HCS

    OpenHarmony”)开源社区,在今年4月正式发布了OpenHarmony 3.2 Release版本,标准系统能力进一步完善,提升了系统的整体性能、稳定性和安全性。此次版本对于驱动也提供了一些
    的头像 发表于 05-30 21:45 495次阅读

    一加6T适配OpenHarmony 3.2

    OpenHarmony-3.2-Release 已发布,并且已经有好一段时间,本次释放一加 6/6T 相关刷机包,主要升级为 OpenHarmony-3.2-Release 正式版并初步支持部分外设功能(如音频播放和蓝牙),及更
    的头像 发表于 05-30 15:39 603次阅读
    一加6T适配<b class='flag-5'>OpenHarmony</b> <b class='flag-5'>3.2</b>

    突破性进展|软通动力子公司率先完成OpenHarmony操作系统适配PC端

    鸿湖万联应邀出席此次大会,并在本次大会上重磅发布了基于OpenHarmony 3.2 Release版本,面向PC端的SwanLinkOS商业PC发行版(Beta版)。
    的头像 发表于 05-24 17:43 826次阅读

    OpenHarmony开发者大会 | 技术分论坛:OpenHarmony 3.2 Release技术底座深度解析

    2023(以下简称“大会”)在北京圆满落幕。大会正式对外发布了OpenAtom OpenHarmony(以下简称“OpenHarmony”)3.2 Release版本,该版本全面提升了复杂带屏设备体验,可以
    的头像 发表于 05-11 20:16 485次阅读