写点什么

OpenHarmony 3.2 Beta 多媒体系列——音视频播放框架

  • 2022-11-22
    上海
  • 本文字数:5568 字

    阅读完需:约 18 分钟

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 调用,真正实现媒体播放的功能。


用户头像

OpenHarmony开发者官方账号 2021-12-15 加入

OpenHarmony是由开放原子开源基金会(OpenAtom Foundation)孵化及运营的开源项目,目标是面向全场景、全连接、全智能时代,基于开源的方式,搭建一个智能终端设备操作系统的框架和平台,促进万物互联产业的繁荣发展

评论

发布
暂无评论
OpenHarmony 3.2 Beta多媒体系列——音视频播放框架_OpenHarmony_OpenHarmony开发者社区_InfoQ写作社区