OpenHarmony 3.2 Beta多媒體系列——音影片播放框架

OpenHarmony開發者社群發表於2022-11-22


一、簡介

媒體子系統為開發者提供一套介面,方便開發者使用系統的媒體資源,主要包含音影片開發、相機開發、流媒體開發等模組。每個模組都提供給上層應用對應的介面,本文會對音影片開發中的音影片播放框架做一個詳細的介紹。

二、目錄

foundation/multimedia/media_standard

├── frameworks                        #框架程式碼
│   ├── js
│   │   ├── player
│   ├── native
│   │   ├── player                    #native實現
│   └── videodisplaymanager         #顯示管理
│       ├── include
│       └── src
├── interfaces
│   ├── inner_api                     #內部介面
│   │   └── native
│   └── kits                          #外部JS介面
├── sa_profile                        #服務配置檔案
└── services
    ├── engine                        #engine程式碼
    │   └── gstreamer
    ├── etc                           #服務配置檔案
    ├── include                       #標頭檔案
    └── services
        ├── sa_media                  #media服務
        │   ├── client                #media客戶端
        │   ├── ipc                   #media ipc呼叫
        │   └── server                #media服務端
        ├── factory                   #engine工廠
        └── player                    #player服務
           ├── client                 #player客戶端
           ├── ipc                    #player ipc呼叫
           └── server                 #player服務端

三、播放的總體流程

undefined

四、Native介面使用

OpenHarmony系統中,音影片播放透過N-API介面提供給上層JS呼叫,N-API相當於是JS和Native之間的橋樑,在OpenHarmony原始碼中,提供了C++直接呼叫的音影片播放例子,在foundation/multimedia/player_framework/test/nativedemo/player目錄中。

void PlayerDemo::RunCase(const string &path)
{
    player_ = OHOS::Media::PlayerFactory::CreatePlayer();
    if (player_ == nullptr) {
        cout << "player_ is null" << endl;
        return;
    }
    RegisterTable();
    std::shared_ptr<PlayerCallbackDemo> cb = std::make_shared<PlayerCallbackDemo>();
    cb->SetBufferingOut(SelectBufferingOut());
    int32_t ret = player_->SetPlayerCallback(cb);
    if (ret != 0) {
        cout << "SetPlayerCallback fail" << endl;
    }
    if (SelectSource(path) != 0) {
        cout << "SetSource fail" << endl;
        return;
    }
    sptr<Surface> producerSurface = nullptr;
    producerSurface = GetVideoSurface();
    if (producerSurface != nullptr) {
        ret = player_->SetVideoSurface(producerSurface);
        if (ret != 0) {
            cout << "SetVideoSurface fail" << endl;
        }
    }
    SetVideoScaleType();
    if (SelectRendererMode() != 0) {
        cout << "set renderer info fail" << endl;
    }
    ret = player_->PrepareAsync();
    if (ret !=  0) {
        cout << "PrepareAsync fail" << endl;
        return;
    }
    cout << "Enter your step:" << endl;
    DoNext();
}

首先根據RunCase可以大致瞭解一下播放音影片的主要流程,建立播放器,設定播放源,設定回撥方法(包含播放過程中的多種狀態的回撥),設定播放顯示的Surface,這些準備工作做好之後,需要呼叫播放器的PrepareASync方法,這個方法完成後,播放狀態會變成Prepared狀態,這時就可以呼叫播放器的play介面,進行音影片的播放了。

RegisterTable()方法中,將字串和對應的方法對映到Map中,這樣後續的DoNext會根據輸入的命令,來決定播放器具體的操作。

void PlayerDemo::DoNext()
{
    std::string cmd;
    while (std::getline(std::cin, cmd)) {
        auto iter = playerTable_.find(cmd);
        if (iter != playerTable_.end()) {
            auto func = iter->second;
            if (func() != 0) {
                cout << "Operation error" << endl;
            }
            if (cmd.find("stop") != std::string::npos && dataSrc_ != nullptr) {
                dataSrc_->Reset();
            }
            continue;
        } else if (cmd.find("quit") != std::string::npos || cmd == "q") {
            break;
        } else {
            DoCmd(cmd);
            continue;
        }
    }
}
void PlayerDemo::RegisterTable()
{
    (void)playerTable_.emplace("prepare", std::bind(&Player::Prepare, player_));
    (void)playerTable_.emplace("prepareasync", std::bind(&Player::PrepareAsync, player_));
    (void)playerTable_.emplace("", std::bind(&Player::Play, player_)); // ENTER -> play
    (void)playerTable_.emplace("play", std::bind(&Player::Play, player_));
    (void)playerTable_.emplace("pause", std::bind(&Player::Pause, player_));
    (void)playerTable_.emplace("stop", std::bind(&Player::Stop, player_));
    (void)playerTable_.emplace("reset", std::bind(&Player::Reset, player_));
    (void)playerTable_.emplace("release", std::bind(&Player::Release, player_));
    (void)playerTable_.emplace("isplaying", std::bind(&PlayerDemo::GetPlaying, this));
    (void)playerTable_.emplace("isloop", std::bind(&PlayerDemo::GetLooping, this));
    (void)playerTable_.emplace("speed", std::bind(&PlayerDemo::GetPlaybackSpeed, this));
}

以上的DoNext方法中核心的程式碼是func()的呼叫,這個func就是之前註冊進Map中字串對應的方法,在RegisterTable方法中將空字串""和"play"對繫結為Player::Play方法,預設不輸入命令引數時,是播放操作。

五、呼叫流程

undefined

本段落主要針對媒體播放的框架層程式碼進行分析,所以在流程中涉及到了IPC呼叫相關的客戶端和服務端,程式碼暫且分析到呼叫gstreamer引擎。首先Sample透過PlayerFactory建立了一個播放器例項(PlayerImpl物件),建立過程中呼叫Init函式。

int32_t PlayerImpl::Init()
{
    playerService_ = MediaServiceFactory::GetInstance().CreatePlayerService();
    CHECK_AND_RETURN_RET_LOG(playerService_ != nullptr, MSERR_UNKNOWN, "failed to create player service");
    return MSERR_OK;
}

MediaServiceFactory::GetInstance()返回的是MediaClient物件,所以CreateplayerService函式實際上是呼叫了MediaClient對應的方法。

std::shared_ptr<IPlayerService> MediaClient::CreatePlayerService()
{
    std::lock_guard<std::mutex> lock(mutex_);
    if (!IsAlived()) {
        MEDIA_LOGE("media service does not exist.");
        return nullptr;
    }
    sptr<IRemoteObject> object = mediaProxy_->GetSubSystemAbility(
        IStandardMediaService::MediaSystemAbility::MEDIA_PLAYER, listenerStub_->AsObject());
    CHECK_AND_RETURN_RET_LOG(object != nullptr, nullptr, "player proxy object is nullptr.");
    sptr<IStandardPlayerService> playerProxy = iface_cast<IStandardPlayerService>(object);
    CHECK_AND_RETURN_RET_LOG(playerProxy != nullptr, nullptr, "player proxy is nullptr.");
    std::shared_ptr<PlayerClient> player = PlayerClient::Create(playerProxy);
    CHECK_AND_RETURN_RET_LOG(player != nullptr, nullptr, "failed to create player client.");
    playerClientList_.push_back(player);
    return player;
}

這個方法中主要透過PlayerClient::Create(playerProxy)方法建立了PlayerClient例項,並且將該例項一層層向上傳,最終傳給了PlayerImpl的playerService_變數,後續對於播放器的操作,PlayerImpl都是透過呼叫PlayerClient例項實現的。

int32_t PlayerImpl::Play()
{
    CHECK_AND_RETURN_RET_LOG(playerService_ != nullptr, MSERR_INVALID_OPERATION, "player service does not exist..");
    MEDIA_LOGW("KPI-TRACE: PlayerImpl Play in");
    return playerService_->Play();
}
int32_t PlayerImpl::Prepare()
{
    CHECK_AND_RETURN_RET_LOG(playerService_ != nullptr, MSERR_INVALID_OPERATION, "player service does not exist..");
    MEDIA_LOGW("KPI-TRACE: PlayerImpl Prepare in");
    return playerService_->Prepare();
}
int32_t PlayerImpl::PrepareAsync()
{
    CHECK_AND_RETURN_RET_LOG(playerService_ != nullptr, MSERR_INVALID_OPERATION, "player service does not exist..");
    MEDIA_LOGW("KPI-TRACE: PlayerImpl PrepareAsync in");
    return playerService_->PrepareAsync();
}

對於PlayerImpl來說,playerService_指向的PlayerClient就是具體的實現,PlayerClient的實現是透過IPC的遠端呼叫來實現的,具體地是透過IPC中的proxy端向遠端服務發起遠端呼叫請求。

我們以播放Play為例:

