OpenHarmony 3.2 Beta Audio——音訊渲染

OpenHarmony開發者發表於2023-03-02

一、簡介

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");
        }

        //關閉wavFile
        fclose(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例項。

  1. 設定回撥
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 callback
    if (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中也設定了渲染回撥。

  1. 啟動渲染
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庫我們暫且不分析。

  1. 寫入資料
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》

圖片

相關文章