在视频解码前,先了解以下几个基本的概念:

  • 解码器(CODEC):能够进行视频和音频压缩(CO)与解压缩(DEC),是视频编解码的核心部分。
  • 容器/多媒体文件(Container/File):没有了解视频的编解码之前,总是错误的认为平常下载的电影的文件的后缀(avi,mkv,rmvb等)就是视频的编码方式。事实上,刚才提到的几种文件的后缀
    并不是视频的编码方式,只是其封装的方式。一个视频文件通常有视频数据、音频数据以及字幕等,封装的格式决定这些数据在文件中是如何的存放的,封装在一起音频、视频等数据组成的多媒体文件,也可以叫做容器(其中包含了视音频数据)。所以,只看多媒体文件的后缀名是难以知道视音频的编码方式的。
  • 流数据 Stream,例如视频流(Video Stream),音频流(Audio Stream)。流中的数据元素被称为帧Frame

FFmpeg视频解码过程

通常来说,FFmpeg的视频解码过程有以下几个步骤:

  1. 注册所支持的所有的文件(容器)格式及其对应的CODEC av_register_all()
  2. 打开文件 avformat_open_input()
  3. 从文件中提取流信息 avformat_find_stream_info()
  4. 在多个数据流中找到视频流 video stream(类型为MEDIA_TYPE_VIDEO)
  5. 查找video stream 相对应的解码器 avcodec_find_decoder
  6. 打开解码器 avcodec_open2()
  7. 解码帧分配内存 av_frame_alloc()
  8. 从流中读取读取数据到Packet中 av_read_frame()
  9. 对video 帧进行解码,调用 avcodec_decode_video2()

解码过程的具体说明

1. 注册

av_register_all该函数注册支持的所有的文件格式(容器)及其对应的CODEC,只需要调用一次,故一般放在main函数中。也可以注册某个特定的容器格式,但通常来说不需要这么做。

2. 打开文件

avformat_open_input该函数读取文件的头信息,并将其信息保存到AVFormatContext结构体中。其调用如下

AVFormatContext* pFormatCtx = nullptr;

avformat_open_input(&pFormatCtx, filenName, nullptr, nullptr)

第一个参数是AVFormatContext结构体的指针,第二个参数为文件路径;第三个参数用来设定输入文件的格式,如果设为null,将自动检测文件格式;第四个参数用来填充AVFormatContext一些字段以及Demuxer的private选项。
AVFormatContext包含有较多的码流信息参数,通常由avformat_open_input创建并填充关键字段。

3. 获取必要的CODEC参数

avformat_open_input通过解析多媒体文件或流的头信息及其他的辅助数据,能够获取到足够多的关于文件、流和CODEC的信息,并将这些信息填充到AVFormatContext结构体中。但任何一种多媒体格式(容器)提供的信息都是有限的,而且不同的多媒体制作软件对头信息的设置也不尽相同,在制作多媒体文件的时候难免会引入一些错误。也就是说,仅仅通过avformat_open_input并不能保证能够获取所需要的信息,所以一般要使用

avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)

avformat_find_stream_info主要用来获取必要的CODEC参数,设置到ic->streams[i]->codec。
解码的过程中,首先要获取到各个stream所对应的CODEC类型和id,CODEC的类型和id是两个枚举值,其定义如下:

enum AVMediaType {

AVMEDIA_TYPE_UNKNOWN = -1,

AVMEDIA_TYPE_VIDEO,

AVMEDIA_TYPE_AUDIO,

AVMEDIA_TYPE_DATA,

AVMEDIA_TYPE_SUBTITLE,

AVMEDIA_TYPE_ATTACHMENT,

AVMEDIA_TYPE_NB

};

enum CodecID {

CODEC_ID_NONE,     /* video codecs */

CODEC_ID_MPEG1VIDEO,

CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding

CODEC_ID_MPEG2VIDEO_XVMC,

CODEC_ID_H261,

CODEC_ID_H263,

...

}

通常,如果多媒体文件具有完整而正确的头信息,通过avformat_open_input即可用获得这两个参数。

4. 打开解码

经过上面的步骤,已经将文件格式信息读取到了AVFormatContext中,要打开流数据相应的CODEC需要经过下面几个步骤

  • 找到视频流 video stream
    一个多媒体文件包含有多个原始流,例如 movie.mkv这个多媒体文件可能包含下面的流数据
  • 原始流 1 h.264 video
  • 原始流 2 aac audio for Chinese
  • 原始流 3 aac audio for English
  • 原始流 4 Chinese Subtitle
  • 原始流 5 English Subtitle

解码视频,首先要在AVFormatContext包含的多个流中找到CODEC类型为AVMEDIA_TYPE_VIDEO,代码如下:

//查找视频流 video stream

int videoStream = -1;

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

{

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

{

videoStream = i;

break;

}

}

if (videoStream == -1)

return -1; // 没有找到视频流video stream

结构体AVFormatContext中的streams字段是一个AVStream指针的数组,包含了文件所有流的描述,上述上述代码在该数组中查找CODEC类型为
AVMEDIA_TYPE_VIDEO的流的下标。

  • 根据codec_id找到相应的CODEC,并打开
    结构体AVCodecContext描述了CODEC上下文,包含了众多CODEC所需要的参数信息。
  • AVCodecContext* pCodecCtxOrg = nullptr;
  • AVCodec* pCodec = nullptr;
  • pCodecCtxOrg = pFormatCtx->streams[videoStream]->codec; // codec context
  • // 找到video stream的 decoder
  • pCodec = avcodec_find_decoder(pCodecCtxOrg->codec_id);
  • // open codec
  • if (avcodec_open2(pCodecCtxOrg , pCodec, nullptr) < 0)

