解密多媒体封装解封装框架

上一篇文章我们搭好了环境并编译出所需的ffmpeg库,本篇我们讨论如何利用ffmpeg提供的API函数进行多媒体文件的解封装(demux)过程。在讲解之前,我们需要了解一些基本的多媒体文件知识,大虾请飘过。

  • 容器格式:不管是音频文件还是视频格式的文件,都是一个多媒体的容器,即container,比如常见的视频容器格式有avi、mp4、mkv、flv、rm/rmvb、mov、ts、vob、dat,音频容器格式有MP3、WAV、AAC、APE,FLAC等等,它容纳了视频、音频、字幕(subtitle)等一个或多个基本流数据,有的甚至一个容器中存放有多个视频、音频以及字幕。
  • 压缩格式:对视频、音频数据的基本流进行的压缩方式就是音视频的压缩格式。常见的视频压缩格式如mpeg2、mpeg4、H264、VC1、Rm/Rmvb,常见音频压缩格式如MPA、AAC、AC3、DTS。注意这里的部分名字和上面的一样,但意义不同,上面是封装格式,这里是压缩格式。为什么要压缩呢?因为不压缩的话,要存储图像或声音就需要非常多的空间,比如mpeg2压缩比能达到25:1左右,而H264甚至能达到102:1的惊人程度!
  • ES:也就是ElementaryStream,也称为基本流、组件流等称呼,就是单独的一路视频、一条音频、一个subtitle字幕或者单个的附加数据。显然常见的多媒体文件一个都有一个视频ES、音频ES,有的也含有多个视频ES和音频ES以及subtitleES。比如蓝光原版的TS一般都含有多个音轨ES和字幕ES,但不是所有有字幕都有字幕ES,可能字幕已经内嵌进视频,这样的字幕其实成了视频的一部分。
  • Demux:在播放时,需要把这些视音频以及字幕等基本流分离出来,这个过程就叫Demux,或者解封装,也称为解复用。分离出来的各个基本流(ES)分别送给视频解码器、音频解码器等解码后才能得到图像声音。Demux过程如下图(subtitle也可能需要解码):

  • Remux:当然Demux反过来把基本的音频、视频、字幕等组合成一个完整的多媒体就是Remux或者封装,也称为复用。比如很多电影网站的音视频压制的人就需要先做Demux,分离成ES,在加入必要的中文字幕和音轨后、重新封装。所有的转码工具也都必须有Remux和重新Demux的过程。复用与解复用的概念对于熟悉DVB行业的读者来说应该比较清楚。
  • PTS:也就是显示时间戳,指图像或者声音在解码后应该显示或者发声的时间点。音视频不是一解码出来就播出来,否则就乱了,性能好的解码器播放的快,差的播放的慢,并且视频和音频也对不上号。所有这些都是靠PTS来同步的。至于DTS解码时间戳在现在相对以前较大解码内存缓冲下,显得不那么重要了。

有了这些基本的多媒体知识,我们就可以继续讲解如何利用ffmpeg来进行Demux这个过程。首先介绍一下主要的几个API函数:

intavformat_open_input(AVFormatContext **ps, const char *filename,

AVInputFormat *fmt, AVDictionary **options);

这个函数用于打开多媒体文件,并读取相关文件头信息。

voidavformat_close_input(AVFormatContext **ps);

这个函数用于关闭上面打开的多媒体文件,释放相关资源。

intavformat_find_stream_info(AVFormatContext *ic, AVDictionary**options);

这个函数通过注册的文件格式解析器读取文件的取各种信息,比如播放持续时间、音视频压缩格式、音轨信息、字幕信息、帧率、采样率等等。

int av_read_frame(AVFormatContext*s, AVPacket *pkt);

这个函数对于Demux过程是最重要的一个函数,它从文件中读取一帧视频、一帧或多帧音频、字幕等ES数据包,除了数据本身之外,还包括PTS、持续时间、参考帧等重要信息。

void av_free_packet(AVPacket *pkt);

这个函数用于释放ES数据包,与上面的函数成对使用。

