(二) FFmpeg解码视频学习
目录
- 目录
- 前言
- 概述
FFmpeg结构体和函数
FFmpeg的关键结构体
- FFmpeg关键结构体概述
- FFmpeg结构体详解
- FFmpeg的函数
- 解码视频代码
- 总结
- 链接地址
前言
概述
编码(encode):通过特定的压缩技术,将某个视频的视频流格式转换成另一种视频格式的视频流方式。比如视频:YUV420/422->h264;音频:PCM(原始)->AAC
解码(decode):通过特定的解压缩技术,将某个视频格式的视频流转换成另一种视频格式的视频流方式。
转码(transcode):视频转码技术将视频信号从一种格式转换成另一种格式。比如:视频包括改变分辨率;改变帧率;改变比特率(bir rate)等编码参数;音频包括改变采样率;改变通道数(双通道,单通道);改变位宽(sample format)。
封装(mux):复用,按一定格式组织原始音视频流。
解封装(demux):解复用,按一定格式解析出原始音视频流。
封装可以看做是对原始数据的一些解释,就如同bmp图像数据有文件头一样。
ES流:原始流,直接从编码器出来的数据流。
rtsp流:RTSP(Real Time Streaming Protocol) RFC2326s,实时流传输协议,是TCP/IP协议系中的一个应用层协议。
rtmp流:Real Time Messaging Protocol(实时消息传输协议),是adobe公司的协议
服务端:服务端是为客户端服务的,向客户端提供资源,保存客户端数据。
客户端:也可以称用户端,与服务端相对应,为客户提供本地服务的程序。
流媒体:采用流式传输方式在Internet播放的媒体格式。
推模式:当通知消息来之时,把所有相关信息都通过参数的形式”推给”观察者。就比如微信公众号的推送通知。
拉模式:当通知消息来之时,通知的函数不带任何相关的信息,而是要观察者主动去”拉”信息。就比如我们刷微博。
实时流:Real Time stream 实时传输的音视频流。比如直播。
FFmpeg结构体和函数
FFmpeg的关键结构体
FFmpeg关键结构体概述
- 解协议(http,rtsp,rtmp,mms)
AVIOContext
,URLContext
,URLContext
主要存储视音频使用的协议类型以及状态。URLProtocol
存储输入视音频使用的封装格式。每种协议都对应URLProtocol
结构。(注意:FFmpeg
中文件也被当作一种协议) - 解封装(flv,avi,rmvb,mp4)
AVFormatContext
主要存储视音频封装格式中包含的信息;AVInputFormat
存储输入视音频使用的封装格式。每种视音频的封装格式都对应一个AVInputFormat
。 - 解码(h264,mpeg2,aac, mp3)
每个AVStream
存储一个视频/音频流的相关数据;每个AVStream
对应于AVCodecContext
,存储该视频/音频流使用解码方式的相关数据;每个AVCodecContext
中对应一个AVCodec
,包含该视频/音频对应的解码器。每种解码器都对应一个AVCodec
结构。 - 存数据
视频的话,每个结构一般是存一帧;音频可能是好几帧。
解码前数据:AVPacket
解码后数据:AVFrame
从图中可以看出AVFormat Context
这个结构体包含协议层结构体,封装层结构体,编码层结构体等。总之,这个结构体中贯穿编解码过程。
FFmpeg结构体详解
AVFormatContext
AVFormatContext
是FFmpeg解封装(flv,mp4,rmvb,avi)功能的结构体。struct AVInputFormat iformat:输入数据的封装格式
AVIOContext pb:输入数据缓存
unsigned int nb_streams:视音频流的个数
AVStream **streams:视音频流
char filename[1024]:文件名
FFMPEG结构体分析:AVFormatContext
AVIOContext
AVIOContext
是FFmpeg管理输入输出数据的结构体。unsigned char buffer:缓存开始位置
int buffer_size:缓存大小
unsigned char buf_ptr:当前指针读取到的位置
unsigned char buf_end:缓存结束的位置
void opaque:URLContext结构体,URLContext结构体中有一个结构体URLProtocol.注:每种协议(rtp,rtmp,file等)对应一个URLProtocol.
在解码的情况下,buffer
用于存储ffmpeg读入的数据。例如:打开一个视频文件的时候,先把数据从硬盘读入buffer
,然后送给解码器用于解码。
FFMPEG结构体分析:AVIOContext
AVStream
AVStream
是存储每一个视频/音频流信息的结构体。int index:标识该视频/音频
AVCodecContex *codec:指向该视频/音频流的AVCodecContext
int64_t duration:该视频/音频流长度
FFMPEG结构体分析:AVStream
AVCodecContext
enum AVMeiaType codec_type:编解码器类型(视频、音频)
enum AVMediaType {AVMEDIA_TYPE_UNKNOWN = -1, ///< Usually treated as AVMEDIA_TYPE_DATA
AVMEDIA_TYPE_VIDEO,
AVMEDIA_TYPE_AUDIO,
AVMEDIA_TYPE_DATA, ///< Opaque data information usually continuous
AVMEDIA_TYPE_SUBTITLE,
AVMEDIA_TYPE_ATTACHMENT, ///< Opaque data information usually sparse
AVMEDIA_TYPE_NB
};
enum AVMediaType codec_type; // 编码器类型
enum AVSampleFormat sample_fmt; // 采样格式
每个AVStream
存储一个视频/音频流的相关数据;每一个AVStream
对应一个AVCodecContext
,存储该视频/音频流使用解码方式的相关数据;每个AVCodecContext
中对应一个AVCodec
,包含该视频/音频对应的解码器。每种解码器都对应一个AVCodec
结构。
FFMPEG结构体分析:AVCodecContext
AVCodec
AVCodec
是视频/音频对应的解码器。const char name:编解码器的名字
const char long_name:编解码其的名字,全称
enum AVMediaType type: 指明类型,视频,音频,字母
enum AVCodecID id:ID
const AVRational supported_framerates:支持的帧率(仅视频)
const enum AVPixelFormat pix_fmts:支持的像素格式(仅视频)
const int supported_samplerates:支持的采样率(仅音频)
const enum AVSampleFormat sample_fmts:支持的采样格式(仅音频)
const uint64_t *channel_layouts:支持的声道数(仅音频)
int priv_data_size:私有数据的大小
每一个编解码器对应一个该结构体:H.264解码器
AVCodec ff_h264_decoder = {
.name = "h264",
.type = AVMEDIA_TYPE_VIDEO,
.id = CODEC_ID_H264,
.priv_data_size = sizeof(H264Context),
.init = ff_h264_decode_init,
.close = ff_h264_decode_end,
.decode = decode_frame,
.capabilities = /*CODEC_CAP_DRAW_HORIZ_BAND |*/ CODEC_CAP_DR1 | CODEC_CAP_DELAY | CODEC_CAP_SLICE_THREADS | CODEC_CAP_FRAME_THREADS,
.flush= flush_dpb,
.long_name = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
.init_thread_copy = ONLY_IF_THREADS_ENABLED(decode_init_thread_copy),
.update_thread_context = ONLY_IF_THREADS_ENABLED(decode_update_thread_context),
.profiles = NULL_IF_CONFIG_SMALL(profiles),
.priv_class = &h264_class,
};
遍历ffmpeg中的解码器信息的方法:
- 注册所有的编解码器:av_register_all();
- 声明一个
AVCodec
类型的指针,比如:AVCodec* first_c;
调用
av_codec_next()
函数,即可获得指向链表下一个解码器的指针,循环往复可以获得所有解码器的信息。注意,如果想要获得指向第一个解码器的指针,则需要将该函数的参数设置为NULL.
FFMPEG结构体分析:AVCodec
-AVPacket
AVPacket
是存储压缩编码数据相关信息的结构体。uint8_t *data:压缩编码的数据
int size:data的大小
int64_t pts:显示时间戳
int64_t dts:解码时间戳
int stream_index:标识该AVPacket所属的视频/音频流
FFMPEG结构体分析:AVPacket
AVFrame
AVFrame
结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV/RGB,对音频来说是PCM),此外还包含一些相关信息。比如:解码时存储宏块类型表,QP表,运动矢量表等数据。编码时也存储了相关数据。比如:typedef struct AVFrame
{define AV_NUM_DATA_POINTERS 8
uint8_t* data [AV_NUM_DATA_POINTERS]; // 指向图像数据
int linesize [AV_NUM_DATA_POINTERS]; // 行的长度
int width; //图像的宽
int height; //图像的高
int format; //图像格式
……
}AVFrame;
AVFrame* frame = av_frame_alloc(); // 只是分配AVFrame结构体 data指向的内存并没有分配
int bytes_num = avpicture_get_size(AV_PIX_FMT_YUV420P, width, height); // AV_PIX_FMT_YUV420P是FFmpeg定义的标明YUV420P图像格式的宏定义
// 这个函数中会帮我们计算这个格式的图片大小// 申请空间来存放图片数据。包含源数据和目标数据
uint8_t buff = (uint8_t)av_malloc(bytes_num);// 把AVFrame结构体指针的图像数据指针赋值,也就是给图像数据指针和指向的内存之间做关联
avpicturefill((AVPicture*)frame, buff, AV_PIX_FMT YUV420P,width, height);
uint8_t* data [AV_NUM_DATA_POINTERS];
指向图像数据,图像有存储方式有planar
和packed
两种模式。对于planar
模式的YUV
,data[0]
指向Y分量的开始位置、data[1]
指向U分量的开始位置、data[2]
指向V分量的开始位置。对于packed
模式YUV
,data[0]
指向数据的开始位置,而data[1]
和data[2]
都为NULL
。
FFMPEG结构体分析:AVFrame
图像视频编码和FFmpeg(3)—–用FFmpeg进行图像格式转换和AVFrame简介
SwsContext
记录进行图像格式转换时,源图像和目标图像的格式、大小分别是什么。
FFmpeg的函数
av_register_all()
av_register_all()
用于注册复用器,该函数在所有基于ffmpeg的应用程序中几乎都是第一个被调用的。只有调用了该函数,才能使用复用器,编码器等。avformat_open_input()
avformat_open_input
用于打开多媒体数据并且获得一些相关的信息。int avformat_open_input(AVFormatContext ps, const char filename, AVInputFormat fmt, AVDictionary options);
ps: 函数调用之后处理过的AVFormatContext
结构体
file: 打开的视音频流的URL
fmt:强制指定AVFormatContext中AVInputFormat。这个参数一般情况下可以设置为NULL
dictionary:附加的一些选项,一般情况下可以设置为NULL.
在这个函数中,FFmpeg完成了:
- 输入输出结构体AVIOContext的初始化
- 输入数据的协议的识别(例如:RTMP, 或者file)的识别:判断文件名后缀;读取文件头的数据进行比对。
- 使用获得最高分的文件协议对应的URLProtocol,通过函数指针的方式,与FFmpeg连接。
调用该URLProtocol的函数进行open, read等操作。
-avformat_find_stream_info()
avformat_find_stream_info()
可以读取一部分视音频数据并且获得一些相关信息。这个函数主要用于给每个媒体流(音频/视频)的AVStream
结构体赋值。据雷神说这个函数已经实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作。换句话说,该函数实际上已经”走通”了解码的整个流程。avcodec_open2()
1) 为各种结构体分配内存
2) 将输入的AVDictionary
形式的选项设置为AVCodecContext
3) 一些其他检查,比如检测编解码其是否处于”实验”阶段
4) 如果是编码器,检测输入参数是否符合编码器的要求
5) 调用AVCodec
的init()
初始化具体的解码器sws_getContext()
libswscale
主要用于处理图片像素数据的类库,可以完成图片像素格式的转换,图片的拉伸等工作。sws_getContext()
用于初始化一个SwsContext
:struct SwsContext sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags, SwsFilter srcFilter, SwsFilter dstFilter, const double param);
srcW:源图像的宽
srcH:源图像的高
srcFormat:源图像的像素格式
dstW:目标图像的宽
dstH:目标图像的高
dstFormat:目标图像的像素格式
flags:设定图像拉伸使用的算法avcodec_decode_video2()
avcodec_decode_video2()
的作用是实现压缩视频的解码,解码一帧视频数据。输入一个压缩编码的结构体AVPacket
,输出一个解码后的结构体AVFrame
。int avcodec_decode_video2(AVCodecContext avctx, AVFrame picture, int got_picture_ptr, const AVPacket avpkt);
AVCodecContext avctx:编解码上下文环境,定义了编解码操作的一些细节;
AVFrame picture:输出参数,传递到该方法的对象本身必须在外部有av_frame_alloc()分配空间。
got_picture_ptr:该值为0表示没有图像可以解码,否则表明有图像可以解码。
avpkt:输入参数,包含待解码数据
FFmpeg源代码简单分析:libswscale的sws_getContext()
解码视频代码
参考这篇从零开始学习音视频编程技术(五) 使用FFMPEG解码视频之保存成图片博文。我直接在Ubuntu下成功运行了这篇博文中运行的代码,但是ppm格式不好打开,正好之前有写过保存成bmp图片的文件,但是写得不是很好,顺便把这一部分整理一下!顺便测试一下解码视频是否成功!附上修改的保存bmp
的函数!bmp格式参考 linux下位图结构解析(二)
bool saveAsBitMap(AVFrame* pFrameRGB, int width, int height, int iFrame)
{
FILE *pFile = NULL;
BITMAPFILEHEADER_t bmpHeader;
BITMAPINFOHEADER_t bmpInfoHeader;
char fileName[32];
int bpp = 24;
// open file
sprintf(fileName, "frame%d.bmp", iFrame);
pFile = fopen(fileName, "wb");
if(!pFile)
{
return false;
}
bmpHeader.bfType = ('M'<<8)|'B'; // 0x4D42
bmpHeader.bfReserved = 0;
bmpHeader.bfOffBits = sizeof(BITMAPFILEHEADER_t)+sizeof(BITMAPINFOHEADER_t);
bmpHeader.bfSize = bmpHeader.bfOffBits + width*height*bpp/8;
bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER_t);
bmpInfoHeader.biWidth = width;
bmpInfoHeader.biHeight = -height;
bmpInfoHeader.biPlanes = 1;
bmpInfoHeader.biBitCount = bpp;
bmpInfoHeader.biCompression = 0;
bmpInfoHeader.biSizeImage = 0;
bmpInfoHeader.biXPelsPerMeter = 0;
bmpInfoHeader.biYPelsPerMeter = 0;
bmpInfoHeader.biClrUsed = 0;
bmpInfoHeader.biClrImportant = 0;
fwrite(&bmpHeader, sizeof(BITMAPFILEHEADER_t), 1, pFile);
fwrite(&bmpInfoHeader, sizeof(BITMAPINFOHEADER_t), 1, pFile);
uint8_t *buffer = pFrameRGB->data[0];
for(int h=0; h<height; h++)
{
for(int w=0; w<width; w++)
{
fwrite(buffer+2, 1, 1, pFile);
fwrite(buffer+1, 1, 1, pFile);
fwrite(buffer, 1, 1, pFile);
buffer += 3;
}
}
fclose(pFile);
return true;
}
总结
网上各种查资料,就着前辈们的代码,完成了FFmpeg解码视频的学习。虽然只是刚刚跑通了一个小例子。但是也算是个小入门了!在这个时代,资源比以前多很多,感谢大神们的无私奉献!同时也要明白资源好找,别人可以很快达到这个水平,要更加努力才行呢!
链接地址
从零开始学习音视频编程技术(五) 使用FFMPEG解码视频之保存成图片
还没有评论,来说两句吧...