0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

OpenHarmony 3.2 Beta Audio——音頻渲染

OpenAtom OpenHarmony ? 來源:未知 ? 2023-03-11 16:40 ? 次閱讀

點(diǎn)擊藍(lán)字 ╳ 關(guān)注我們


開源項(xiàng)目 OpenHarmony
是每個(gè)人的 OpenHarmony

巴延興

深圳開鴻數(shù)字產(chǎn)業(yè)發(fā)展有限公司

資深OS框架開發(fā)工程師

一、簡介

Audio是多媒體子系統(tǒng)中的一個(gè)重要模塊,其涉及的內(nèi)容比較多,有音頻的渲染、音頻的采集、音頻的策略管理等。本文主要針對(duì)音頻渲染功能進(jìn)行詳細(xì)地分析,并通過源碼中提供的例子,對(duì)音頻渲染進(jìn)行流程的梳理。

二、目錄

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實(shí)現(xiàn)的接口
│ │ └── native
│ │ └── audiorenderer #audio渲染本地實(shí)現(xiàn)的接口定義
│ │ └── include
│ │ └── audio_renderer.h
│ └── kits #js調(diào)用的接口
│ └── js
│ └── audio_renderer #audio渲染NAPI接口的定義
│ └── include
│ └── audio_renderer_napi.h
└── services #服務(wù)端
└── audio_service
├── BUILD.gn
├── client #IPC調(diào)用中的proxy
│ ├── include
│ │ ├── audio_manager_proxy.h
│ │ ├── audio_service_client.h
│ └── src
│ ├── audio_manager_proxy.cpp
│ ├── audio_service_client.cpp
└── server #IPC調(diào)用中的server
├── include
│ └── audio_server.h
└── src
├── audio_manager_stub.cpp
└──audio_server.cpp

三、音頻渲染總體流程


四、Native接口使用

在OpenAtom OpenHarmony(以下簡稱“OpenHarmony”)系統(tǒng)中,音頻模塊提供了功能測(cè)試代碼,本文選取了其中的音頻渲染例子作為切入點(diǎn)來進(jìn)行介紹,例子采用的是對(duì)wav格式的音頻文件進(jìn)行渲染。wav格式的音頻文件是wav頭文件和音頻的原始數(shù)據(jù),不需要進(jìn)行數(shù)據(jù)解碼,所以音頻渲染直接對(duì)原始數(shù)據(jù)進(jìn)行操作,文件路徑為: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);


//設(shè)置AudioRenderer參數(shù)
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;


//創(chuàng)建AudioRender實(shí)例
unique_ptr audioRenderer = AudioRenderer::Create(rendererOptions);


shared_ptr cb1 = make_shared();
//設(shè)置音頻渲染回調(diào)
ret = audioRenderer->SetRendererCallback(cb1);


//InitRender方法主要調(diào)用了audioRenderer實(shí)例的Start方法,啟動(dòng)音頻渲染
if (!InitRender(audioRenderer)) {
AUDIO_ERR_LOG("AudioRendererTest: Init render failed");
fclose(wavFile);
return false;
}


//StartRender方法主要是讀取wavFile文件的數(shù)據(jù),然后通過調(diào)用audioRenderer實(shí)例的Write方法進(jìn)行播放
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");
}


//關(guān)閉wavFile
fclose(wavFile);
return true;
}
首先讀取wav文件,通過讀取到wav文件的頭信息對(duì)AudioRendererOptions相關(guān)的參數(shù)進(jìn)行設(shè)置,包括編碼格式、采樣率、采樣格式、通道數(shù)等。根據(jù)AudioRendererOptions設(shè)置的參數(shù)來創(chuàng)建AudioRenderer實(shí)例(實(shí)際上是AudioRendererPrivate),后續(xù)的音頻渲染主要是通過AudioRenderer實(shí)例進(jìn)行。創(chuàng)建完成后,調(diào)用AudioRenderer的Start方法,啟動(dòng)音頻渲染。啟動(dòng)后,通過AudioRenderer實(shí)例的Write方法,將數(shù)據(jù)寫入,音頻數(shù)據(jù)會(huì)被播放。

五、調(diào)用流程