return -1; // Could open codec

上述代码,首先通过codec_id找到相应的CODEC,然后调用avcodec_open2打开相应的CODEC。

5. 读取数据帧并解码

已经有了相应的解码器,下面的工作就是将数据从流中读出,并解码为没有压缩的原始数据

AVPacket packet;

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

{

if (packet.stream_index == videoStream)

{

int frameFinished = 0;

avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

if (frameFinished)

{

doSomething();

}

}

}

上述代码调用av_read_frame将数据从流中读取数据到packet中,并调用avcodec_decode_video2对读取的数据进行解码

6. 关闭

需要关闭avformat_open_input打开的输入流,avcodec_open2打开的CODEC

avcodec_close(pCodecCtxOrg);

avformat_close_input(&pFormatCtx);

补充

在配置好FFmpeg的开发环境后,在C++中使用FFmpeg的库函数,会出现解析不出函数的名称链接错误,这是由于FFmpeg库是C语言实现,要在C++调用C函数需要 extern "C"的声明。

extern "C"

{

# include <libavcodec\avcodec.h>

# include <libavformat\avformat.h>

# include <libswscale\swscale.h>

}

FFMpeg视频解码初探的更多相关文章

  1. FFmpeg学习1:视频解码

    在视频解码前,先了解以下几个基本的概念: 编解码器(CODEC):能够进行视频和音频压缩(CO)与解压缩(DEC),是视频编解码的核心部分. 容器/多媒体文件(Container/File):没有了解 ...

  2. 最简单的基于FFMPEG+SDL的视频播放器 ver2 (採用SDL2.0)

    ===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...

  3. 最简单的基于FFmpeg的解码器-纯净版(不包含libavformat)

    ===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...

  4. 最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)

    ===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...

  5. FFmpeg: FFmepg中的sws_scale() 函数分析

    FFmpeg中的 sws_scale() 函数主要是用来做视频像素格式和分辨率的转换,其优势在于:可以在同一个函数里实现:1.图像色彩空间转换, 2:分辨率缩放,3:前后图像滤波处理.不足之处在于:效 ...

  6. FFmpeg再学习 -- SDL 环境搭建和视频显示

    继续看雷霄骅的 课程资料 - 基于FFmpeg+SDL的视频播放器的制作 一.SDL 简介 参看:WIKI -- Simple DirectMedia Layer 参看:最简单的视音频播放示例9:SD ...

  7. FFmpeg再学习 -- FFmpeg解码知识

    继续看雷霄骅的 课程资料 - 基于FFmpeg+SDL的视频播放器的制作 前面用了五个篇幅来讲 FFmpeg,其主要目的是为实现将图片转视频的功能. 总的来说,对于 FFmepg 多少有一些了解了.但 ...

  8. 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)【转】

    转自:http://blog.csdn.net/leixiaohua1020/article/details/8652605 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] ...

  9. 最简单的基于FFMPEG的Helloworld程序

    ===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...

随机推荐

  1. Gym-100676F Palindrome

    原题连接:https://odzkskevi.qnssl.com/1110bec98ca57b5ce6aec79b210d2849?v=1491063604 题意: 多组输入,每一次输入一个n(字符串 ...

  2. sql:CallableStatement执行存储过程

    /** * 使用CablleStatement调用存储过程 * @author APPle * */ public class Demo1 { /** * 调用带有输入参数的存储过程 * CALL p ...

  3. Hive date_trunc函数

    The function date_trunc is conceptually similar to the trunc function for numbers. date_trunc('field ...

  4. Intel processor brand names-Xeon,Core,Pentium,Celeron----Xeon

    http://en.wikipedia.org/wiki/Comparison_of_Intel_processors Processor Series Nomenclature Code Name ...

  5. MDX members使用

    Members (Set) 函数返回该指定层次结构内所有成员(不包括计算成员)的集: Members (String) 函数返回已指定名称的单个成员. 通常,将 Members (String) 函数 ...

  6. 在线清空nohup.out内容

    通过 cat /dev/null > filename 命令可以在线清空nohup.out里的内容

  7. HTML块,含样式的标签

    HTML块,含样式的标签 html块 div标签 块元素,表示一块内容,没有具体的语义. span标签 行内元素,表示一行中的一小段内容,没有具体的语义. 含样式和语义的标签 em标签 行内元素,表示 ...

  8. docker的入门简介

    可能写的不是很完美,需要大家指正修改和意见(谢谢合作) docker的入门: docker的好处: 1.更快交付你的应用(Faster delivery of your applications) 2 ...

  9. Python之字符串转换为日期、结合时区的日期操作

    一.字符串转换为日期 方法一 s = '2019-01-20' print(datetime.strptime(s, '%Y-%m-%d')) # 2019-01-20 00:00:00 方法二 de ...

  10. Python3学习笔记——自定义模块

    import sys import os print(__file__) #打印相对路径 base_dir = os.path.dirname(os.path.dirname(os.path.absp ...