媒体知识杂谈

媒体杂谈和GStreamer

MPEG-4是一套用于音频视频信息的压缩编码标准,由国际标准化组织ISO)和国际电工委员会IEC)下属的“动态图像专家组”(Moving Picture Experts Group,即MPEG)制定,第一版在1998年10月通过,第二版在1999年12月通过。MPEG-4格式的主要用途在于网上光盘、语音传送(视频电话),以及电视广播

MPEG-4包含了MPEG-1MPEG-2的绝大部份功能及其他格式的长处,并加入及扩充对虚拟现实模型语言(VRML , Virtual Reality Modeling Language)的支持,面向对象的合成文件(包括音效,视频及VRML对象),以及数字版权管理DRM)及其他交互功能。而MPEG-4比MPEG-2更先进的其中一个特点,就是不再使用宏区块做图像分析,而是以图像上个体为变化记录,因此尽管图像变化速度很快、码率不足时,也不会出现方块画面。

由于MPEG-4是一个公开的平台,各公司、机构均可以根据MPEG-4标准开发不同的制式,因此市场上出现了很多基于MPEG-4技术的视频格式,例如WMV 9Quick TimeDivXXvid等。MPEG-4大部份功能都留待开发者决定采用是否。这意味着整个格式的功能不一定被某个程序所完全函括。因此,这个格式有所谓配置profile)及级别level),定义了MPEG-4应用于不同平台时的功能集合。

MPEG-4分部[编辑]

MPEG-4由一系列的子标准组成,被称为部 (part)(有时也译为),包括以下的部分:

·        第一部分(ISO/IEC 14496-1):系统:描述视频和音频数据流的控制、同步以及混合方式(即混流 Multiplexing,简写为MUX)。

·        第二部分(ISO/IEC 14496-2):视频:定义了一个对各种视觉信息(包括自然视频、静止纹理、计算机合成图形等等)的编解码器。(例如XviD编码就属于MPEG-4 Part 2)

·        第三部分(ISO/IEC 14496-3):音频:定义了一个对各种音频信号进行编码的编解码器的集合。包括高级音频编码(Advanced Audio Coding,缩写为AAC)的若干变形和其他一些音频/语音编码工具。

·        第四部分(ISO/IEC 14496-4):一致性:定义了对本标准其他的部分进行一致性测试的程序。

·        第五部分(ISO/IEC 14496-5):参考软件:提供了用于演示功能和说明本标准其他部分功能的软件

·        第六部分(ISO/IEC 14496-6):多媒体传输集成框架DMIF for Delivery Multimedia Integration Framework)

·        第七部分(ISO/IEC 14496-7):优化的参考软件:提供了对实现进行优化的例子(这里的实现指的是第五部分)。

·        第八部分(ISO/IEC 14496-8):在IP网络上传输:定义了在IP网络上传输MPEG-4内容的方式。

·        第九部分(ISO/IEC 14496-9):参考硬件:提供了用于演示怎样在硬件上实现本标准其他部分功能的硬件设计方案。

·        第十部分(ISO/IEC 14496-10):高级视频编码或称高级视频编码(Advanced Video Coding,缩写为AVC):定义了一个视频编解码器(codec)。AVC和XviD都属于MPEG-4编码,但由于AVC属于MPEG-4 Part 10,在技术特性上比属于MPEG-4 Part2的XviD要先进。另外,它和ITU-T H.264标准是一致的,故又称为H.264

·        第十二部分(ISO/IEC 14496-12):基于ISO的媒体文件格式:定义了一个存储媒体内容的文件格式。

·        第十三部分(ISO/IEC 14496-13):知识产权管理和保护(IPMP for Intellectual Property Managementand Protection)拓展。

·        第十四部分(ISO/IEC 14496-14):MPEG-4文件格式:定义了基于第十二部分的用于存储MPEG-4内容的视频文件格式

·        第十五部分(ISO/IEC 14496-15):AVC文件格式:定义了基于第十二部分的用于存储第十部分的视频内容的文件格式。

·        第十六部分(ISO/IEC 14496-16):动画框架扩展(AFX : Animation Framework eXtension)。

·        第十七部分(ISO/IEC 14496-17):同步文本字幕格式。

·        第十八部分(ISO/IEC 14496-18):字体压缩和流式传输(针对开放字体格式 Open Font Format)。

·        第十九部分(ISO/IEC 14496-19):合成材质流(Synthesized Texture Stream)。

·        第二十部分(ISO/IEC 14496-20):简单场景表示(LASeR for Lightweight Scene Representation。

·        第二十一部分(ISO/IEC 14496-21):用于描绘(Rendering)的MPEG-J拓展。

·        第二十二部分(ISO/IEC 14496-22):开放字体格式(Open Font Format)。

·        第二十三部分(ISO/IEC 14496-23):符号化音乐表示(Symbolic Music Representation)。

·        第二十四部分(ISO/IEC 14496-24):音频与系统交互作用(Audio and systems interaction)。

·        第二十五部分(ISO/IEC 14496-25):3D图形压缩模型(3D Graphics Compression Model)。

·        第二十六部分(ISO/IEC 14496-26):音频一致性检查:定义了测试音频数据与ISO/IEC 14496-3是否一致的方法(Audio conformance)。

·        第二十七部分(ISO/IEC 14496-27):3D图形一致性检查:定义了测试3D图形数据与ISO/IEC 14496-11:2005, ISO/IEC14496-16:2006, ISO/IEC 14496-21:2006, 和 ISO/IEC 14496-25:2009是否一致的方法(3D Graphics conformance)。

Profiles是在每个部分内定义的,所以对某个部分的一个实现通常不是对该部分的完整实现。

MPEG-1MPEG-2MPEG-7MPEG-21是由MPEG制定的其他MPEG标准。

 

Gstreamer

Gstreamer是个好东西,张一元的花茶好喝,今天用五花肉炼的油我看行;-) 

Gstreamer java版本cortado不再继续更新,被淘汰了?

GStreamer plug-ins:
1 protocols handling
2 sources: for audio and video (involves protocol plugins)
3 formats: parsers, formaters, muxers, demuxers, metadata, subtitles
4 codecs: coders and decoders
5 filters: converters, mixers, effects, 
6 sinks: for audio and video (involves protocol plugins)

Specifically, GStreamer provides
1 an API for multimedia applications
2 a plugin architecture
3 a pipeline architecture
4 a mechanism for media type handling/negociation
5 over 150 plug-ins
6 a set of tools
来自http://blog.sina.com.cn/xingqx 讲解了gstreamer的部分内容。
GStreamer
GNOME 桌面环境下用来构建流媒体应用的编程框架(framework),其目标是要简化音/视频应用程序的开发,目前已经能够被用来处理像 MP3OggMPEG1MPEG2AVIQuicktime 等多种格式的多媒体数据。