1.創(chuàng)建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方法獲取音頻流的類型,根據(jù)音頻流類型創(chuàng)建AudioRendererPrivate對(duì)象,AudioRendererPrivate是AudioRenderer的子類。緊接著對(duì)audioRenderer進(jìn)行參數(shù)設(shè)置,其中包括采樣格式、采樣率、通道數(shù)、編碼格式。設(shè)置完成后返回創(chuàng)建的AudioRendererPrivate實(shí)例。

2.設(shè)置回調(diào)
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;
}
參數(shù)傳入的回調(diào)主要涉及到兩個(gè)方面:一方面是AudioInterruptCallbackImpl中設(shè)置了我們傳入的渲染回調(diào),另一方面是AudioStreamCallbackRenderer中也設(shè)置了渲染回調(diào)。

3.啟動(dòng)渲染
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這個(gè)操作主要是根據(jù)AudioInterrupt來進(jìn)行音頻中斷的激活,這里涉及了音頻策略相關(guān)的內(nèi)容,后續(xù)會(huì)專門出關(guān)于音頻策略的文章進(jìn)行分析。這個(gè)方法的核心是通過調(diào)用AudioStream的StartAudioStream方法來啟動(dòng)音頻流。
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主要的工作是調(diào)用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方法中主要是調(diào)用了pulseaudio庫的pa_stream_cork方法進(jìn)行流啟動(dòng),后續(xù)就調(diào)用到了pulseaudio庫中了。pulseaudio庫我們暫且不分析。

4.寫入數(shù)據(jù)
int32_t AudioRendererPrivate::Write(uint8_t *buffer, size_t bufferSize)
{
return audioStream_->Write(buffer, bufferSize);
}
通過調(diào)用AudioStream的Write方式實(shí)現(xiàn)功能,接下來看一下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方法中分成兩個(gè)階段,首次寫數(shù)據(jù),先調(diào)用RenderPrebuf方法,將preBuf_的數(shù)據(jù)寫入后再調(diào)用WriteStream進(jìn)行音頻數(shù)據(jù)的寫入。
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方法不是直接調(diào)用pulseaudio庫的寫入方法,而是通過WriteToAudioCache方法將數(shù)據(jù)寫入緩存中,如果緩存沒有寫滿則直接返回,不會(huì)進(jìn)入下面的流程,只有當(dāng)緩存寫滿后,才會(huì)調(diào)用下面的PaWriteStream方法。該方法涉及對(duì)pulseaudio庫寫入操作的調(diào)用,所以緩存的目的是避免對(duì)pulseaudio庫頻繁地做IO操作,提高了效率。

六、總結(jié)

本文主要對(duì)OpenHarmony 3.2 Beta多媒體子系統(tǒng)的音頻渲染模塊進(jìn)行介紹,首先梳理了Audio Render的整體流程,然后對(duì)幾個(gè)核心的方法進(jìn)行代碼的分析。整體的流程主要通過pulseaudio庫啟動(dòng)流,然后通過pulseaudio庫的pa_stream_write方法進(jìn)行數(shù)據(jù)的寫入,最后播放出音頻數(shù)據(jù)。

音頻渲染主要分為以下幾個(gè)層次:
(1)AudioRenderer的創(chuàng)建,實(shí)際創(chuàng)建的是它的子類AudioRendererPrivate實(shí)例。
(2)通過AudioRendererPrivate設(shè)置渲染的回調(diào)。
(3)啟動(dòng)渲染,這一部分代碼最終會(huì)調(diào)用到pulseaudio庫中,相當(dāng)于啟動(dòng)了pulseaudio的流。
(4)通過pulseaudio庫的pa_stream_write方法將數(shù)據(jù)寫入設(shè)備,進(jìn)行播放。

對(duì)OpenHarmony 3.2 Beta多媒體系列開發(fā)感興趣的讀者,也可以閱讀我之前寫過幾篇文章:《OpenHarmony 3.2 Beta多媒體系列——視頻錄制》《OpenHarmony 3.2 Beta源碼分析之MediaLibrary》《OpenHarmony 3.2 Beta多媒體系列——音視頻播放框架》《OpenHarmony 3.2 Beta多媒體系列——音視頻播放gstreamer》。


原文標(biāo)題:OpenHarmony 3.2 Beta Audio——音頻渲染