有了这些函数和上面的基本知识,下面我们来实现一个简单的Demux框架实例。这个实例的功能是把多媒体文件中的音视频ES数据抽出来分别写入不同文件。我们为了简单,这里不处理返回错误,在实际项目中自己添加错误处理机制。本文力求用最简单最原始的方式把ffmpeg解封装的基本框架讲解清楚。

?


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

#include <stdio.h>

#include "libavformat/avformat.h"

static  const  char  *media_file =  "test_media.mp4" ;

int  main( void )

{

int  i, vid_idx, aud_idx;

FILE  *fp_vides = NULL, *fp_audes = NULL;

AVFormatContext *pFormatCtx = NULL;

AVPacket pkt;

av_register_all();

avformat_open_input(&pFormatCtx, media_file, NULL, NULL);

avformat_find_stream_info(pFormatCtx, NULL);

fp_vides =  fopen ( "vid_es.dat" "wb" );

fp_audes =  fopen ( "aud_es.dat" "wb" );

// 1, handle stream info

for  (i=0; i<pFormatCtx->nb_streams; i++)

{

if  (pFormatCtx->streams[i]->codec->codec_type ==AVMEDIA_TYPE_VIDEO)

vid_idx = i;

else  if  (pFormatCtx->streams[i]->codec->codec_type ==AVMEDIA_TYPE_AUDIO)

aud_idx = i;

else

; //such as subtitile

}

while  (av_read_frame(pFormatCtx, &pkt) >= 0)

{

// 2, handle pkt data

if  (pkt.stream_index == vid_idx)

fwrite (pkt.data, pkt.size, 1, fp_vides);

else  if  (pkt.stream_index == aud_idx)

fwrite (pkt.data, pkt.size, 1, fp_audes);

else

; // such as subtitile

av_free_packet(&pkt);

}

fclose (fp_vides);

fclose (fp_audes);

avformat_close_input(&pFormatCtx);

return  0;

}

在注释1的地方,需要处理基本流索引与音视频对应的关系和重要信息记录,这个关系会在注释2的地方用到,并且也是后续的多音轨、字幕切换的凭据,本例只处理了最简单的只有一路音视频的情况,且没有对其他信息进行记录,比如帧率、视频宽高、编码类型、时间标度、第一个PTS等等。原则上这些跟Demux的框架没有关系,且每个人有有自己的处理方式,就不在这里贴出来。

第一时间获得博客更新,获得更详细信息和Demo代码,请关注微信号:程序员互动联盟,扫一扫下方二维码或者搜索微信号coder_online即可关注,我们可以在线交流。

时间: 2024-07-31 02:23:30

解密多媒体封装解封装框架的相关文章

互联数据包封装和解封装过程

一.互联数据包封装/解封装过程(OSI) 一言不合来张图: 注意:1.mac地址只在本地有效,通过路由器传输过程中,mac地址会发生改变2.路由器根据路由表识别目标IP地址网段信息,确认是否可以进行转发,或是进行数据包的丢弃 二.TCP/IP协议簇(DOD) 1.tcp和udp对比: tcp:传输控制协议(属于面向连接网络协议)同步传输--"在线"速度慢,安全如:WEB浏览器 电子邮件 文件传输程序udp:用户数据报协议(属于无连接传输协议)异步传输--"离线"速度

利用Java针对MySql封装的jdbc框架类 JdbcUtils 完整实现(包含增删改查、JavaBean反射原理,附源码)

最近看老罗的视频,跟着完成了利用Java操作MySql数据库的一个框架类JdbcUtils.java,完成对数据库的增删改查.其中查询这块,包括普通的查询和利用反射完成的查询,主要包括以下几个函数接口: 1.public Connection getConnection()   获得数据库的连接 2.public boolean updateByPreparedStatement(String sql, List<Object>params)throws SQLException  更新数据库

OSI互联数据包封装与解封装

当我们在七层协议最上层,主机A想和其它主机通信, 比如telnet到主机B,各层都为数据打包后再封装上自己能识别的数据标签,现在我们只说四层以下的通信过程. 1.当一个高层的数据包到达传输层,由于telnet使用TCP协议,传输层将上层传过来的数据不变再封装TCP的包头以便目标主机可以正确解包,继续向下层(网络层)传递. 2.网络层同样不会改变之前的数据包,当然也包括之前封装的任何包头,首先主机A要对目标主机作判断,他会用自己的IP地址和自己的子网掩码进行与运算结果是172.16.12.0,然后