一、基本概念 GStreamer 作为 GNOME 桌面环境推荐的流媒体应用框架,采用了基于插件(plugin)和管道(pipeline)的体系结构,框架中的所有的功能模块都被实现成可以插拔的组件component),并且在需要的时候能够很方便地安装到任意一个管道上,由于所有插件都通过管道机制进行统一的数据交换,因此很容易利用已有的各种插件组装出一个功能完善的多媒体应用程序。

1.1 元件处理

对于需要应用 GStreamer 框架的程序员来讲,GstElement 是一个必须理解的概念,因为它是组成管道的基本构件,也是框架中所有可用组件的基础,这也难怪 GStreamer 框架中的大部分函数都会涉及到对 GstElement 对象的操作。从 GStreamer 自身的观点来看,GstElement 可以描述为一个具有特定属性的黑盒子,它通过连接点(link point)与外界进行交互,向框架中的其余部分表征自己的特性或者功能。

按照各自功能上的差异,GStreamer 又将 GstElement 细分成如下几类:

·        Source Element 数据源元件 只有输出端,它仅能用来产生供管道消费的数据,而不能对数据做任何处理。一个典型的数据源元件的例子是音频捕获单元,它负责从声卡读取原始的音频数据,然后作为数据源提供给其它模块使用。

·        Filter Element 过滤器元件 既有输入端又有输出端,它从输入端获得相应的数据,并在经过特殊处理之后传递给输出端。一个典型的过滤器元件的例子是音频编码单元,它首先从外界获得音频数据,然后根据特定的压缩算法对其进行编码,最后再将编码后的结果提供给其它模块使用。

·        Sink Element 接收器元件 只有输入端,它仅具有消费数据的能力,是整条媒体管道的终端。一个典型的接收器元件的例子是音频回放单元,它负责将接收到的数据写到声卡上,通常这也是音频处理过程中的最后一个环节。

1将有助于你更好地理解数据源元件、过滤器元件和接收器元件三者的区别,同时也不难看出它们是如何相互配合形成管道的:


1
 

需要注意的是,过滤器元件的具体形式是非常灵活的,GStreamer并没有严格规定输入端和输出端的数目,事实上它们都可以是一个或者多个。图2是一个 AVI分离器的基本结构,它能够将输入数据分离成单独的音频信息和视频信息,用于实现该功能的过滤器元件很明显只具有一个输入端,但却需要有两个输出端。


2
 

要想在应用程序中创建GstElement对象,唯一的办法是借助于工厂对象GstElementFactory。由于GStreamer框架提供了多种类型的GstElement对象,因此对应地提供了多种类型的GstElementFactory对象,它们是通过特定的工厂名称来进行区分的。例如,下面的代码通过gst_element_factory_find()函数获得了一个名为mad的工厂对象,它之后可以用来创建与之对应的MP3解码器元件:

GstElementFactory*factory;
factory = gst_element_factory_find ("mad"); 

成功获得工厂对象之后,接下来就可以通过gst_element_factory_create()函数来创建特定的GstElement对象了,该函数在调用时有两个参数,分别是需要用到的工厂对象,以及即将创建的元件名称。元件名称可以用查询的办法获得,也可以通过传入空指针(NULL)来生成工厂对象的默认元件。下面的代码示范了如何利用已经获得的工厂对象,来创建名为decoderMP3解码器元件:

GstElement*element;
element = gst_element_factory_create (factory, "decoder"); 

当创建的GstElement不再使用的时候,还必须调用gst_element_unref()函数释放其占用的内存资源:

gst_element_unref(element); 

