一、简介
媒体子系统为开发者提供一套接口,不便开发者应用零碎的媒体资源,次要蕴含音视频开发、相机开发、流媒体开发等模块。每个模块都提供给下层利用对应的接口,本文会对音视频开发中的音视频播放框架做一个具体的介绍。
二、目录
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 函数。
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));
}
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 中进行一些状态的判读后调用 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 调用,真正实现媒体播放的性能。