2023年11月27日发(作者:)

1 OpenCore概述

OpenCore的(又称PacketVideo,它是Android的多媒体核心,是一个多媒体

的框架。OpenCore的代码非常庞大,它是一个基于C++的实现,定义了全功能的操作系

统移植层,各种基本的功能均被封装成类的形式,各层次之间的接口多使用继承等方式。

宏观上来看,它主要包含了两大方面的内容:

PVPlayer:提供媒体播放器的功能,完成各种音频(Audio)、视频(Video)流的回放

(Playback)功能

PVAuthor:提供媒体流记录的功能,完成各种音频(Audio)、视频(Video)流的以及静

态图像捕获功能

PVPlayerPVAuthorSDK的形式提供给开发者,可以在这个SDK之上构建多种

应用程序和服务。在移动终端中 常常使用的多媒体应用程序,例如媒体播放器、照相机、

录像机、录音机等等。

为了更好的组织整体的架构,OpenCore在软件层次在宏 观上分成几个层次:

OSCLOperating System Compatibility Library (操作系统兼容库),包含了一些

操作系统底层的操作,为了更好地在不同操作系统移植。包含了基本数据类型、配置、字符

串工具、IO、错误处理、线程等内容,类似一个基础的C++库。

PVMFPacketVideo Multimedia Framework(PV多媒体框架),在框架内实现一个

文件解析(parser)和组成(composer)、编解码的NODE,也可以继承其通用的接口,在

用户层实现一些NODE

PVPlayer EnginePVPlayer引擎。

PVAuthor EnginePVAuthor引擎。

事实上,OpenCore中包含的内容非常多:

从播放的角度,PVPlayer的输入的(Source)是文件或者网络媒体流,输出(Sink)是音

频视频的输出设备,其基本功能包含了媒体流控制、文件解析、音频视频流的解码(Decode)

等方面的内容。

除了从文件中播放媒体文件之外,还包含了与网络相关的RTSP(Real Time Stream

Protocol,实时流协议)

在媒体流记录的方面,PVAuthor的输入的(Source)是照相机、麦克风等设备,输出

(Sink)是各种文件, 包含了流的同步、音频视频流的编码(Encode)以及文件的写入等功

能。

在使用OpenCoreSDK的时候,有可能需要在应用程序层实现一个适配器

(Adaptor),然后在适配器之上实现具体的功能,对于PVMFNODE也可以基于通用的

接口,在上层实现,以插件的形式使用。

2 OpenCore的代码结构

2.1》代码结构

OpenCore的代码在以下目录中:external/opencore/。这个目录是OpenCore的根目

录,其中包含的子目录如下所示:

android:这里面是一个上层的库,它基于PVPlayerPVAuthorSDK实现了一

个为Android使用的PlayerAuthor

baselibs:包含数据结构和线程安全等内容的底层库

build_config:编译配置

codecs_v2:这是一个内容较多的库,主要包含编解码的实现,以及一个OpenMAX

的实现

doc:文档

engines:包含PVPlayerPVAuthor引擎的实现

extern_libs_v2:包含了khronosOpenMAX的头文件

extern_tools_v2:扩展工具

fileformats:文件格式的解析 (parser)工具

modules:一些已经实现的模块,可以注册

nodes:提供一些PVMFNODE,主要是编解码和文件解析方面的。

oscl:操作系统兼容库

protocols:主要是与网络相关的RTSPRTPHTTP等协议的相关内容

pvmi:输入输出控制的抽象接口

tools_v2:编译工具以及一些可注册的模块。

external/opencore/目录中还有2个文件,如下所示:

:全局的编译文件

pvplayer.cfg:配置文件

2.2》编译结构

2.2.1》库的层次关系:

AndroidOpenCore编译出来的各个库如下所示:

libopencore_author.soOpenCore Author

libopencore_OpenCore底层的公共库

libopencore_:下载功能实现库

libopencore_ :下载注册库

libopencore_MP4本地功能实现库

libopencore_MP4本地注册库

libopencore_net_:网络支持库

libopencore_player.soOpenCore Player

libopencore_RTSP功能实现库

libopencore_RTSP注册库

这些库的层次关系如下图所示:

OpenCore的各个库之间具有如下的关系:

libopencore_是所有的库的依赖库,提供了公共的功能;

libopencore_player.so libopencore_author.so是两个并立的库,分别用于回放

和记录,而且这两个库是OpenCore对外的接口库;

libopencore_net_提供网络支持的功能;

一些功能以插件(Plug-In)的方式放入Player中使用,每个功能使用两个库,一个实现具

体功能,一个用于注册。

2.2.2libopencore_库的结构

libopencore_是整个OpenCore的核心库,它的编译控制的文件的路径为

/build_config/opencore_dynamic/Android_opencore_(具体内容见此

文件),这个文件使用递归的方式寻找子文件。在libopencore_库中包含了

以下内容:

OSCL的所有内容(oscl/oscl/

Pvmf框架部分的内容(pvmi/pvmf/

基础库中的一些内容(baselibs

编解码的一些内容(codecs_v2

文件输出的nodenodes/pvfileoutputnode/

从库的结构中可以看出,最终生成库的结构与OpenCore的层次关系并非完全重合。

libopencore_库中就包含了底层的OSCL的内容、PVMF的框架,以及Node

和编解码的工具。

2.2.3libopencore_player.so库的结构

libopencore_player.so

/build_config/opencore_dynamic/Android_opencore_(具体内容见此文

件)libopencore_player.so中包含了以下内容:

一些解码工具

文件的解析器(fileformats/...

解码工具对应的Node

player的引擎部分 engines/player/

Androidplayer适配器 engines/adapters/player/...

识别工具(pvmi/recognizer

对应几个插件Node的注册

libopencoreplayer.so中的内容较多,其中主要为各个文件解析器,PVPlayer的核心

功能在engines/player/当中。

2.2.4libopencore_author.so库的结构

libopencore_author.so是用于媒体流记录的功能库,它的编译控制的文件的路径为

/build_config/opencore_dynamic/Android_opencore_author.mk(具体内容见此文

件)libopencoreauthor.so 中包含了以下内容:

文件的组成器(mp4

编码工具对应的Node

author 的引擎部分(engines/author/

Androidauthor适配器(android/author/

libopencoreauthor.so PVAuthor

engines/author/当中,而android/author/是在PVAuthor

之上构建的一个为Android使用的媒体记录器。

2.2.5》其他库

另外的几个库的都在/build_config/opencore_dynamic/文件路径下,

如下所示:

libopencore_net_

Android_opencore_net_

MP4libopencore_

libopencore_

Android_opencore_

Android_opencore_

RTSP功能实现库libopencore_和注册库libopencore_

Android_opencore_

Android_opencore_

libopencore_

libopencore_

Android_opencore_

Android_opencore_

2.3》文件格式处理和编解码部分

在多媒体方面,文件格式的处理和编解码(Codec)是很基础的两个方面的内容。多媒体

应用的两个方面是媒体的播放(PlayBack)和媒体的记录(Recording)

在媒体的播放过程中,通常情况是从对媒体文件的播放,必要的两个步骤为文件的解析

和媒体流的解码。例如对于一个mp4的文件,其中可能包括AMRAAC的音频流,H263

MPEG4以及AVC(H264)的视频流,这些流被封装在3GP的包当中,媒体播放器要做的

就是从文件中将这些流解析出来,然后对媒体流进行解码,解码后的数据才可以播放。

在媒体的记录过程中,通过涉及到视频、音频、图像的捕获功能。对于将视频加音频录

制成文件功能,其过程与播放刚好相反,首先从硬件设备得到视频和音频的媒体流,然后对

其进行编码,编码后的流还需要被分层次写入到文件之中,最终得到组成好的文件。

OpenCore有关文件格式处理和编解码部分两部分的内容,分别在目录fileformats

codecs_v2 当中。这两部分都属于基础性的功能,不涉及具体的逻辑,因此它们被别的模

块调用来使用。

文件格式的处理:由于同时涉及播放文件和记录文件两种功能,因此OpenCore中的

文件格式处理有两种类型,一种是parser(解析器),另一种是composer(组成器),具体

fileformats目录结构下内容。

编解码:编解码部分主要针对AudioVideo具体见codecs_v2的目录结构下内容。

audio video目录中,对应了针对各种流的子目录,其中包含decenc两个目录,

分别对应解码和编码。 子目录omx实现了一个khronos

3 PVPlayer引擎设计

PVPlayer引擎是播放器的核心,是一个标准架构,结构灵活的、可扩展的,轻巧的多媒

体播放框架。。它接收和处理所有PVPlayer SDK从用户和管理PVMF播放多媒体所需的

组成及相关业务的请求,其任务应用和简化高级控制。这个PVPlayer引擎也侦测,处理,

过滤事件和信息生成多媒体播放操作控制。包括媒体信息的层次结构、流程的控制和数据流

图、状态机、错误处理、异步事件的处理,以及用例场景。引擎使用PVMF节点和节点图

表数据处理和内部注册时,节点的图形结构。下图是PVPlayer SDK简单框架图。

3.1》控制流

回放控制PVPlayer SDK是一般开发者的PVPlayer,典型的LINUX应用程序,通过BINDLE

一个服务给JAVA层调用。PVPlayer SDK api提供了如调用初使化、setdatasourse

prepare开始(停止)多媒体播放等等。PVPlayer SDK控制流量通常是自上而下的。

这个应用程序请求被PVPlayer收到后经引擎适配。这个PVPlayer引擎根据先前注册的节

点并通过相应的标识控制PVMF节点数据。有一些控制数据连接节点之间,但主要的控制数

PVPlayer引擎和PVMF节点之间。

3.2》数据流

PVPlayer SDK多媒体数据流过程的通过一个或更多的PVMF节点连接在一起。PVMF

节点类型使用和结构将取决于回放文件源参数的类型进行相应播放操作。文件类型主要是提

取文件元数据的具体参数,PVPlayer引擎或PVMF提取节点PVPlayer SDK通过适当的

接口返回给用户。

3.3PVPlayerInterface API

PVPlayer用户界面PVPlayer引擎通过PVPlayerInterface接口类来确定是否有一种

适配接口用户和PVPlayer引擎。PVPlayerInterface是一种OSCL-based接口和遵循公

共接口,除了多媒体播放特定api,PVPlayerInterface提供方法检索SDK信息、操纵和取

消的命令。并描述PVPlayerInterface API,指的是一种PVPlayerInterface API文档生成

的支持的标记。

3.4》引擎框架

下面的图表说明了应用程序使用PVPlayer引擎的接口PVPlayerInterface时直接适

配。PVPlayerFactory处理实例化组件和destoryPVPlayerEngine对象。所有

PVPlayer引擎的api提供PVPlayerInterfacePVPlayerEngine采用三种回收处理过的

,PVCommandStatusObserver,PVInformationalObserver,PVErrorEventObserve

r,通知申请上述指令完成同步误差和信息的事件。

3.5》状态机 PVPlayer 引擎实例化后状态为IDLE,在IDLE状态时,可以调

AddDataSource()来指定需要回放的多媒体数据,然后调用init()初始化数据并且状态

转为INITIALIZED,在进入INITIALIZED状态的时候,用户可以获取媒体的tracks

metadata,并且可以调用 AddDataSink()去指定具体的data sinks去回放。

在所有的data sinks添加后,用户调用Prepare(),使引擎建立相关的PVMF节点,

并为数据流指定多媒体播放的数据源和数据接收器,建立需要播放的数据流队列。用户在

PREPARED状态时调用Start()进入到STARTED状态,启动多媒体播放。在调用Stop()

后回到INITIALIZED状态并且刷新多媒体数据流。

STARTED状态时,用户也可以调用 Pause()Stop()。调用Stop()后停止回放,

刷新所有数据流,并且使引擎回到初始化状态。调用Pause()会停止回放,但不会刷新数

据流,而且可以调用Resume()继续从暂停的地方播放。PAUSED状态时可以调用Stop()

使引擎回到初始化状态。

调用Stop()回到INITIALIZED状态后,数据队列可以通过调用AddDataSink()

RemoveDataSink()来添加和删除。在调用Prepare()Start()重新回放,但是关闭回到

IDLE状态时,或需要重新打开另一个媒体文件时,需要调用Reset()。因为在IDLE状态

调用RemoveDataSink()不能删除所有数据队列。调用Reset()后,又回到起始状态,流

程如上一样。如果用户想退出PVPlayer也可以调用Reset()PREPARED STARTED

PAUSED状态时都可以调用该函数。

如果PVPlayer引擎收到错误信息或从其它组件传来错误事件后,引擎会发送ERROR

状态并且尝试恢复。如果这个错误是不可恢复的,刚引擎会清空所有状态和数据列回到IDLE

状态。此时用户应该等待PVMFInfoErrorHandlingComplete informational event

4 OpenCore OSCL

OSCL,全称为Operating System Compatibility Library (操作系统兼容库),它包

含了一些在不同操作系统中移植层的功能,其代码结构如下所示:

oscl/oscl

|-- config :配置的宏

|-- osclbase :包含基本类型、宏以及一些STL容器类似的功能

|-- osclerror :错误处理的功能

|-- osclio :文件IOSocket等功能

|-- oscllib :动态库接口等功能

|-- osclmemory :内存管理、自动指针等功能

|-- osclproc :线程、多任务通讯等功能

|-- osclregcli :注册客户端的功能

|-- osclregserv :注册服务器的功能

`-- osclutil :字符串等基本功能

oscl的目录中,一般每一个目录表示一个模块。OSCL对应的功能是非常细致的,

几乎对C语言中每一个细节的功能都进行封装,并使用了C++的接口提供给上层使用。事

实上,OperCore中的PVMFEngine部分都在使用OSCL,而整个OperCore的调用者

也需要使用OSCL

PVPlayer的接口是标准的-based 接口,引擎的API都是通过这些接口来调用相对应

的操作,但是像一些OSCL类型的组件和PVMF类型的组件里需要注意有些不同。

4.1》适配层

4.2》多线程支持

默认的OSCL-based 接口不支持多线程,所以在使用多线程的时候,适配层需要提供

一个功能来实现。一种方法是使用OSCL proxy 接口组件来提供多线程的支持,另一种方

法是添加一个另外的Platform Threading Support。两种方法的框图如下:

4.3》媒体数据输出到数据池

PVPlayer引擎利用PVMF节点指定数据输出的数据池,但大部份使用的时候,同步数

据通过适合的渲染后再送到媒体输出设备。在输出视频流的时候,需要一个显示设备,音频

流需要一个PCM 音频输出设备。媒体输出设备是一个具体的特定平台.输出设备的创建有

两个方法可以实现:

(1)PVMF节点里封装一个media device,引擎可以直接调用,这种方法可以最大

限度的减少PVPlayer和接口层的代码,但是需要创建一个新的PVMF节点。

(2)直接调用PV media I/O接口,在I/O接口中调用输出设备。这种方法相对于第一

种来说并不需要创建新的PVMF节点,复杂性也小。但增加了代码的层次和和代码量。下

图是两种方法的引擎。

5 OpenCoreA/V同步机制

PVPlayer 在渲染 (render) 所有多媒体数据是都需要保持一个暂时的同步,也就是通

常所说的 A/V 同步。为了达到同步,需要一些信息:媒体回放的时钟,媒体数据的时间戳,

Sink 中获取的时间信息(如从音频设备设定的特定的采样率来获取的播放速率)。下图

描述了与同步相关的 PVPlayer 模块之间的关系。

5.1》媒体时钟

PVMFMediaClock:媒体时钟主要负责维持一个时间的引用,从而保持媒体回放的节奏,

获取和实现媒体播放的同步。 媒体时钟的具体以下特点:

时间源:媒体时钟可以作为一个时间源提供给多媒体,它本身可能来自于系统时钟或其

他时间源(比如音频设备时钟)。它可以给多媒体提供一个时间基准,同时来维护该时间基

准。

时钟观察者:媒体时钟可以把自己作为一个观察者,来通知对象时钟状态的改变。以下

接口实现了其作为观察者的角色:

PVMFMediaClockObserver:用来通知时钟基值,时钟计数的更新,时钟的调整;

PVMFMediaClockStateObserver:用来通知时钟状态的改变;

PVMFMediaClockNotificationsObs:用来获取回调通知。

时钟的回调:在媒体时钟上设置回调是组件采取动作的基础,这些回调可以减少在时钟

发生改变时,组件自己需要设置他们的时钟。媒体时钟采用输入特定时间窗口来取代绝对时

间,这样可以使得处于竞争状态的任务或线程可以尽可能早的得到响应。

延迟处理:当集成了多个不同媒体流的Sinks来输出一个多媒体时,每个Sink都可能

会有不同程度的延迟,为了弥补不同媒体流之间的延迟从而同步播放,就需要进行延迟处理。

每个 Sink 都向媒体时钟注册自己的延迟,最后由媒体时钟来调整最终的调度的延迟。

NPT 映射:媒体时钟是一个单调递增的时钟,而媒体在播放时却可能需要 Seek 到任

意位置,为了控制媒体的播放,使其正确 Render 媒体时钟需要在媒体时钟时间和 NPT

之间维护一个 NPT normal play time )映射,任意对媒体播放位置的改变将会通知

进行一次映射。

NPT 时钟转换:当一个新的NPT开始时,用户可以给媒体时间设置一个绝对时间。用

户还可以任意调整 NPT 的方向(比如向前,向后)

5.2》时间戳

为了及时准确地输出媒体数据,就不得不考虑媒体数据中包含的时间戳信息、以及媒体回放

时钟。

如果时间戳值等于当前回放时间,则媒体数据是同步的,需要进行Render

如果时间戳小于当前回放时间,则说明媒体数据到达时间晚了;反之,如果时间戳大于

当前回放时间,则说明媒体数据到达时间早了。

如何处理这些来早的或来晚的媒体数据则取决于PVPlayer引擎的配置,通常情况下,来早

的数据需要等待,直到播放时间到达;来晚的数据则会被丢弃而不被Render。但有时候来

晚的数据也会被Render

5.3》同步音频

音频数据的Render通常不需要外部时钟来进行同步,因为音频设备通常会被配置一定的采

样率来消化音频数据。因此,音频设备被配置的这个采样率通常也作为媒体回放时钟的速率。

Render开始时的同步:一旦媒体时钟开始后,就必须要求媒体数据尽可能快地被

Render然而硬件在 Render时很可能需要额外的时间,或者硬件需要等到更多的媒体数

据被缓存。因此会导致媒体时钟的开始时间与媒体数据真正被输出的时间不一致,从而导致

PVPlayer报告给应用程序的播放进度与真实播放进度产生误差。为了解决这一问题,采用

了在硬件没有开始输出媒体数据时,媒体时钟就处于暂停状态,当硬件开始输出媒体数据时

给媒体时钟发一个消息来通知媒体时钟也开始运行。这样就可以保证在媒体时钟开始的时

候,媒体数据也开始进行输出。

重新定位后的同步:当应用程序请求重新定位媒体播放位置时,MIO组件和硬件缓存的

数据需要被立即释放,并且在新的位置开始播放。

播放中的同步:尽管硬件消费数据的速率应该与媒体时钟的速率是一致的,但毕竟他们

是单独运行的,因此不可避免的会有一些不同。通常情况下,这中差距是很小的,然而随着

播放的进行,差距将会被积累,最终导致不同步。

因此,为了控制在短时间内播放时钟与音频输出进程差距在很小的范围内,需要不时地调整

两者之间的差,使之小于一个特定的阈值。

5.4》同步视频

与音频输出相比,视频的输出需要参考一个时钟来决定何时输出一个特定的视频帧,视频帧

的输出要尽可能的与该帧的时间戳相一致。PVPlayer维护了一个与音频播放同步的播放时

钟,因此,一旦视频输出与播放时钟同步,那么也就意味着视频输出与音频输出同步。

5.5》音视频同步

音视频同步是音频同步和视频同步的终极目标。PVPlayer架构中,媒体时钟需要调整以

便与音频输出过程相一致,而对于视频输出来说,在输出一个视频帧时要使得该帧的时间戳

与媒体时钟同步。因此音频输出设备、视频帧的时间戳与媒体时钟的结合便造就了 A/V

步。下图描述了参与音视频同步模块之间的交互过程。

从技术上来说,解决音视频同步问题的最佳方案就是时间戳:

首先选择一个参考时钟(要求参考时钟上的时间是线性递增的);生成数据流时依据参

考时钟上的时间给每个数据块都打上时间戳(一般包括开始时间和结束时间);在播放时,

读取数据块上的时间戳,同时参考当前参考时钟上的时间来安排播放(如果数据块的开始时

间大于当前参考时钟上的时间,则不急于播放该数据块,直到参考时钟达到数据块的开始时

间;如果数据块的开始时间小于当前参考时钟上的时间,尽快播放这块数据或者索性将

这块数据丢弃,以使播放进度追上参考时钟)

避免音视频不同步现象有两个关键:

一是在生成数据流时要打上正确的时间戳。如果数据块上打的时间戳本身就有问题,那

么播放时再怎么调整也于事无补。打时间戳时,视频流和音频流都是参考参考时钟的时间,

而数据流之间不会发生参考关系;也就是说,视频流和音频流是通过一个中立的第三方(也

就是参考时钟)来实现同步的。

第二个关键的地方,就是在播放时基于时间戳对数据流的控制,也就是对数据块早到或

晚到采取不同的处理方法。

基于时间戳的播放过程中,仅仅对早到的或晚到的数据块进行等待或快速处理,有时候是不

够的。如果想要更加主动并且有效地调节播放性能,需要引入一个反馈机制,也就是要将当

前数据流速度太快或太慢的状态反馈给,让源去放慢或加快数据流的速度。