前言
Audio Vivid(菁彩三维声)是全球首个基于AI技能的音频编解码标准,由天下超高清视频产业联盟(UWA联盟)与数字音视频编解码技能标准工作组(AVS)团结订定,共同发布。包含音频PCM数据以及元数据的音频格式,相比传统立体声音源,Audio Vivid包含音频内容源的元数据信息,能够还原物理和感知天下中的真实听感,打造极致的沉浸式听觉体验。
HarmonyOS打造全链路高清空间音频系统,包含Audio Vivid编解码、空间音频渲染算法等关键本领,并在各类终端产物逐步构建全场景空间音频特性,从传统立体声升级到三维声,得到更好的音质、更沉浸的空间感。
HarmonyOS支持播放Audio Vivid格式音源,并在耳机实现双耳空间音频渲染重放,在手机、平板、PC等支持外放空间音频渲染重放。系统的空间音频渲染本领无感接入,不必要做特定适配。
搭配HarmonyOS高清空间音频渲染本领,将音乐中的人声和各种乐器声作为独立的声音对象,重新定义各种声音对象的位置、移动轨迹和声音大小、远近等要素,实现声音在听众四周及上方全面萦绕,实现更佳的空间音频沉浸式体验,得到影院、音乐厅等的临场感与艺术体验。佩戴支持头动跟踪的耳机收听空间音频,还能实现动态头动跟踪,让声音围绕听众重新定位,还原逼真的临场感。
以下主要介绍利用HarmonyOS进行Audio Vivid格式音源的端到端播放的流程。
Audio Vivid端到端播放包罗调用系统编解码本领进行解封装、解码,以及调用系统播放本领进行渲染播放两个部门。
AudioVivid解封装
获取到Audio Vivid封装的mp4文件后,先调用解封装相干接口,选中音频轨,读取每一帧Audio Vivid,送入解码器中。详细的API请参考音频解封装API参考。
在Cmake脚本中链接到动态库
- target_link_libraries(sample PUBLIC
- libnative_media_codecbase.so libnative_media_core.so
- libnative_media_acodec.so libnative_media_avdemuxer.so libnative_media_avsource.so
- )
复制代码 添加头文件
- //解封装头文件
- #include "multimedia/player_framework/native_avdemuxer.h"
- // 解封装解码传递信息结构体
- struct AudioSampleInfo {
- std::string audioCodecMime = "";
- int32_t audioSampleForamt = 0;
- int32_t audioSampleRate = 0;
- int32_t audioChannelCount = 0;
- int64_t audioChannelLayout = 0;
- uint8_t audioCodecConfig[100] = {0};
- size_t audioCodecSize = 0;
- };
- AudioSampleInfo info;
复制代码 开发步调
- // ts code获取fd和size
- let inputFile = fs.openSync(filepath,fs.OpenMode.READ_ONLY);
- if(inputFile){
- let inputFileState = fs.stateSync(inputFile.fd);
- let inputFileSize = inputFIleState.size;
- }
复制代码- //C++ code
- OH_AVSource *source = OH_AVSource_CreateWithFD(inputFd,0,inputFileSize);
- OH_AVDemuxer *demuxer = OH_AVDemuxer_CreateWithSource(source);
- auto sourceFormat = std::shared_ptr<OH_AVFormat>(OH_AVSource_GetSourceFormat(source_), OH_AVFormat_Destroy);
- int32_t trackCount = 0;
- OH_AVFormat_GetIntValue(sourceFormat.get(), OH_MD_KEY_TRACK_COUNT, &trackCount);
复制代码- int32_t trackCount = 0;
- OH_AVFormat_GetIntValue(sourceFormat.get(), OH_MD_KEY_TRACK_COUNT, &trackCount);
- for (int32_t index = 0; index < trackCount; index++) {
- int trackType = -1;
- auto trackFormat =
- std::shared_ptr<OH_AVFormat>(OH_AVSource_GetTrackFormat(source_, index), OH_AVFormat_Destroy);
- // 获取轨道类型
- OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_TRACK_TYPE, &trackType);
- // 判断当前轨道为音频轨
- if (trackType == MEDIA_TYPE_AUD) {
- // 选中音频轨
- OH_AVDemuxer_SelectTrackByID(demuxer, index);
- // 获取位深
- OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_AUDIO_SAMPLE_FORMAT, &info.audioSampleForamt);
- // 获取声道数
- OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_AUD_CHANNEL_COUNT, &info.audioChannelCount);
- // 获取声道布局
- OH_AVFormat_GetLongValue(trackFormat.get(), OH_MD_KEY_CHANNEL_LAYOUT, &info.audioChannelLayout);
- // 获取采样率
- OH_AVFormat_GetIntValue(trackFormat.get(), OH_MD_KEY_AUD_SAMPLE_RATE, &info.audioSampleRate);
- // 获取额外配置信息
- uint8_t *addr = nullptr;
- OH_AVFormat_GetBuffer(trackFormat.get(), OH_MD_KEY_CODEC_CONFIG, &addr, &info.audioCodecSize);
- memcpy((void *)info.audioCodecConfig, (void *)addr, info.audioCodecSize);
- // 获取解码器类型
- char *audioCodecMime;
- OH_AVFormat_GetStringValue(trackFormat.get(), OH_MD_KEY_CODEC_MIME, const_cast<char const **>(&audioCodecMime));
- info.audioCodecMime = audioCodecMime;
- int32_t trackId = index;
- break;
- }
- }
复制代码- OH_AVBuffer *buffer;
- int32_t ret = OH_AVDemuxer_ReadSampleBuffer(demuxer, trackId, buffer);
复制代码- int32_t Release()
- {
- if (demuxer != nullptr) {
- OH_AVDemuxer_Destroy(demuxer);
- demuxer = nullptr;
- }
- if (source != nullptr) {
- OH_AVSource_Destroy(source);
- source = nullptr;
- }
- return AVCODEC_SAMPLE_ERR_OK;
- }
复制代码 AudioVivid解码
获取解封装后的数据,送入解码器中,利用解码器获取PCM和Metadata元数据。详细的API请参考音频解码API参考。
在Cmake脚本中链接到动态库
- target_link_libraries(sample PUBLIC
- libnative_media_codecbase.so libnative_media_core.so
- libnative_media_acodec.so libnative_media_avdemuxer.so libnative_media_avsource.so
- )
复制代码 添加头文件
- //解封装头文件
- #include "multimedia/player_framework/native_avdemuxer.h"
- // 解封装解码传递信息结构体
- struct AudioSampleInfo {
- std::string audioCodecMime = "";
- int32_t audioSampleForamt = 0;
- int32_t audioSampleRate = 0;
- int32_t audioChannelCount = 0;
- int64_t audioChannelLayout = 0;
- uint8_t audioCodecConfig[100] = {0};
- size_t audioCodecSize = 0;
- };
- AudioSampleInfo info;
复制代码 定义相干实例
定义CodecBufferInfo
解码码流的属性定义,为后面传给播放的码流数据封装。
- struct CodecBufferInfo {
- uint32_t bufferIndex = 0;
- uintptr_t *buffer = nullptr;
- uint8_t *bufferAddr = nullptr;
- OH_AVCodecBufferAttr attr = {0, 0, 0, AVCODEC_BUFFER_FLAGS_NONE};
- CodecBufferInfo(uint8_t *addr) : bufferAddr(addr){};
- CodecBufferInfo(uint8_t *addr, int32_t bufferSize)
- : bufferAddr(addr), attr({0, bufferSize, 0, AVCODEC_BUFFER_FLAGS_NONE}){};
- CodecBufferInfo(uint32_t argBufferIndex, OH_AVMemory *argBuffer, OH_AVCodecBufferAttr argAttr)
- : bufferIndex(argBufferIndex), buffer(reinterpret_cast<uintptr_t *>(argBuffer)), attr(argAttr){};
- CodecBufferInfo(uint32_t argBufferIndex, OH_AVMemory *argBuffer)
- : bufferIndex(argBufferIndex), buffer(reinterpret_cast<uintptr_t *>(argBuffer)){};
- CodecBufferInfo(uint32_t argBufferIndex, OH_AVBuffer *argBuffer)
- : bufferIndex(argBufferIndex), buffer(reinterpret_cast<uintptr_t *>(argBuffer)) {
- OH_AVBuffer_GetBufferAttr(argBuffer, &attr);
- };
- };
复制代码 定义解码工作队列
- class CodecUserData {
- public:
- SampleInfo *sampleInfo = nullptr;
- // 输入帧数
- uint32_t inputFrameCount_ = 0;
- // 输入队列锁,防止多线程同时操作输入队列
- std::mutex inputMutex_;
- // 输入线程的条件变量,当输入队列为空时用于阻塞输入线程
- std::condition_variable inputCond_;
- // 输入buffer队列,存放编解码器传给用户用来写入输入数据的buffer
- std::queue<CodecBufferInfo> inputBufferInfoQueue_;
- // 输出帧数
- uint32_t outputFrameCount_ = 0;
- // 输出队列锁,防止多线程同时操作输出队列
- std::mutex outputMutex_;
- // 输出线程的条件变量,当输出队列为空时用于阻塞输出线程
- std::condition_variable outputCond_;
- std::mutex renderMutex_;
- std::condition_variable renderCond_;
- // 输出buffer队列,存放编解码器传给用户用来存放输出数据的buffer
- std::queue<CodecBufferInfo> outputBufferInfoQueue_;
- std::shared_ptr<AudioDecoder> audioCodec_;
- std::queue<unsigned char> renderQueue_;
- void ClearQueue() {
- {
- std::unique_lock<std::mutex> lock(inputMutex_);
- auto emptyQueue = std::queue<CodecBufferInfo>();
- inputBufferInfoQueue_.swap(emptyQueue);
- }
- {
- std::unique_lock<std::mutex> lock(outputMutex_);
- auto emptyQueue = std::queue<CodecBufferInfo>();
- outputBufferInfoQueue_.swap(emptyQueue);
- }
- }
- };
复制代码 定义回调函数
- class SampleCallback {
- public:
- // 报错回调函数,当编解码器内部报错时调用,返回给用户相应错误码
- static void OnCodecError(OH_AVCodec *codec, int32_t errorCode, void *userData);
- // 参数修改回调函数,当编解码器参数被修改时调用,返回给用户被修改后的format参数
- static void OnCodecFormatChange(OH_AVCodec *codec, OH_AVFormat *format, void *userData);
- // 输入回调函数,当编解码器需要输入时调用,返回给用户用来写入输入数据的buffer及其对应的index
- static void OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData);
- // 输出回调函数,当编解码器生成新的输出数据时调用,返回给用户用来存放输出数据的buffer及其对应的index
- static void OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData);
- };
- void SampleCallback::OnCodecError(OH_AVCodec *codec, int32_t errorCode, void *userData) {
- (void)codec;
- (void)errorCode;
- (void)userData;
- }
- void SampleCallback::OnCodecFormatChange(OH_AVCodec *codec, OH_AVFormat *format, void *userData) {
- }
- void SampleCallback::OnNeedInputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) {
- if (userData == nullptr) {
- return;
- }
- (void)codec;
- CodecUserData *codecUserData = static_cast<CodecUserData *>(userData);
- std::unique_lock<std::mutex> lock(codecUserData->inputMutex_);
- // 将输入buffer存放到输入队列中
- codecUserData->inputBufferInfoQueue_.emplace(index, buffer);
- // 通知输入线程开始运行
- codecUserData->inputCond_.notify_all();
- }
- void SampleCallback::OnNewOutputBuffer(OH_AVCodec *codec, uint32_t index, OH_AVBuffer *buffer, void *userData) {
- if (userData == nullptr) {
- return;
- }
- (void)codec;
- CodecUserData *codecUserData = static_cast<CodecUserData *>(userData);
- std::unique_lock<std::mutex> lock(codecUserData->outputMutex_);
- // 将输出buffer存放到输出队列中
- codecUserData->outputBufferInfoQueue_.emplace(index, buffer);
- // 通知输出线程开始运行
- codecUserData->outputCond_.notify_all();
- }
复制代码 开发步调
- // 创建解码器
- OH_AVCodec * decoder = OH_AudioCodec_CreateByMime(info.audioCodecMime,false);
- // 参数配置
- OH_AVFormat *format = OH_AVFormat_Create();
- OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUDIO_SAMPLE_FORMAT, SAMPLE_S16LE); //或者S24LE
- OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_CHANNEL_COUNT, sampleInfo.audioChannelCount);
- OH_AVFormat_SetIntValue(format, OH_MD_KEY_AUD_SAMPLE_RATE, sampleInfo.audioSampleRate);
- OH_AVFormat_SetIntValue(format, OH_MD_KEY_AAC_IS_ADTS, 1);
- OH_AVFormat_SetLongValue(format, OH_MD_KEY_BITRATE, 96422);//码率,当前作为参考,解封装也可以获取到
- OH_AVFormat_SetBuffer(format, OH_MD_KEY_CODEC_CONFIG, sampleInfo.audioCodecConfig, sampleInfo.audioCodecSize);
- bool res = OH_AVFormat_SetLongValue(format, OH_MD_KEY_CHANNEL_LAYOUT, sampleInfo.audioChannelLayout);
- ret = OH_AudioCodec_Configure(decoder, format);
- OH_AVFormat_Destroy(format);
- format = nullptr;
- // 设置回调,用于输入输出buffer准备完毕后由系统回调出来
- int32_t ret = OH_AudioCodec_RegisterCallback(decoder,
- {SampleCallback::OnCodecError, SampleCallback::OnCodecFormatChange,
- SampleCallback::OnNeedInputBuffer, SampleCallback::OnNewOutputBuffer},codecUserData);
- // 准备回调和参数设置完毕后通知系统解码器准备好了,下一步准备启动。
- ret = OH_AudioCodec_Prepare(decoder)
复制代码- int32_t PushInputData(CodecBufferInfo &info)
- {
- int32_t ret = OH_AVBuffer_SetBufferAttr(reinterpret_cast<OH_AVBuffer *>(info.buffer), &info.attr);
- ret = OH_AudioCodec_PushInputBuffer(decoder, info.bufferIndex);
- return 0;
- }
复制代码- CodecUserData*audioDecContext_ = new CodecUserData;
- void AudioDecInputThread()
- {
- while (true) {
- if(!isStarted_){
- return;
- }
- std::unique_lock<std::mutex> lock(audioDecContext_->inputMutex_);
- // 阻塞输入线程,直接程序运行结束,或者输入队列不为空
- bool condRet = audioDecContext_->inputCond_.wait_for(
- lock, 5s, [this]() { return !isStarted_ || !audioDecContext_->inputBufferInfoQueue_.empty(); });
- if(!isStarted_ || audioDecContext_->inputBufferInfoQueue_.empty()){
- return;
- }
- // 获取输入buffer
- CodecBufferInfo bufferInfo = audioDecContext_->inputBufferInfoQueue_.front();
- audioDecContext_->inputBufferInfoQueue_.pop();
- audioDecContext_->inputFrameCount_++;
- lock.unlock();
- // 从解封装器中读取一帧数据写入输入buffer
- demuxer_->ReadSample(demuxer_->GetAudioTrackId(), reinterpret_cast<OH_AVBuffer *>(bufferInfo.buffer), bufferInfo.attr);
- int32_t ret = audioDecoder_->PushInputData(bufferInfo);
- if(ret != 0){
- return;
- }
- if(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS){
- return;
- }
- }
- // StartRelease();
- }
复制代码- CodecUserData*audioDecContext_ = new CodecUserData;
- void AudioDecInputThread()
- {
- while (true) {
- if(!isStarted_){
- return;
- }
- std::unique_lock<std::mutex> lock(audioDecContext_->inputMutex_);
- // 阻塞输入线程,直接程序运行结束,或者输入队列不为空
- bool condRet = audioDecContext_->inputCond_.wait_for(
- lock, 5s, [this]() { return !isStarted_ || !audioDecContext_->inputBufferInfoQueue_.empty(); });
- if(!isStarted_ || audioDecContext_->inputBufferInfoQueue_.empty()){
- return;
- }
- // 获取输入buffer
- CodecBufferInfo bufferInfo = audioDecContext_->inputBufferInfoQueue_.front();
- audioDecContext_->inputBufferInfoQueue_.pop();
- audioDecContext_->inputFrameCount_++;
- lock.unlock();
- // 从解封装器中读取一帧数据写入输入buffer
- demuxer_->ReadSample(demuxer_->GetAudioTrackId(), reinterpret_cast<OH_AVBuffer *>(bufferInfo.buffer), bufferInfo.attr);
- int32_t ret = audioDecoder_->PushInputData(bufferInfo);
- if(ret != 0){
- return;
- }
- if(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS){
- return;
- }
- }
- // StartRelease();
- }
复制代码- void AudioDecOutputThread()
- {
- while (true) {
- if(!isStarted_){
- return;
- }
- std::unique_lock<std::mutex> lock(audioDecContext_->outputMutex_);
- // 阻塞输出线程,直接程序运行结束,或者输出队列不为空
- bool condRet = audioDecContext_->outputCond_.wait_for(
- lock, 5s, [this]() { return !isStarted_ || !audioDecContext_->outputBufferInfoQueue_.empty(); });
- if(!isStarted_ || audioDecContext_->outputBufferInfoQueue_.empty()){
- return;
- }
- // 获取输出buffer
- CodecBufferInfo bufferInfo = audioDecContext_->outputBufferInfoQueue_.front();
- audioDecContext_->outputBufferInfoQueue_.pop();
- if(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS){
- return;
- }
- audioDecContext_->outputFrameCount_++;
- // 获取解码后的pcm数据
- uint8_t *source = OH_AVBuffer_GetAddr(reinterpret_cast<OH_AVBuffer *>(bufferInfo.buffer));
- OH_AVFormat * format = OH_AVBuffer_GetParameter(data);
- uint8_t * metadata;
- size_t size;
- // 获取元数据
- OH_AVFormat_GetBuffer(format, OH_MD_KEY_AUDIO_VIVID_METADATA, &metadata, &size);
- #ifdef DEBUG_DECODE
- if (audioOutputFile_.is_open()) {
- audioOutputFile_.write((const char*)OH_AVBuffer_GetAddr(reinterpret_cast<OH_AVBuffer *>(bufferInfo.buffer)), bufferInfo.attr.size);
- }
- #endif
- lock.unlock();
- int32_t ret = audioDecoder_->FreeOutputData(bufferInfo.bufferIndex);
- if(ret != 0){
- return;
- }
- }
- }
复制代码- int ret = OH_AudioCodec_Start(decoder);
复制代码- OH_AudioCodec_Stop(decoder);
- OH_AudioCodec_Destroy(decoder);
- decoder = nullptr;
复制代码 AudioVivid播放
在获取到解码后的Audio Vivid的PCM数据和元数据之后,可以调用OHAudio的相干播放接口,进行Audio Vivid格式音源的渲染播放。详细的API说明请参考OHAudio API参考。
在Cmake脚本中链接到动态库
- target_link_libraries(sample PUBLIC libohaudio.so)
复制代码 添加头文件
- #include <ohaudio/native_audiorenderer.h>
- #include <ohaudio/native_audiostreambuilder.h>
复制代码 开发步调
开发者可以通过以下几个步调来实现一个简单的播放功能。
- 创建构造器。
OHAudio提供OH_AudioStreamBuilder接口,遵照构造器筹划模式,用于构建音频流。在Audio Vivid播放场景下,必要选择OH_AudioStream_Type为AUDIOSTREAM_TYPE_RENDERER,创建一个渲染播放范例的音频构造器。
- OH_AudioStreamBuilder* builder;
- OH_AudioStreamBuilder_Create(&builder, AUDIOSTREAM_TYPE_RENDERER);
复制代码
- 配置音频流参数。
创建音频播放构造器后,可以设置音频流所必要的参数,可以参考以下案例。
Audio Vivid音源搭配系统空间音频渲染算法,播放效果和体验最佳。系统会根据输出音频流的工作场景(OH_AudioStream_Usage),选择利用对应的空间音频渲染效果,当前支持的工作场景包罗音乐、电影和有声读物。
- // 设置音频采样率为48000Hz
- OH_AudioStreamBuilder_SetSamplingRate(builder, 48000);
- // 设置音频声道为10 (假定输入Audio Vivid音源是5.1.2声床 + 2对象格式)
- OH_AudioStreamBuilder_SetChannelCount(builder, 10);
- // 设置音频声道布局为5.1.2 (声道布局只考虑声床,若想使用默认声道布局,可以传入 CH_LAYOUT_UNKNOWN 参数)
- OH_AudioStreamBuilder_SetChannelLayout(builder, CH_LAYOUT_5POINT1POINT2);
- // 设置音频采样格式
- OH_AudioStreamBuilder_SetSampleFormat(builder, AUDIOSTREAM_SAMPLE_S16LE);
- // 设置音频流的编码类型为Audio Vivid编码类型
- OH_AudioStreamBuilder_SetEncodingType(builder, AUDIOSTREAM_ENCODING_TYPE_AUDIOVIVID);
- // 设置输出音频流的工作场景,根据实际工作场景选择音乐、电影、有声读物等类型
- OH_AudioStreamBuilder_SetRendererInfo(builder, AUDIOSTREAM_USAGE_MUSIC);
复制代码
- 设置音频回调函数。
OHAudio利用回调模式进行音频流数据的写入,以及各种音频事件的上报,应用可以按需选择必要监听的音频事件。
为了克制不可预期的举动,在设置音频回调函数时,请确认OH_AudioRenderer_Callbacks的每一个回调都被自定义的回调方法或空指针初始化。
对于Audio Vivid播放场景,必要另外利用OH_AudioRenderer_WriteDataWithMetadataCallback进行PCM和元数据的写入。
- // 自定义音频流事件函数
- int32_t MyOnStreamEvent(
- OH_AudioRenderer* renderer,
- void* userData,
- OH_AudioStream_Event event)
- {
- // 根据event表示的音频流事件信息,更新播放器状态和界面
- return 0;
- }
- // 自定义音频中断事件函数
- int32_t MyOnInterruptEvent(
- OH_AudioRenderer* renderer,
- void* userData,
- OH_AudioInterrupt_ForceType type,
- OH_AudioInterrupt_Hint hint)
- {
- // 根据type和hint表示的音频中断信息,更新播放器状态和界面
- return 0;
- }
- // 自定义异常回调函数
- int32_t MyOnError(
- OH_AudioRenderer* renderer,
- void* userData,
- OH_AudioStream_Result error)
- {
- // 根据error表示的音频异常信息,做出相应的处理
- return 0;
- }
- // 配置回调函数
- OH_AudioRenderer_Callbacks callbacks;
- // Audio Vivid播放时,该回调可以置空,使用元数据回调方式进行数据写入
- callbacks.OH_AudioRenderer_OnWriteData = nullptr;
- // 对音频流事件进行监听,如果不需要,可以使用 nullptr 赋值
- callbacks.OH_AudioRenderer_OnStreamEvent = MyOnStreamEvent;
- // 对音频中断事件进行监听,如果不需要,可以使用 nullptr 赋值
- callbacks.OH_AudioRenderer_OnInterruptEvent = MyOnInterruptEvent;
- // 对音频异常事件进行监听,如果不需要,可以使用 nullptr 赋值
- callbacks.OH_AudioRenderer_OnError = MyOnError;
- //设置输出音频流的回调
- OH_AudioStreamBuilder_SetRendererCallback(builder, callbacks, nullptr);
- // 自定义同时写入PCM数据和元数据函数
- int32_t MyOnWriteDataWithMetadata(
- OH_AudioRenderer* renderer,
- void* userData,
- void* audioData,
- int32_t audioDataSize,
- void* metadata,
- int32_t metadataSize)
- {
- // 将待播放的PCM数据和元数据,分别按audioDataSize和metadataSize写入buffer
- return 0;
- }
- // 配置回调函数
- OH_AudioRenderer_WriteDataWithMetadataCallback metadataCallback = MyOnWriteDataWithMetadata;
- // 设置同时写入PCM数据和元数据的回调
- OH_AudioStreamBuilder_SetWriteDataWithMetadataCallback(builder, metadataCallback, nullptr);
复制代码- OH_AudioRenderer* audioRenderer;
- OH_AudioStreamBuilder_GenerateRenderer(builder, &audioRenderer);
复制代码
- 利用音频流。
可以利用以下接口,实现对音频流的控制,完成开始播放、停息播放、停止播放、清除缓存等基本操作。
在不再利用该条音频流时,可以释放播放实例,以便更好地管理内存。
- 释放构造器。
当构造器不再利用时,必要释放相干资源。
- OH_AudioStreamBuilder_Destroy(builder);
复制代码 写在最后
有很多小伙伴不知道学习哪些鸿蒙开发技能?不知道必要重点把握哪些鸿蒙应用开发知识点?而且学习时频仍踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有须要的。
盼望这一份鸿蒙学习文档能够给大家带来帮助,有必要的小伙伴自行领取,限时开源,先到先得~无套路领取!!
假如你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感爱好以及转行职员,可以直接领取这份资料
请点击→纯血版全套鸿蒙HarmonyOS学习文档
鸿蒙(HarmonyOS NEXT)5.0最新学习路线
路线图适合人群:
IT开发职员:想要拓展职业边界
零底子小白:鸿蒙爱好者,盼望从0到1学习,增长一项技能。
技能提拔/进阶跳槽:发展瓶颈期,提拔职场竞争力,快速把握鸿蒙技能
获取以上完备版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习文档
《鸿蒙 (HarmonyOS)开发入门讲授视频》
《鸿蒙生态应用开发V3.0白皮书》
《鸿蒙 (OpenHarmony)开发底子到实战手册》
OpenHarmony北向、南向开发情况搭建
《鸿蒙开发底子》
●ArkTS语言
●安装DevEco Studio
●运用你的第一个ArkTS应用
●ArkUI声明式UI开发
.……
《鸿蒙开发进阶》
●Stage模型入门
●网络管理
●数据管理
●电话服务
●分布式应用开发
●关照与窗口管理
●多媒体技能
●安全技能
●任务管理
●WebGL
●国际化开发
●应用测试
●DFX面向未来筹划
●鸿蒙系统移植和裁剪定制
……
《鸿蒙进阶实战》
●ArkTS实践
●UIAbility应用
●网络案例
……
获取以上完备鸿蒙HarmonyOS学习文档,请点击→纯血版全套鸿蒙HarmonyOS学习文档
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |