OpenHarmony 3.2 Beta多媒體系列——音影片播放框架
一、簡介
媒體子系統為開發者提供一套介面,方便開發者使用系統的媒體資源,主要包含音影片開發、相機開發、流媒體開發等模組。每個模組都提供給上層應用對應的介面,本文會對音影片開發中的音影片播放框架做一個詳細的介紹。
二、目錄
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服務端
三、播放的總體流程
四、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方法,預設不輸入命令引數時,是播放操作。
五、呼叫流程
本段落主要針對媒體播放的框架層程式碼進行分析,所以在流程中涉及到了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呼叫,真正實現媒體播放的功能。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70011554/viewspace-2924465/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- OpenHarmony 3.2 Beta多媒體系列——影片錄製
- OpenHarmony 3.2 Beta Audio——音訊渲染音訊
- Android多媒體之認識聲音、錄音與播放(PCM)Android
- OpenHarmony新音影片引擎——HiStreamer
- 鴻蒙ArkWeb 元件多媒體探究:從影片到音訊鴻蒙Web元件音訊
- [Android多媒體技術] 播放Raw/Assets音視訊方法總結Android
- 無外掛直播流媒體音影片播放器EasyPlayer.js播放器的g711系列的音訊,聽起來為什麼都是雜音播放器JS音訊
- Android 媒體播放框架MediaSession分析與實踐Android框架Session
- 多媒體播放器哪款值得入手?播放器
- OmniPlayer Pro for Mac(全能多媒體播放器)Mac播放器
- Flutter(十) 音訊+影片播放Flutter音訊
- GStreamer跨平臺多媒體框架框架
- OmniPlayer Pro for Mac(全能多媒體播放器)1.4.8Mac播放器
- Android多媒體之認識MP3與內建媒體播放(MediaPlayer)Android
- pygame播放影片並實現音影片同步GAM
- H.265流媒體播放器EasyPlayer.js H5流媒體播放器如何驗證影片播放是否走硬解播放器JSH5
- Android車載多媒體與MediaSession框架AndroidSession框架
- 自帶多媒體視訊播放器Infuse pro播放器
- clementine for Mac多平臺音樂管理播放軟體Mac
- 短影片播放量高的有什麼型別?新媒體短影片運營型別
- 開發板如何適配OpenHarmony 3.2
- 新媒體運營發展方向多嗎?影片新媒體剪輯軟體
- Fig Player for Mac(多媒體播放器) 1.3.7啟用版Mac播放器
- 支援M1、Infuse Pro for Mac「多媒體播放器」Mac播放器
- mac 終極多媒體卡拉OK播放器:QMidi ProMac播放器
- QMidi Pro for mac 終極多媒體卡拉OK播放器Mac播放器
- Android多媒體之視訊播放器(基於MediaPlayer)Android播放器
- 影片作品播放量低:自媒體作者如何走出新手村
- 黃吉——如何適配OpenHarmony自有音訊框架ADM?音訊框架
- Android 多媒體之 Silk 格式音訊解碼Android音訊
- 基於.Net 的 AvaloniUI 多媒體播放器方案彙總UI播放器
- 好用的多媒體播放器 Infuse Pro中文版最新播放器
- 音訊和影片無法在PowerPoint中播放音訊
- 流媒體平臺/影片監控/安防影片EasyCVR播放暫停後,影片畫面黑屏是什麼原因?VR
- 阿里雲影片雲 Retina 多媒體 AI 體驗館開張啦!阿里AI
- 【直播回顧】OpenHarmony知識賦能五期第三課——多媒體整體介紹
- 多媒體影片處理工具 ffmpeg 常用命令操作
- 多媒體播放器:Infuse Pro for Mac v7.6.1啟用版播放器Mac