文章出處:【微信公眾號(hào):OpenAtom OpenHarmony】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 鴻蒙
    +關(guān)注

    關(guān)注

    57

    文章

    2352

    瀏覽量

    42859
  • OpenHarmony
    +關(guān)注

    關(guān)注

    25

    文章

    3722

    瀏覽量

    16320

原文標(biāo)題:OpenHarmony 3.2 Beta Audio——音頻渲染

文章出處:【微信號(hào):gh_e4f28cfa3159,微信公眾號(hào):OpenAtom OpenHarmony】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    請(qǐng)問cc3200 audio boosterpack音頻采集是不是底噪很大?

    基于TLV320AIC3254的音頻開發(fā)辦,我燒入wifi_audio_app例程(例程中關(guān)掉板載咪頭輸入,并將音量調(diào)到最大),另兩入輸入接口沒有接音頻信號(hào),但是板子一直吱吱吱的響,是板子本身底噪就這么大嗎?
    發(fā)表于 10-25 06:28

    TPS6595 Audio Codec輸出音頻偶發(fā)混入7Khz雜波是怎么回事?

    主芯片是DM3730, 音頻使用的是TPS65950的Audio 外設(shè)。 DM3730使用MCBSP輸出8Khz音頻數(shù)據(jù),通過I2C設(shè)置 TPS65950相關(guān)寄存器。 采用Audio
    發(fā)表于 10-15 07:08

    【龍芯2K0300蜂鳥板試用】OpenHarmony代碼

    fetch origin OpenHarmony-3.2-Release:OpenHarmony-3.2-Release git switch OpenHarmony-3.2
    發(fā)表于 09-18 11:42

    LE Audio音頻技術(shù)助推影音、助聽市場(chǎng),藍(lán)牙+助聽芯片成為趨勢(shì)

    3.0版本支持高速數(shù)據(jù)傳輸和更低的功耗,藍(lán)牙4.0版本引入了低功耗模式(BLE),藍(lán)牙5.0版本提高了傳輸速度。再到藍(lán)牙5.2版本提出了LE Audio,增強(qiáng)了藍(lán)牙音頻體驗(yàn)。 進(jìn)入無損時(shí)代,LE Audio帶來低延遲、高音質(zhì)體驗(yàn)
    的頭像 發(fā)表于 07-08 07:41 ?3011次閱讀
    LE <b class='flag-5'>Audio</b><b class='flag-5'>音頻</b>技術(shù)助推影音、助聽市場(chǎng),藍(lán)牙+助聽芯片成為趨勢(shì)

    HarmonyOS NEXT Developer Beta1中的Kit

    管理服務(wù))、Network Kit(網(wǎng)絡(luò)服務(wù))等。 媒體相關(guān)Kit開放能力:Audio Kit(音頻服務(wù))、Media Library Kit(媒體文件管理服務(wù))等。 圖形相關(guān)Kit開放能力
    發(fā)表于 06-26 10:47

    使用提供的esp_audio_codec 的庫組件時(shí),不能將AAC音頻解碼回PCM音頻,為什么?

    使用提供的esp_audio_codec 的庫組件時(shí),能夠?qū)CM音頻編碼為AAC音頻,但是不能將AAC音頻解碼回PCM音頻,是為什么導(dǎo)致的
    發(fā)表于 06-05 06:39

    鴻蒙開發(fā)接口媒體:【@ohos.multimedia.audio (音頻管理)】

    音頻管理提供管理音頻的一些基礎(chǔ)能力,包括對(duì)音頻音量、音頻設(shè)備的管理,以及對(duì)音頻數(shù)據(jù)的采集和渲染
    的頭像 發(fā)表于 05-31 09:53 ?2521次閱讀
    鴻蒙開發(fā)接口媒體:【@ohos.multimedia.<b class='flag-5'>audio</b> (<b class='flag-5'>音頻</b>管理)】

    【RTC程序設(shè)計(jì):實(shí)時(shí)音視頻權(quán)威指南】音頻采集與渲染

    在進(jìn)行視頻的采集與渲染的同時(shí),我們還需要對(duì)音頻進(jìn)行實(shí)時(shí)的采集和渲染。對(duì)于rtc來說,音頻的實(shí)時(shí)性和流暢性更加重要。 聲音是由于物體在空氣中振動(dòng)而產(chǎn)生的壓力波,聲波的存在依賴于空氣介質(zhì),
    發(fā)表于 04-28 21:00

    【開源鴻蒙】下載OpenHarmony 4.1 Release源代碼

    本文介紹了如何下載開源鴻蒙(OpenHarmony)操作系統(tǒng) 4.1 Release版本的源代碼,該方法同樣可以用于下載OpenHarmony最新開發(fā)版本(master分支)或者4.0 Release、3.2 Release等發(fā)
    的頭像 發(fā)表于 04-27 23:16 ?928次閱讀
    【開源鴻蒙】下載<b class='flag-5'>OpenHarmony</b> 4.1 Release源代碼

    鴻蒙ArkUI開發(fā)學(xué)習(xí):【渲染控制語法】

    ArkUI開發(fā)框架是一套構(gòu)建 HarmonyOS / OpenHarmony 應(yīng)用界面的聲明式UI開發(fā)框架,它支持程序使用?`if/else`?條件渲染,?`ForEach`?循環(huán)渲染以及?`LazyForEach`?懶加載
    的頭像 發(fā)表于 04-09 16:40 ?1006次閱讀
    鴻蒙ArkUI開發(fā)學(xué)習(xí):【<b class='flag-5'>渲染</b>控制語法】

    潤開鴻全場(chǎng)景應(yīng)用開發(fā)實(shí)訓(xùn)平臺(tái)通過OpenHarmony兼容性測(cè)評(píng)

    近日,江蘇潤開鴻數(shù)字科技有限公司(以下簡稱“潤開鴻”)基于OpenHarmony的全場(chǎng)景應(yīng)用開發(fā)實(shí)訓(xùn)平臺(tái)通過OpenHarmony 3.2. Release版本兼容性測(cè)評(píng),為高校開展
    的頭像 發(fā)表于 01-22 10:08 ?719次閱讀

    潤開鴻基于OpenHarmony的全場(chǎng)景應(yīng)用開發(fā)實(shí)訓(xùn)平臺(tái)通過兼容性測(cè)評(píng)

    近日,江蘇潤開鴻數(shù)字科技有限公司(以下簡稱“潤開鴻”)基于OpenHarmony的全場(chǎng)景應(yīng)用開發(fā)實(shí)訓(xùn)平臺(tái)通過OpenHarmony3.2.Release版本兼容性測(cè)評(píng),為高校開展
    的頭像 發(fā)表于 01-20 08:02 ?561次閱讀
    潤開鴻基于<b class='flag-5'>OpenHarmony</b>的全場(chǎng)景應(yīng)用開發(fā)實(shí)訓(xùn)平臺(tái)通過兼容性測(cè)評(píng)

    鴻蒙開發(fā)OpenHarmony組件復(fù)用案例

    概述 在開發(fā)應(yīng)用時(shí),有些場(chǎng)景下的自定義組件具有相同的組件布局結(jié)構(gòu),僅有狀態(tài)變量等承載數(shù)據(jù)的差異。這樣的組件緩存起來,需要使用到該組件時(shí)直接復(fù)用, 減少重復(fù)創(chuàng)建和渲染的時(shí)間,從而提高應(yīng)用頁面的加載速度
    發(fā)表于 01-15 17:37

    藍(lán)牙m(xù)idi和藍(lán)牙音頻或者藍(lán)牙audio有什么區(qū)別呢

    藍(lán)牙m(xù)idi和藍(lán)牙音頻或者藍(lán)牙audio有什么區(qū)別呢 首先這里分為三個(gè)概念,也就是什么是藍(lán)牙?什么是藍(lán)牙m(xù)idi?什么是藍(lán)牙音頻audio? 1、什么是藍(lán)牙,這個(gè)就不用贅述了,大家
    的頭像 發(fā)表于 01-09 15:22 ?1190次閱讀
    藍(lán)牙m(xù)idi和藍(lán)牙<b class='flag-5'>音頻</b>或者藍(lán)牙<b class='flag-5'>audio</b>有什么區(qū)別呢

    OpenHarmony Sheet 表格渲染引擎

    基于 Canvas 實(shí)現(xiàn)的高性能 Excel 表格引擎組件 [OpenHarmonySheet]。 由于大部分前端項(xiàng)目渲染層是使用框架根據(jù)排版模型樹結(jié)構(gòu)逐層渲染的,整棵渲染樹也是與排版
    發(fā)表于 01-05 16:32