GStreamer使用了与GObject相同的机制来对属性(property)进行管理,包括查询(query)、设置(set)和读取(get等。所有的GstElement对象都需要从其父对象GstObject那里继承名称(name)这一最基本的属性,这是因为像 gst_element_factory_make()gst_element_factory_create()这样的函数在创建工厂对象和元件对象时都会用到名称属性,通过调用gst_object_set_name()gst_object_get_name()函数可以设置和读取 GstElement对象的名称属性。

1.2 衬垫处理

衬垫(pad)是GStreamer框架引入的另外一个基本概念,它指的是元件(element)与外界的连接通道,对于框架中的某个特定元件来说,其能够处理的媒体类型正是通过衬垫暴露给其它元件的。成功创建GstElement对象之后,可以通过gst_element_get_pad()获得该元件的指定衬垫。例如,下面的代码将返回element元件中名为src的衬垫:

GstPad *srcpad;
srcpad = gst_element_get_pad (element, "src"); 

如果需要的话也可以通过gst_element_get_pad_list()函数,来查询指定元件中的所有衬垫。例如,下面的代码将输出element元件中所有衬垫的名称:

GList *pads;
pads = gst_element_get_pad_list (element);
while (pads) {
GstPad *pad = GST_PAD (pads->data);
g_print ("pad name is: %s\n", gst_pad_get_name (pad));
pads = g_list_next (pads);

与元件一样,衬垫的名称也能够动态设置或者读取,这是通过调用gst_pad_get_name ()gst_pad_set_name()函数来完成的。所有元件的衬垫都可以细分成输入衬垫和输出衬垫两种,其中输入衬垫只能接收数据但不能产生数据,而输出衬垫则正好相反,只能产生数据但不能接收数据,利用函数gst_pad_get_direction()可以获得指定衬垫的类型。 GStreamer框架中的所有衬垫都必然依附于某个元件之上,调用gst_pad_get_parent()可以获得指定衬垫所属的元件,该函数的返回值是一个指向GstElement的指针。衬垫从某种程度上可以看成是元件的代言人,因为它要负责向外界描述该元件所具有的能力。GStreamer框架提供了统一的机制来让衬垫描述元件所具有的能力(capability),这是借助数据结构_GstCaps来实现的:

struct _GstCaps{
gchar *name; 
guint16 id; 
guint refcount; 
GstProps *properties; 
GstCaps *next; 
}; 

以下是对mad元件的能力描述,不难看出该元件中实际包含sinksrc两个衬垫,并且每个衬垫都带有特定的功能信息。名为sink的衬垫是mad元件的输入端,它能够接受MIME类型为audio/mp3的媒体数据,此外还具有layerbitrateframed三种属性。名为src的衬垫是 mad元件的输出端,它负责产生MIME类型为audio/raw媒体数据,此外还具有formatdepthratechannels等多种属性。

Pads:
SINK template: ’sink’
Availability: Always
Capabilities:
’mad_sink’:
MIME type: ’audio/mp3’:
SRC template: ’src’
Availability: Always
Capabilities:
’mad_src’:
MIME type: ’audio/raw’:
format: String: int
endianness: Integer: 1234
width: Integer: 16
depth: Integer: 16
channels: Integer range: 1 - 2
law: Integer: 0
signed: Boolean: TRUE
rate: Integer range: 11025 - 48000 

准确地说,GStreamer框架中的每个衬垫都可能对应于多个能力描述,它们能够通过函数gst_pad_get_caps()来获得。例如,下面的代码将输出pad衬垫中所有能力描述的名称及其MIME类型:

GstCaps *caps;
caps = gst_pad_get_caps (pad);
g_print ("pad name is: %s\n", gst_pad_get_name (pad));
while (caps) {
g_print (" Capability name is %s, MIME type is %s\n",
gst_caps_get_name (cap),
gst_caps_get_mime (cap));
caps = caps->next;

1.3 箱柜

箱柜(bin)是GStreamer框架中的容器元件,它通常被用来容纳其它的元件对象,但由于其自身也是一个GstElement对象,因此实际上也能够被用来容纳其它的箱柜对象。利用箱柜可以将需要处理的多个元件组合成一个逻辑元件,由于不再需要对箱柜中的元件逐个进行操作,因此能够很容易地利用它来构造更加复杂的管道。在GStreamer框架中使用箱柜还有另外一个优点,那就是它会试着对数据流进行优化,这对于多媒体应用来讲是很具吸引力的。

3描述了箱柜在GStreamer框架中的典型结构:


3
 

GStreamer应用程序中使用的箱柜主要有两种类型:

·        GstPipeline 管道是最常用到的容器,对于一个GStreamer应用程序来讲,其顶层箱柜必须是一条管道。

·        GstThread 线程的作用在于能够提供同步处理能力,如果GStreamer应用程序需要进行严格的音视频同步,一般都需要用到这种类型的箱柜。

GStreamer框架提供了两种方法来创建箱柜:一种是借助工厂方法,另一种则是使用特定的函数。下面的代码示范了如何使用工厂方法创建线程对象,以及如何使用特定函数来创建管道对象:

GstElement*thread, *pipeline;
//
创建线程对象,同时为其指定唯一的名称。
thread = gst_element_factory_make ("thread", NULL);
//
根据给出的名称,创建一个特定的管道对象。
pipeline = gst_pipeline_new ("pipeline_name"); 

箱柜成功创建之后,就可以调用gst_bin_add()函数将已经存在的元件添加到其中来了:

GstElement*element;
GstElement *bin;
bin = gst_bin_new ("bin_name");
element = gst_element_factory_make ("mpg123", "decoder");
gst_bin_add (GST_BIN (bin), element); 

而要从箱柜中找到特定的元件也很容易,可以借助gst_bin_get_by_name()函数实现:

GstElement*element;
element = gst_bin_get_by_name (GST_BIN (bin), "decoder"); 

由于GStreamer框架中的一个箱柜能够添加到另一个箱柜之中,因此有可能会出现箱柜嵌套的情况,gst_bin_get_by_name()函数在查找元件时会对嵌套的箱柜作递归查找。元件有添加到箱柜之中以后,在需要的时候还可以从中移出,这是通过调用gst_bin_remove()函数来完成的:

GstElement*element;
gst_bin_remove (GST_BIN (bin), element); 

如果仔细研究一下图3中描述的箱柜,会发现它没有属于自己的输入衬垫和输出衬垫,因此显然是无法作为一个逻辑整体与其它元件交互的。为了解决这一问题,GStreamer引入了精灵衬垫(ghost pad)的概念,它是从箱柜里面所有元件的衬垫中推举出来的,通常来讲会同时选出输入衬垫和输出衬垫,如图4所示:


4
 

具有精灵衬垫的箱柜在行为上与元件是完全相同的,所有元件具有的属性它都具有,所有针对元件能够进行的操作也同样能够针对箱柜进行,因此在GStreamer应用程序中能够像使用元件一样使用这类箱柜。下面的代码示范了如何为箱柜添加一个精灵衬垫:

GstElement *bin;
GstElement *element;
element = gst_element_factory_create ("mad", "decoder");
bin = gst_bin_new ("bin_name");
gst_bin_add (GST_BIN (bin), element);
gst_element_add_ghost_pad (bin, gst_element_get_pad (element,"sink"), "sink"); 

二、元件连接

在引入了元件和衬垫的概念之后,GStreamer对多媒体数据的处理过程就变得非常清晰了:通过将不同元件的衬垫依次连接起来构成一条媒体处理管道,使数据在流经管道的过程能够被各个元件正常处理,最终实现特定的多媒体功能。

图1就描述了一条很简单的管道,它由三个基本元件构成:数据源元件只负责产生数据,它的输出衬垫与过滤器元件的输入衬垫相连;过滤器元件负责从自己的输入衬垫中获取数据,并在经过特定的处理之后,将结果通过输出衬垫传给与之相连的接收器元件;接收器元件只负责接收数据,它的输入衬垫与过滤器元件的输出衬垫相连,负责对最终结果进行相应的处理。

GStreamer框架中的元件是通过各自的衬垫连接起来的,下面的代码示范了如何将两个元件通过衬垫连接起来,以及如何在需要的时候断开它们之间的连接:

GstPad *srcpad,*sinkpad;
srcpad = gst_element_get_pad (element1, "src");
sinpad = gst_element_get_pad (element2, "sink");
//
连接
gst_pad_link (srcpad, sinkpad);
//
断开
gst_pad_unlink (srcpad, sinkpad); 

如果需要建立起连接的元件都只有一个输入衬垫和一个输出衬垫,那么更简单的做法是调用gst_element_link()函数直接在它们之间建立起连接,或者调用gst_element_unlink()函数断开它们之间的连接:

// 连接
gst_element_link (element1, element2);
//
断开
gst_element_unlink (element1, element2);

三、元件状态

GStreamer框架中的元件通过管道连接好之后,它们就开始了各自的处理流程,期间一般会经历多次状态切换,其中每个元件在特定时刻将处于如下四种状态之一:

·        NULL 这是所有元件的默认状态,表明它刚刚创建,还没有开始做任何事情。

·        READY 表明元件已经做好准备,随时可以开始处理流程。

·        PAUSED 表明元件因某种原因暂时停止处理数据。

·        PLAYING 表明元件正在进行数据处理。

所有的元件都从NULL状态开始,依次经历NULLREADYPAUSEDPLAYING等状态间的转换。元件当前所处的状态可以通过调用gst_element_set_state()函数进行切换:

GstElement *bin;

gst_element_set_state (bin, GST_STATE_PLAYING); 

默认情况下,管道及其包含的所有元件在创建之后将处于NULL状态,此时它们不会进行任何操作。当管道使用完毕之后,不要忘记重新将管道的状态切换回NULL状态,让其中包含的所有元件能够有机会释放它们正在占用的资源。

管道真正的处理流程是从第一次将其切换到READY状态时开始的,此时管道及其包含的所有元件将做好相应的初始化工作,来为即将执行的数据处理过程做好准备。对于一个典型的元件来讲,处于READY状态时需要执行的操作包括打开媒体文件和音频设备等,或者试图与位于远端的媒体服务器建立起连接。

处于READY状态的管道一旦切换到PLAYING状态,需要处理的多媒体数据就开始在整个管道中流动,并依次被管道中包含的各个元件进行处理,从而最终实现管道预先定义好的某种多媒体功能。GStreamer框架也允许将管道直接从NULL状态切换到PLAYING状态,而不必经过中间的READY态。

正处于播放状态的管道能够随时切换到PAUSED状态,暂时停止管道中所有数据的流动,并能够在需要的时候再次切换回PLAYING状态。如果需要插入或者更改管道中的某个元件,必须先将其切换到PAUSED或者NULL状态,元件在处于PAUSED状态时并不会释放其占用的资源。

四、实现MP3播放器

在理解了一些基本概念和处理流程之后,下面来看看如何利用GStreamer框架提供的组件,来实现一个简单的MP3播放器。在图1中描述的结构能够很容易地映射成MP3播放器,其中数据源元件负责从磁盘上读取数据,过滤器元件负责对数据进行解码,而接受器元件则负责将解码后的数据写入声卡。

与其它众多GNOME项目一样,GStreamer也是用C语言实现的。如果想要在程序中应用GStreamer提供的各种功能,首先必须在主函数中调用 gst_init()来完成相应的初始化工作,以便将用户从命令行输入的参数传递给GStreamer函数库。一个典型的GStreamer应用程序的初始化如下所示:

#include<gst/gst.h>
int main (int argc, char *argv[])
{
gst_init (&argc, &argv);

接下去需要创建三个元件并连接成管道,由于所有GStreamer元件都具有相同的基类GstElement,因此能够采用如下方式进行定义:

GstElement*pipeline, *filesrc, *decoder, *audiosink; 

管道在GStreamer框架中是用来容纳和管理元件的,下面的代码将创建一条名为pipeline的新管道:


pipeline = gst_pipeline_new ("pipeline"); 

数据源元件负责从磁盘文件中读取数据,它具有名为location的属性,用来指明文件在磁盘上的位置。使用标准的GObject属性机制可以为元件设置相应的属性:

filesrc =gst_element_factory_make ("filesrc", "disk_source");
g_object_set (G_OBJECT (filesrc), "location", argv[1], NULL); 

过滤器元件负责完成对MP3格式的数据进行解码,最简单的办法是安装mad这一插件,借助它来完成相应的解码工作:

decoder =gst_element_factory_make ("mad", "decoder"); 

接收器元件负责将解码后的数据利用声卡播放出来:

audiosink =gst_element_factory_make ("audiosink", "play_audio"); 

已经创建好的三个元件需要全部添加到管道中,并按顺序连接起来:

gst_bin_add_many(GST_BIN (pipeline), filesrc, decoder, audiosink, NULL);

gst_element_link_many (filesrc, decoder, audiosink, NULL); 

所有准备工作都做好之后,就可以通过将管道的状态切换到PLAYING状态,来启动整个管道的数据处理流程:

gst_element_set_state(pipeline, GST_STATE_PLAYING); 

由于没有用到线程,因此必须通过不断调用gst_bin_iterate()函数的办法,来判断管道的处理过程会在何时结束:

while(gst_bin_iterate (GST_BIN (pipeline))); 

只要管道内还会继续有新的事件产生,gst_bin_iterate()函数就会一直返回TRUE,只有当整个处理过程都结束的时候,该函数才会返回FALSE,此时就该终止管道并释放占用的资源了:

gst_element_set_state(pipeline, GST_STATE_NULL);

gst_object_unref (GST_OBJECT (pipeline)); 

GStreamer实现的MP3播放器的源代码如下所示:

#include<gst/gst.h>
int main (int argc, char *argv[])
{
GstElement *pipeline, *filesrc, *decoder, *audiosink;
gst_init(&argc, &argv);
if (argc != 2) {
g_print ("usage: %s <mp3 filename>\n", argv[0]);
exit (-1);
}

pipeline = gst_pipeline_new ("pipeline");

filesrc = gst_element_factory_make ("filesrc","disk_source");
g_object_set (G_OBJECT (filesrc), "location", argv[1], NULL);

decoder = gst_element_factory_make ("mad", "decoder");

audiosink = gst_element_factory_make ("osssink", "play_audio");

gst_bin_add_many (GST_BIN (pipeline), filesrc, decoder, audiosink, NULL);

gst_element_link_many (filesrc, decoder, audiosink, NULL);

gst_element_set_state (pipeline, GST_STATE_PLAYING);
while (gst_bin_iterate (GST_BIN (pipeline)));

gst_element_set_state (pipeline, GST_STATE_NULL);

gst_object_unref (GST_OBJECT (pipeline));
exit (0);
}

五、小结

随着 GNOME 桌面环境的不断普及,GStreamer 作为一个强大的多媒体应用开发框架,已经开始受到越来越多人的关注。Gstreamer在设计时采用了非常灵活的体系结构,并且提供了许多预定义的媒体处理模块,因此能够极大简化在Linux下开发多媒体应用的难度。

补充

除了上述Gstreamer中描述的组件外,还有busbufferevent等:
Bus:

       pipeline线程向应用程序发送发送消息,目的是对应用屏蔽线程的概念。缺省情况下,每个pipeline包含一个bus,所以应用不需要创建bus,仅仅需要在bus上设定一个对象信号处理类似的消息处理。当主循环开始运行后,bus周期性的检查新消息,如果有消息,那么调用注册的回调函数。如果使用glib,那么glib有对应的处理机制,否则使用信号机制。

流过pipeline的数据由buffersevents组成。buffers中是确切的pipeline数据,events中是控制信息,比如seek信息,end-of-stream指示等。src-element通常创建一个新的buffer,通过pad传递到链中的下一个element

buffer:

       buffer包含由(内存指针,内存大小,时间戳,引用计数等信息)。简单的使用方式是:创建buffer,分配内存,放入数据,传输到后续element,后续element读数据,处理后减去引用计数。更复杂的情况是element就地修改buffer中的内容,而不是分配一个新的bufferelement还写到硬件内存(video-capture src等),bufer还可以是只读的。

event:

       Event控制包,可以发送给上/下游element。发送给下游的event统治后续element流状态,是断绝,涮新,流结尾等信息。发送给上游event用在应用交互和event-event交互,用来请求改变流状态,例如seek等。对于应用来说,只有上流event比较重要。下流event仅仅维持一个完整的数据流概念。

 

 

Gstreamer说明

 Gstreamer简介

是一个框架,灵活轻便。

第一部分基本没有难度,只要能看懂英文。从我目前接触的感觉上看,Gstreamer确实简化了动态 库的加载,模块与模块间的合作。

但是Gstreamer用得还是有点不太习惯,可能是 GLIB这种风格没有适应。

gstreamer整个分为:

l         core:核心库

l         基础插件:一些很基础的插件

l         好插件:编写质量较好的遵循LGPL协议的插件

l         坏插件:有待改进的插件

l         其他库

1.1    核 心库

核心库是不了解任何媒体信息的,它只是一个框架,将所有单元联系起来。

单元是gstreamer里的核心概念。

 基础知识

2.1 单元

Element是构成管道的组件,每个element实际就是一个插件,在gst中得到组装成一个pipe,数据从源单元流向目的单元,完成整个流程。单元间是可以链接起来的(必须得链接起来以组成pipe)。

2.2 Pad

pad是一个单元的输入输出端口,通过pad, 才能将两个单元链接到一起。对输入来说,pad就是一个插口,对输出来说pad就是一个塞子。pad有自己的规格,所以不同规格的pad就限制了数据的规格。只有规格相符的pad才能链接到一起。

l         规格协商的过程叫caps negotiation

l         数据类型叫GstCaps

2.3 盒子和管道

盒子Bin是一组单元的集合,而管道Pipeline是一种特殊的盒子,在管道中,所有单元可以一体执行某些。

在实现的时候,Bin也是一种单元,操纵Bin就可以改变内部所有单元的属性,而且Bin还能传递内部单元的信号事件。这样就简化了外界使用的难度。

管道是一个顶层的bin,可以设置状态为PAUSED或者PLAYING。内部会启动一个独立线程来干活。

 GST构建

3.1 初始化

GST库必须先初始化,调用gst_init

3.2 单元elements

GstElement是最重要的对象。一些高级对象也是从它派生出来的。有好几种类型的elements,必须分清楚了。

1.       源单元

source单元是数据的产生方,对应一个源pad。一般画在右边。

l         源单元只能生产数据,不能接收数据

2.       中间单元

中间单元包括过滤器,转换器,复用器,解复用器,编解码器等。

它有多个源pad,对应多个目标pad

3.       目标单元

只能接受数据。

4.       创建和使用单元

通过factory_makegst_object_unref来创建及释放单元。make需要两个参数,一个是工厂名,一个是单元名。工厂名实际就是插件名,所以需要先加载插件上 来,才能创建对应的单元。

单元继承所有Gobject的属性,所以可以当做Gobject来处理。

单元有属性,单元还能触发信号,所以必须关注这些。

作为工厂,其功能还不仅限于创建单元,一个工厂有属性,它知道自己能创建怎样的单元。

其实就是这个插件知道自己能创建怎样的单元。可能需要看了插件编写才真正知道。

5.       链接单元

单元必须链接起来,才能协同工作。

源单元-à中间单元--à目标单元

l         必须先加到管道后才能link起来。

l         不在同一bin中的不能link

6.       单元状态

单元链接好后,啥事也不会发生,除非设置单元状态。单元一共有四种状态:

l         GST_STATE_NULL:默认 状态,内部会释放单元的所有资源,其实就是初始状态。

l         XXX_READY:就绪状态,分配 资源,打开设备。但是流不会打开,所以此时流信息都是零。如果之前打开了流,在这状态中将会被关闭,流信息都会被重设。

l         XXX_PAUSED:已经打开了 流,但是暂时不处理它。这个时候可以去修改例如seek位置等流信息。时间轴停止

l         XXX_PLAYING:时间轴运 行。设置为这个状态后,整个流程就开始启动了。内部会将消息发送从管道所在的线程转移到应用程序线程。(?)

通过set_state函数设置状态,GST会平滑设置,例如从PLAY设置为NULL,将平滑设置READYPAUSED

3.4 Bins

Bin不仅是一个单元的集合,更是集成了对内部单元的管理。管道是一种特殊的bin,实际要播放一个视音频就得用到管道。管道能独立在后台运行。

1.       创建Bin

Binelement派生而来,所以创建 单元的函数均可创建Bin,但一般用两个更方便的函数:

l         gst_bin_new

l         gst_pipeline_new

Bin是一个集合,需要将单元加入进去,也可以删除。

l         gst_bin_add,gst_bin_remove

注意,一旦加入到Bin,单元的所有者就变为Bin了,所以删除Bin的话,内部的单元也会相应减少引用。

2.       定制化Bin

还是得看插件编程指南才能真正理解。

3.5 Bus

1. 总论

Bus的好处是可以把pipeline所在的线程的消息 路由到应用程序指定的那个Context中去。

是全局那个线程吗?待会查看下源码。定期检查bus?设计还是比较巧妙的。

bus上要加入监控回调函数。通过

l         gst_bus_add_watch/gst_bus_add_signal_watch

l         要从bus中取消息,得调用gst_bus_pop/peek/pop

注意,Bus发出的消息是GstMessage结构,值得解释。

Bus含一个队列,每次post一 个消息就加到队列里,然后出发maincontext的wakeup。 这样就完成了将消息路由到maincontext去了。因为maincontext等 待的有这个bus队列。

这里边绕啊绕的..想法还是很直观。

有没有办法不用默认context呢?有一个函数将message转换为signalemit

l         gst_bus_async_signal_func

这里要区分下messagesignalsignalGobject系统提供的,messageGST提供的。message的处理是异步的,而signal的处理是同步的。

如果你不想写很多switch来区别message的话,那么另外一种办法就是注册对应的signal到系统。另外,如果不使 用mainloop的话,异步消息-信号不会发出去。

bus的消息分为异步和同步两类。

l         异步信号是通过加入到mainloop中的Gsource来触发的,所以必须有mainloop再运行才可

l         同步信号必须先注册一个同步信号处理函数才可。

 

需要自己设置一个同步信号处理函数,在那里触发另外一个context,并且调用上面这个函 数发送signal

 

2.  message类型

描述一个message有以下:

l         Message的来源:记录是从哪个 单元发出来的

l         类型:

l         时间戳

比较重要的是类型信息:有以下几类类型

l         error,warning,info等 信息:需要调用对应API进行解析

l         EOS:文件结尾,需要重设管道状态 等

l         Tag:标签信息(其实就是媒体信 息,比如长度,采样率等)

l         State变化:

l         缓存信息:调用gst_bus_get_structure来解析缓存信息

l         单元信息:某些单元会发送特殊的信息

l         应用程序自己的信息

 

3.6 Pads和属性

Pads很关键,代表了单元的出入口。标示Pads特性的有两个:

l         方向,从单元内角度看,sink收数据,source发数据

l         可用性(availability

方向好说,只有sinksource两种,可用性是一个新概念。主要代表这个pad是存活期的。

分三种:

l         pad一直存在

l         动态存在:有时候有,有时候没有,动态创建和删除

l         响应存在:根据外界要求来创建

这个可用性是针对媒体文件类型的一种简化表示。下面针对这个具体讲述,pad的可用性非常重要。

1. 动态pad

为何会有动态之说?原因很简单。例如播放音频的时候,要动态检测有几路音频,然后再创建对应的pad

程序里边应该绑定一个消息处理函数到动态pad的创建通知上。

2. 响应pad

响应用户要求而创建的pad。必须从一个支持创建这种类型的单元中去创建,调用

l         gst_element_get_request_pad

这个element必须支持Request这种,这个熟悉由插件注册的时候指定的。

还有一种就是查找相容的pad

l         get_element_get_compatible_pad,根据源padcaps来从单元中找一个相容的。

 

3. Pad的属性

刚才提到过,查找相容的pad,那么相容是怎么判断和体现的呢?pad有自己的能力熟悉(Capabilities

pad的能力熟悉是和pad模板以及pad绑定一起的。pad模板估计就是一个pad工厂。

一个pad有很多不同的能力,这个是最原始的信息。但是具体工作后,一个pad要和别的pad协商,大家按照规定的能力办 事。这样,pad的能力就是协商后的能力了。

能力在GST中用GstCaps来表示。

GstCaps含一到多个Gstructure,一个Gstructure代表一种pad能处理的媒体类型。

4. GST中属性和值的表示

GST除了使用GLIB中的数据类型外,还单独定 义了一些数据类型,用来表示属性值。

值得注意的有:

l         GST_TYPE_INT_RANGE:范围值

l         GST_TYPE_LIST:包含任 何基本类型都可以

l         GST_TYPE_ARRAY:只能 包含相同的类型

5. Caps的用处

Caps实际的用处很多,其实就是一个寻找匹配padelement之用。

Caps中有一项描述媒体信息的,叫metadata。如何从caps中获取条目呢?

caps中存的是structure条 目,一个structure代表一种能力

gst_caps_get_structure/size

根据条目多少和属性,caps可分为:

l         简单caps:只含一条structure

l         固定caps:含一条structure,并且属性值没有range之类可变化的

l         任意caps和空caps是两种特殊cap

 

6. 创建过滤器使用的caps

刚才讲的全是从单元中获取caps,都是已经弄好了的。那么如果想动态创建caps该如何做呢?

l         gst_caps_new_simple:创建simple

l         xx_full:创建nstructurecaps

这还只是创建pad,要把srcdst通过过滤单元链接起来,用

gst_element_link_filtered,内部会根据过滤pad自动创建一个capsfilter

所以关闭链接的时候,需要把src和dst分别从capsfilter中关闭链接。而非简单 的关闭源和目标

 

7. 幽灵pad

有啥用?其实就是创建一个代理pad吧。

为啥要有个这个东西?因为bin本身是没有pad的。所以你就没办法把两个bin链接起来。

这个时候,可以用bin中的一个单元的pad构造一个代理pad,这样bin就有一个代理pad了。这个pad实际指向被代理的那个单元的pad

 

3.7 缓冲与事件

数据流动是以缓冲传递来实际工作的,所以buffer比较在重要。

eventsmessage不太一样,这个events实际就是命令,而且在 管道中流动。这么说的话,buffer对应的就是数据。

从命名习惯上来说,buffer更应该看成是一种容器,里边含data和events。

1. Buffer

GstBuffer有以下成员:

l         数据内存指针

l         缓冲大小

l         时间戳

l         用者计数(与引用计数对应)

l         标识

2. Events

事件是一种控制数据,能够在管道中上下流动。

一般来说,上游的控制命令可能是真的在控制什么,来自下游的events可 能大多数是些状态通知之类的。?原文是这么说的。

应用程序自己能发送控制?例如seek命令。

恩,确实应该有地方可以发送控制命令。典型的就是seek。用户也需要一个地方能做这个工作。

看来都是通过events方式来做到控制的。

l         gst_events_new_xxx

l         gst_element_send_event

先创建一个命令,然后发出去….

 

 

 

   饿汉式与懒汉式的区别:

 

  饿汉式:
        public class Singleton{
            private static Singleton singleton = new Singleton ();
            private Singleton (){}
            public Singleton getInstance(){return singletion;}
       } 

     
懒汉式:
       public class Singleton{
            private static Singleton singleton = null;
            public static synchronized synchronized getInstance(){
                 if(singleton==null){
                     singleton = new Singleton();
                 }
                return singleton;
            }
       } 

     
比较:
         
饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不在改变
          
懒汉式如果在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的
          
推荐使用第一种 

 

1楼基本上已经回答了问题.但是懒汉式没有加私有的构造函数
从实现方式来讲他们最大的区别就是懒汉式是延时加载,
他是在需要的时候才创建对象,而饿汉式在虚拟机启动的时候就会创建,
使用的场合根据具体环境和个人习惯吧.

 

 

 

谈谈码率,帧率,分辨率和清晰度的关系 

为了了解视频的码率、帧率、分辨率。我们先来看看视频编码的基本原理:视频图像数据有极强的相关性,也就是说有大量的冗余信息。其中冗余信息可分为空域冗余信息和时域冗余信息。压缩技术就是将数据中的冗余信息去掉(去除数据之间的相关性),压缩技术包含帧内图像数据压缩技术、帧间图像数据压缩技术和熵编码压缩技术。视频文件一般涉及到三个参数:帧率、分辨率和码率。

   
帧率:每秒显示的图片数。影响画面流畅度,与画面流畅度成正比:帧率越大,画面越流畅;帧率越小,画面越有跳动感。由于人类眼睛的特殊生理结构,如果所看画面之帧率高于16的时候,就会认为是连贯的,此现象称之为视觉暂留。并且当帧速达到一定数值后,再增长的话,人眼也不容易察觉到有明显的流畅度提升了。  

分辨率:(矩形)图片的长度和宽度,即图片的尺寸  

码率:把每秒显示的图片进行压缩后的数据量。影响体积,与体积成正比:码率越大,体积越大;码率越小,体积越小。(体积=码率×时间)  帧率X分辨率=压缩前的每秒数据量(单位应该是若干个字节)   压缩比=压缩前的每秒数据量/码率(对于同一个视频源并采用同一种视频编码算法,则:压缩比越高,画面质量越差。) 

 所谓清晰,是指画面十分细腻,没有马赛克。并不是分辨率越高图像就越清晰。  简单说:在码率一定的情况下,分辨率与清晰度成反比关系:分辨率越高,图像越不清晰,分辨率越低,图像越清晰。在分辨率一定的情况下,码率与清晰度成正比关系,码率越高,图像越清晰;码率越低,图像越不清晰。  

但是,事实情况却不是这么简单。可以这么说:在码率一定的情况下,分辨率在一定范围内取值都将是清晰的;同样地,在分辨率一定的情况下,码率在一定范围内取值都将是清晰的。  在视频压缩的过程中, I帧是帧内图像数据压缩,是独立帧。而P帧则是参考I帧进行帧间图像数据压缩,不是独立帧。在压缩后的视频中绝大多数都是P帧,故视频质量主要由P帧表现出来。由于P帧不是独立帧,而只是保存了与邻近的I帧的差值,故实际上并不存在分辨率的概念,应该看成一个二进制差值序列。而该二进制序列在使用熵编码压缩技术时会使用量化参数进行有损压缩,视频的质量直接由量化参数决定,而量化参数会直接影响到压缩比和码率。  视频质量可以通过主观和客观方式来表现,主观方式就是通常人们提到的视频清晰度,而客观参数则是量化参数或者压缩比或者码率。在视频源一样,压缩算法也一样的前提下比较,量化参数,压缩比和码率之间是有直接的比例关系的。  分辨率的变化又称为重新采样。由高分辨率变成低分辨率称为下采样,由于采样前数据充足,只需要尽量保留更多的信息量,一般可以获得相对较好的结果。而由低分辨率变成高分辨率称为上采样,由于需要插值等方法来补充(猜测)缺少的像素点,故必然会带有失真,这就是一种视频质量(清晰度)的损失。 

 

 

 

视频码率,帧率和分辨率到底哪一个影响电影的清晰度

 

码率:影响体积,与体积成正比:码率越大,体积越大;码率越小,体积越小。

码率就是数据传输时单位时间传送的数据位数,一般我们用的单位是kbps即千位每秒。也就是取样率(并不等同与采样率,采样率的单位是Hz,表示每秒采样的次数),单位时间内取样率越大,精度就越高,处理出来的文件就越接近原始文件,但是文件体积与取样率是成正比的,所以几乎所有的编码格式重视的都是如何用最低的码率达到最少的失真,围绕这个核心衍生出来cbr(固定码率)与vbr(可变码率), “码率”就是失真度,码率越高越清晰,反之则画面粗糙而多马赛克。

 

下面是通过一个wav文件的采样率来计算码率和文件大小,通过MediaInfo工具显示的文件信息如下:

概要

完整名称                            :audio\wav\adele-rolling_in_the_deep.wav

文件格式                            : Wave

文件大小                            : 38.3 MiB

长度                                   : 3分 47秒

平均混合码率                    : 1 411 Kbps

 

音频

ID                                        : 0

文件格式                            : PCM

格式设置,Endianness     : Little

编码设置ID                         : 1

编码设置ID/提示信息        : Microsoft

长度                                     : 3分 47秒

码率                                     : 1 411.2 Kbps

声道                                    : 2声道

采样率                                : 44.1 KHz

位深度                                : 16位

大小                                    : 38.3 MiB (100%)

 

1.码率计算公式:

码率=采样率x 位深度 x 声道

所以,上面文件的码率= 44.1Khzx 16位x 2声道 = 1411.2Kbps

 

2.文件大小= 码率 x 时长 =1411.2 Kbps x (3 x 60 + 47 )s = 1411.2Kbps x 227s

 =38102.4Kb

38102.4Kb / 1024 Kb/M = 37.2M

近似等于mediainfo工具显示的文件大小38.3M。

注:此计算公式对未压缩的wav格式文件有效,不适用于mp3等被压缩的文件。

 

帧率:影响画面流畅度,与画面流畅度成正比:帧率越大,画面越流畅;帧率越小,画面越有跳动感。如果码率为变量,则帧率也会影响体积,帧率越高,每秒钟经过的画面越多,需要的码率也越高,体积也越大。

帧率就是在1秒钟时间里传输的图片的帧数,也可以理解为图形处理器每秒钟能够刷新几次,

 

分辨率:影响图像大小,与图像大小成正比:分辨率越高,图像越大;分辨率越低,图像越小。

 

清晰度

在码率一定的情况下,分辨率与清晰度成反比关系:分辨率越高,图像越不清晰,分辨率越低,图像越清晰。
在分辨率一定的情况下,码率与清晰度成正比关系,码率越高,图像越清晰;码率越低,图像越不清晰。

 

带宽、帧率

例如在ADSL线路上传输图像,上行带宽只有512Kbps,但要传输4路CIF分辨率的图像。按照常规,CIF分辨率建议码率是512Kbps,那么照此计算就只能传一路,降低码率势必会影响图像质量。那么为了确保图像质量,就必须降低帧率,这样一来,即便降低码率也不会影响图像质量,但在图像的连贯性上会有影响。

 

avi帧率 dwScale,dwRate

转自:http://yixiangongzhu.blog.163.com/blog/static/19736320320111123111753465/

msdn上说dwRate/dwScale才是播放速率。   
  视频中每秒播放的帧数可能不是整数,比如可能是29.97等,   
  注意到在AVISTREAMINFO结构中,所以属性都是整型变量表示的,所以小数只能  
  用两个整数相除得到,这样就需要用两个整数(dwRate和dwScale)来得到播放速率。   
  比如速率是29.97,那么可以用dwRate=2997,dwScale=100得到   
  如果速率是29.9,那么可以用dwRate=299,dwScale=10得到

在avi文件中包含有AviMainHeader,AviStreamHeader等头部信息,其中有以下几个字段:Start、Length、Scale、Rate,有资料中介绍:

InAviMainHeader:

The dwStart and dwLength fields specify the starting time ofthe AVI file and the length of the file. The units are defined bydwRate and dwScale. ThedwStart field is usually set to zero.

The dwScale and dwRate fields are used to specify the generaltime scale that the file will use. In addition to this time scale, each streamcan have its own time scale. The time scale in samples per second is determinedby dividing dwRate by dwScale.

InAviStreamHeader:

dwScale isused together with dwRate to specify the time scale that thisstream will use.

Dividing dwRate by dwScale gives the number of samples persecond.

Forvideo streams, this rate should be the frame rate.

Foraudio streams, this rate should correspond to the time needed fornBlockAlign bytes of audio, which for PCM audiosimply reduces to the sample rate.

 

为了理解,我们拿一个实际的avi文件来分析一下:

1.VideoAviStreamHeader:Length=4500、Scale=1、Rate=25,因此此文件视频帧率为25/1=25,可得:视频时长:4500/25=180秒。这几个数字容易理解。

2.AudioAviStreamHeader:Length=2812、Scale=16000、Rate=2,初看一头雾水,反复读资料、仔细分析才明白:对于PCMaudio,nBlockAlign与dwSampleSize相等,为2,及每个音频采样为两个字节,每个音频帧的大小为1024B,而dwRate/dwScale即为采样率:16000/2=8000,因此,音频时长:

(2812* 1024 / 2) / (16000 / 2) = 179.968秒。

 

 

H264获取SPS与PPS

2012年09月20日 ⁄ 多媒体技术音视频编解码 ⁄ 共 3678字 ⁄ 暂无评论 ⁄ 被围观 1,449 views+

在用Android手机进行h264硬编码的时候如果要进行视频流的实时传输,就需要知道视频流的SequenceParameter Sets (SPS) 和Picture Parameter Set (PPS)。

今天算是看明白如何获取SPS和PPS,在这里记录下来,希望有需要的朋友可以在这里获取到一些些的帮助。

首先说一下大前提,我设置的视频录制参数为:

mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);

mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

为了让大家更加明白,我先贴出avcC的数据结构:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

aligned(8) class AVCDecoderConfigurationRecord {
   unsigned int(8) configurationVersion = 1;
   
unsigned int(8) AVCProfileIndication;
   unsigned int(8) profile_compatibility;
   unsigned int(8) AVCLevelIndication;

   bit(6) reserved = '111111'b;
   unsigned int(2) lengthSizeMinusOne;

   bit(3) reserved = '111'b;
   unsigned int(5) numOfSequenceParameterSets;
   for (i=0; i< numOfSequenceParameterSets; i++) {
      unsigned int(16) sequenceParameterSetLength ;
      bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit;
   }

   unsigned int(8) numOfPictureParameterSets;
   for (i=0; i< numOfPictureParameterSets; i++) {
      unsigned int(16) pictureParameterSetLength;
      bit(8*pictureParameterSetLength) pictureParameterSetNALUnit;
   }
}

 

 

ok,数据结构贴出来了,我再贴出录制的3gp输出类型的h264码流片断。

阴影部分就是avcC的全部数据了。

其中: 0x61 0x76 0x63 0x43 就是字符avcC

0x01 是configurationVersion

0x42 是AVCProfileIndication

0x00 是profile_compatibility

0x1F是AVCLevelIndication

0xFF 是6bit的reserved 和2bit的lengthSizeMinusOne

0xE1 是3bit的reserved 和5bit的numOfSequenceParameterSets

0x00 0x09是sps的长度为9个字节。

故SPS的内容为接下来的9个字节:67 42 00 1f e9 02 c1 2c 80

接下来的:01为numOfPictureParameterSets

0x00和0x04是pps的长度为4个字节。

故PPS的内容为接下来的4个字节:68 ce 06 f2

 

通过这段数据片断,就可以获取到SPS和PPS了。

下面我将贴上用java代码获取输出格式为3gp的h264码流的SPS与PPS代码:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

package cn.edu.xmu.zgy;

import java.
io.File;
import java.
io.FileInputStream;
import java.
io.IOException;

public class ObtainSPSAndPPS {

    public void getSPSAndPPS(String fileName) throws IOException {
        File file = new File(fileName);
        FileInputStream fis = new FileInputStream(file);

        int fileLength = (int) file.length();
        byte[]
 fileData = new byte[fileLength];
        fis.read(fileData);

        // 'a'=0x61, 'v'=0x76, 'c'=0x63, 'C'=0x43
        byte[]
 avcC = new byte[] { 0x61, 0x76, 0x63, 0x43 };

        // avcC的起始位置
        
int avcRecord = 0;
        
for (int ix = 0; ix < fileLength; ++ix) {
            if (fileData[ix] == avcC[0] && fileData[ix + 1] == avcC[1]
                    &&
 fileData[ix + 2] == avcC[2]
                    &&
 fileData[ix + 3] == avcC[3]) {
                // 找到avcC,则记录avcRecord起始位置,然后退出循环。
                avcRecord 
= ix + 4;
                
break;
            }

        }
        if (0 == avcRecord) {
            System.out.println("没有找到avcC,请检查文件格式是否正确");
            
return;
        }


        // 7的目的是为了跳过
        
// (1)8字节的 configurationVersion
        
// (2)8字节的 AVCProfileIndication
        
// (3)8字节的 profile_compatibility
        
// (4)8 字节的 AVCLevelIndication
        
// (5)6 bit reserved
        
// (6)2 bit lengthSizeMinusOne
        
// (7)3 bit reserved
        
// (8)5 bit numOfSequenceParameterSets
        
// 6个字节,然后到达sequenceParameterSetLength的位置
        
int spsStartPos = avcRecord + 6;
        byte[]
 spsbt = new byte[] { fileData[spsStartPos],
                fileData[spsStartPos + 1] };
        int spsLength = bytes2Int(spsbt);
        byte[] SPS = new byte[spsLength];
        // 跳过2个字节的 sequenceParameterSetLength
        spsStartPos +
= 2;
        System.
arraycopy(fileData, spsStartPos, SPS, 0, spsLength);
        printResult("SPS", SPS, spsLength);

        // 底下部分为获取PPS
        
// spsStartPos + spsLength 可以跳到pps位置
        
// 再加1的目的是跳过1字节的 numOfPictureParameterSets
        
int ppsStartPos = spsStartPos + spsLength + 1;
        byte[]
 ppsbt = new byte[] { fileData[ppsStartPos],
                fileData[ppsStartPos + 1] };
        int ppsLength = bytes2Int(ppsbt);
        byte[] PPS = new byte[ppsLength];
        ppsStartPos += 2;
        System.
arraycopy(fileData, ppsStartPos, PPS, 0, ppsLength);
        printResult("PPS", PPS, ppsLength);
    }

    private int bytes2Int(byte[] bt) {
        int ret = bt[0];
        ret 
<<= 8;
        ret |
= bt[1];
        
return ret;
    }

    private void printResult(String type, byte[] bt, int len) {
        System.out.println(type + "长度为:" + len);
        String cont = type + "的内容为:";
        System.
out.print(cont);
        for (int ix = 0; ix < len; ++ix) {
            System.out.printf("%02x ", bt[ix]);
        }
        System.out.println("\n----------");
    }


    public static void main(String[] args) throws IOException {
        new ObtainSPSAndPPS().getSPSAndPPS("c:\\zgy.h264");
    }

}

 

 

运行结果如下:

 

1
2
3
4
5
6

SPS长度为:9
SPS
的内容为:67 42 00 1f e9 02 c1 2c 80 
----------

PPS长度为:4
PPS
的内容为:68 ce 06 f2 
----------

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值