0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

OpenAtom OpenHarmony 来源:未知 2022-11-21 21:35 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

点击蓝字 ╳ 关注我们


开源项目 OpenHarmony
是每个人的 OpenHarmony

巴延兴

深圳开鸿数字产业发展有限公司

资深OS框架开发工程师


以下内容来自嘉宾分享,不代表开放原子开源基金会观点

一、简介

媒体子系统为开发者提供一套接口,方便开发者使用系统的媒体资源,主要包含音视频开发、相机开发、流媒体开发等模块。每个模块都提供给上层应用对应的接口,本文会对音视频开发中的音视频播放框架做一个详细的介绍。

二、目录

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::CreatePlayer();
if (player_ == nullptr) {
cout << "player_ is null" << endl;
return;
}
RegisterTable();
std::shared_ptr cb = std::make_shared();
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 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::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::npos && dataSrc_ != nullptr) {
dataSrc_->Reset();
}
continue;
} else if (cmd.find("quit") != std::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 MediaClient::CreatePlayerService()
{
std::lock_guard lock(mutex_);
if (!IsAlived()) {
MEDIA_LOGE("media service does not exist.");
return nullptr;
}


sptr object = mediaProxy_->GetSubSystemAbility(
IStandardMediaService::MEDIA_PLAYER, listenerStub_->AsObject());
CHECK_AND_RETURN_RET_LOG(object != nullptr, nullptr, "player proxy object is nullptr.");


sptr playerProxy = iface_cast(object);
CHECK_AND_RETURN_RET_LOG(playerProxy != nullptr, nullptr, "player proxy is nullptr.");


std::shared_ptr 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 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 PlayerServer::Create()
{
std::shared_ptr server = std::make_shared();
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 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>([this]() {
MediaTrace::TraceBegin("PlayerServer::Play", FAKE_POINTER(this));
auto currState = std::static_pointer_cast(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::Play()
int32_t PlayerServer::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::CreatePlayer()调用返回PlayerImpl实例。

(2)PlayerClient,这部分通过IPC的proxy调用,向远程服务发起调用请求。

(3)PlayerServer,这部分是播放服务的实现端,提供给Client端调用。

(4)Gstreamer,这部分是提供给PlayerServer调用,真正实现媒体播放的功能。





原文标题:OpenHarmony 3.2 Beta多媒体系列——音视频播放框架

文章出处:【微信公众号:OpenAtom OpenHarmony】欢迎添加关注!文章转载请注明出处。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 鸿蒙
    +关注

    关注

    60

    文章

    2857

    浏览量

    45346
  • OpenHarmony
    +关注

    关注

    31

    文章

    3926

    浏览量

    20712

原文标题:OpenHarmony 3.2 Beta多媒体系列——音视频播放框架

文章出处:【微信号:gh_e4f28cfa3159,微信公众号:OpenAtom OpenHarmony】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    音视频编解码封装解封装部件介绍

    是否有探索开源鸿蒙音视频编解码技术的欲望?是否对开源鸿蒙音视频编解码格式支持有诉求?别急——今天这份开源鸿蒙AVCodec Kit介绍文章,就是解答疑惑的指南!参考这份指南,可以使用开源鸿蒙的音视频编解码能力,进行应用开发;也可
    的头像 发表于 10-31 09:15 246次阅读
    <b class='flag-5'>音视频</b>编解码封装解封装部件介绍

    音视频开发全栈解析

    Media Kit 让开发者可以调用系统的音视频能力,涵盖播放、录制、录屏、元数据提取与转码六大核心功能模块,支撑系统应用与第三方应用在视频播放、内容创作、教学录屏、直播互动等典型场景
    的头像 发表于 09-18 14:42 566次阅读
    <b class='flag-5'>音视频</b>开发全栈解析

    一款面向便携式多媒体应用的高品质立体声数模转换器 (DAC) 芯片

    便携式多媒体播放器(Portable Media Player),简称PMP,是集音视频播放、图片浏览与数据存储功能于一体的手持设备。
    的头像 发表于 09-04 10:00 628次阅读
    一款面向便携式<b class='flag-5'>多媒体</b>应用的高品质立体声数模转换器 (DAC) 芯片

    花椒直播首次开源推流器组件 为鸿蒙开发者提供高性能推流解决方案

    器。该工具基于花椒直播自主通用多媒体框架HJMedia打造而来,采用创新的通用多媒体框架设计,以高度模块化、可扩展的插件化架构,极大提升了音视频
    的头像 发表于 09-04 09:52 510次阅读
    花椒直播首次开源推流器组件 为鸿蒙开发者提供高性能推流解决方案

    千视电子受邀亮相2025音视频产业链研讨会,引领全链路音视频IP化新趋势

    2025年8月22日,由中国电子音响行业协会和湖南省工业和信息化厅联合主办的“2025音视频产业链发展研讨暨对接会”在长沙举行。活动汇聚音视频产业链上下游企业,以及长沙市相关政府领导与行业
    的头像 发表于 08-27 11:33 1004次阅读
    千视电子受邀亮相2025<b class='flag-5'>音视频</b>产业链研讨会,引领全链路<b class='flag-5'>音视频</b>IP化新趋势

    泰芯半导体推出星闪音视频无线SOC芯片TXW828

    在短距离无线通信技术加速迭代的浪潮中,珠海泰芯半导体有限公司全球首先发布支持星闪(NearLink)标准的音视频无线SOC芯片——TXW828。这款集WiFi/蓝牙BLE/星闪三模融合音视频无线芯片
    的头像 发表于 06-20 15:51 2271次阅读

    乐鑫ESP-RTC实时音视频通信方案

    ESP-RTC乐鑫科技推出ESP-RTC(Real-TimeCommunication)音视频通信方案,能够实现稳定流畅、超低延时的语音和视频实时通信。ESP-RTC以乐鑫
    的头像 发表于 05-26 18:07 1238次阅读
    乐鑫ESP-RTC实时<b class='flag-5'>音视频</b>通信方案

    中国音视频产业迈向高质量发展

    随着超高清先锋计划的逐步推进,截至2024年底,中国音视频产业规模超5万亿,包括个人消费电子、家庭影音娱乐、行业多媒体应用及车载视听产业和相关衍生应用等行业企业超过百万家,音视频产业已经成为国家数字经济发展的重要支柱。
    的头像 发表于 04-24 11:10 1068次阅读

    音视频一体化解决方案

    。在应急指挥中心、多媒体会议室、移动指挥车、多媒体教室、数字法庭等场景中,用户亟需一套高度集成、智能管控、互联互通的一体化解决方案,以实现音视频信号的综合调度与高效协作。
    的头像 发表于 04-24 09:14 888次阅读

    实用调试技能:全志T113-i 音视频测试

    前言:音视频功能是现代嵌入式系统中的核心应用之一,尤其在全志T113-i开发板中,其丰富的音视频接口为开发者提供了强大的开发能力。本篇文章将带你快速掌握T113-i平台下音视频模块的调试技能,通过
    的头像 发表于 03-06 08:31 2244次阅读
    实用调试技能:全志T113-i <b class='flag-5'>音视频</b>测试

    【北京迅为】itop-3568 开发板openharmony鸿蒙烧写及测试-第2章OpenHarmony v3.2-Beta4版本测试

    【北京迅为】itop-3568 开发板openharmony鸿蒙烧写及测试-第2章OpenHarmony v3.2-Beta4版本测试
    的头像 发表于 03-05 10:53 880次阅读
    【北京迅为】itop-3568 开发板<b class='flag-5'>openharmony</b>鸿蒙烧写及测试-第2章<b class='flag-5'>OpenHarmony</b> v<b class='flag-5'>3.2-Beta</b>4版本测试

    RCA接口音视频传输的原理

    RCA接口音视频传输的原理主要基于模拟信号的传输方式。以下是对其传输原理的介绍: 一、RCA接口的基本结构 RCA接口,又称AV接口或莲花插座,其结构包括一个圆形的插头和一个相应的插座。插头上通常
    的头像 发表于 02-17 15:36 2275次阅读

    RK628H:高端音视频处理与传输芯片详解

    RK628H是一款集高清音视频处理与传输功能于一体的高端芯片,专为满足现代多媒体设备对高分辨率、高帧率视频以及高质量音频的需求而设计。其强大的音视频处理能力和多样化的输入输出接口,使得
    的头像 发表于 02-10 17:56 2023次阅读

    国科微荣获音视频领域关键技术突破一等奖

    近日,第五届“马栏山杯”国际音视频算法大赛-2024音视频领域关键技术突破奖揭晓,国科微8K超高清视频系列芯片凭借优异的产品性能及示范性应用获评一等奖。
    的头像 发表于 12-26 15:11 859次阅读

    RK3588核心板多媒体功能一览

    前言:RK3588支持8K视频编码和解码,以及多路视频源同时解码,为视频处理和多媒体应用提供了强大的支持,本文则从音视频两个功能测试上来认识
    的头像 发表于 12-12 08:31 3158次阅读
    RK3588核心板<b class='flag-5'>多媒体</b>功能一览