App 组件化/模块化之路——如何封装网络请求框架

App 组件化/模块化之路——如何封装网络请求框架 在 App 开发中网络请求是每个开发者必备的开发库,也出现了许多优秀开源的网络请求库.例如 okhttp retrofit android-async-http 这些网络请求库很大程度上提高程序猿的编码效率.但是随着业务的发展,App 变得越来越大,我们将这些网络请求库加入到项目中直接使用,对我们业务类的入侵是非常强的.如果要进行业务分离时,这些网络请求代码将是一个阻止我们进一步工作的绊脚石.对开发者来说是非常痛苦的. 因此我们构建的网络请求框

sk_buff封装和解封装网络数据包的过程详解

可以说sk_buff结构体是Linux网络协议栈的核心中的核心,几乎所有的操作都是围绕sk_buff这个结构体进行的,它的重要性和BSD的mbuf类似(看过<TCP/IP详解 卷2>的都知道),那么sk_buff是什么呢?       sk_buff就是网络数据包本身以及针对它的操作元数据.       想要理解sk_buff,最简单的方式就是凭着自己对网络协议栈的理解封装一个直到以太层的数据帧并且成功发送出去,个人认为这比看代码/看文档或者在网上搜资料强多了.当然,网上已经有了大量的这方面的

sk_buff封装和解封装网络数据包的过程详解(转载)

http://dog250.blog.51cto.com/2466061/1612791 可以说sk_buff结构体是Linux网络协议栈的核心中的核心,几乎所有的操作都是围绕sk_buff这个结构体进行的,它的重要性和BSD的mbuf类似(看过<TCP/IP详解 卷2>的都知道),那么sk_buff是什么呢?       sk_buff就是网络数据包本身以及针对它的操作元数据.       想要理解sk_buff,最简单的方式就是凭着自己对网络协议栈的理解封装一个直到以太层的数据帧并且成功发

封装与解封装

封装:将数据变为比特流的过程中,在参考模型的每一层需要添加上特定的协议报头动作 动作:从高层往低层依次封装,在每一层使用特定的协议,对数据进行处理,在数据前添加特定的协议报头. 封装原则: 1:每一层在上一层数据前添加协议报头 2:添加完协议报头的整体,就是该层的PDU 3:每一层的PDU对于下一层来说就是上层数据(每一层的上层数据就是上层的PDU) PDU(协议数据单元,也就是每层的数据单位) 表示层:APDU 会话层:PPDU 传输层:SPDU 传输层:段(segment) 网络层:包(pa

OSI七层模型、数据封装与解封装过程、TCP三次握手、四次挥手

作者:Georgekai 归档:学习笔记 2018/1/16 网络运维基础(二) 1.1 OSI七层模型  应用层:应用程序与接口(如qq和其他三方软件的对接--对应设备(计算机) 协议:http dns  telnet   nfs ftp tftp   smtp(25)  snmp(161) 表示层:表示数据的格式.压缩.加密 会话层:作用:建立.维护.管理应用程序之间的会话. 功能:对话控制.同步 传输层:作用:负者建立端到端的连接.保证报文在端到端之间的传输.--对应设备(防火墙) 功能:

封装解构,集合,字典,内建函数和简单选择排序相关知识及习题

封装 将多个值使用逗号分割,组合在一起本质上,返回一个元组,只是省掉了小括号python特有语法,被很多语言学习和借鉴 解构 把线性结构的元素解开,并顺序的赋给其它变量左边接纳的变量数要和右边解开的元素个数一致 转置矩阵 方阵利用封装解构完成代码矩阵先开辟空间再利用封装解构完成代码利用enumerate函数得到索引值完成代码 集合 可变的 . 无序的 . 不重复 的元素的集合set的元素要求必须可以hash元素不可以索引set可以迭代set增加 add(elem)增加一个元素到set中,如果元素