int32_t PlayerClient::Play()
{
    std::lock_guard<std::mutex> lock(mutex_);
    CHECK_AND_RETURN_RET_LOG(playerProxy_ != nullptr, MSERR_NO_MEMORY, "player service does not exist..");
    return playerProxy_->Play();
}
int32_t PlayerServiceProxy::Play()
{
    MessageParcel data;
    MessageParcel reply;
    MessageOption option;
    if (!data.WriteInterfaceToken(PlayerServiceProxy::GetDescriptor())) {
        MEDIA_LOGE("Failed to write descriptor");
        return MSERR_UNKNOWN;
    }
    int error = Remote()->SendRequest(PLAY, data, reply, option);
    if (error != MSERR_OK) {
        MEDIA_LOGE("Play failed, error: %{public}d", error);
        return error;
    }
    return reply.ReadInt32();
}

proxy端傳送呼叫請求後,對應的Stub端會在PlayerServiceStub::OnRemoteRequest接收到請求,根據請求的引數進行對應的函式呼叫。播放操作對應的呼叫Stub的Play方法。

int32_t PlayerServiceStub::Play()
{
    MediaTrace Trace("binder::Play");
    CHECK_AND_RETURN_RET_LOG(playerServer_ != nullptr, MSERR_NO_MEMORY, "player server is nullptr");
    return playerServer_->Play();
}

這裡最終是透過playerServer_呼叫Play函式。playerServer_在Stub初始化的時候透過PlayerServer::Create()方式來獲取得到。也就是PlayerServer。

std::shared_ptr<IPlayerService> PlayerServer::Create()
{
    std::shared_ptr<PlayerServer> server = std::make_shared<PlayerServer>();
    CHECK_AND_RETURN_RET_LOG(server != nullptr, nullptr, "failed to new PlayerServer");
    (void)server->Init();
    return server;
}

最終我們的Play呼叫到了PlayerServer的Play()。在媒體播放的整個過程中會涉及到很多的狀態,所以在Play中進行一些狀態的判讀後呼叫OnPlay方法。這個方法中發起了一個播放的任務。

int32_t PlayerServer::Play()
{
    std::lock_guard<std::mutex> lock(mutex_);
    if (lastOpStatus_ == PLAYER_PREPARED || lastOpStatus_ == PLAYER_PLAYBACK_COMPLETE ||
        lastOpStatus_ == PLAYER_PAUSED) {
        return OnPlay();
    } else {
        MEDIA_LOGE("Can not Play, currentState is %{public}s", GetStatusDescription(lastOpStatus_).c_str());
        return MSERR_INVALID_OPERATION;
    }
}
int32_t PlayerServer::OnPlay()
{
    auto playingTask = std::make_shared<TaskHandler<void>>([this]() {
        MediaTrace::TraceBegin("PlayerServer::Play", FAKE_POINTER(this));
        auto currState = std::static_pointer_cast<BaseState>(GetCurrState());
        (void)currState->Play();
    });
    int ret = taskMgr_.LaunchTask(playingTask, PlayerServerTaskType::STATE_CHANGE);
    CHECK_AND_RETURN_RET_LOG(ret == MSERR_OK, ret, "Play failed");
    lastOpStatus_ = PLAYER_STARTED;
    return MSERR_OK;
}

在播放任務中呼叫了PlayerServer::PreparedState::Play()

int32_t PlayerServer::PreparedState::Play()
{
    return server_.HandlePlay();
}

在Play裡面直接呼叫PlayerServer的HandlePlay方法,HandlePlay方法透過playerEngine_呼叫到了gstreamer引擎,gstreamer是最終播放的實現。

int32_t PlayerServer::HandlePlay()
{
    int32_t ret = playerEngine_->Play();
    CHECK_AND_RETURN_RET_LOG(ret == MSERR_OK, MSERR_INVALID_OPERATION, "Engine Play Failed!");
    return MSERR_OK;
}

六、總結

本文主要對OpenHarmony 3.2 Beta多媒體子系統的媒體播放進行介紹,首先梳理了整體的播放流程,然後對播放的主要步驟進行了詳細地分析。

媒體播放主要分為以下幾個層次:

(1) 提供給應用呼叫的Native介面,這個實際上透過

OHOS::Media::PlayerFactory::CreatePlayer()呼叫返回PlayerImpl例項。

(2) PlayerClient,這部分透過IPC的proxy呼叫,向遠端服務發起呼叫請求。

(3) PlayerServer,這部分是播放服務的實現端,提供給Client端呼叫。

(4) Gstreamer,這部分是提供給PlayerServer呼叫,真正實現媒體播放的功能。

undefined



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70011554/viewspace-2924465/,如需轉載,請註明出處,否則將追究法律責任。

相關文章