在了解ffmpeg的模块以及各自的功能以后(最好还是了解一下提供出来的接口函数,各个模块的.h文件有记录)
接下来就来说说播放器这一类,播放器主要就是解码流程以及显示了,下面开始进行分析。
这里主要的目的还是简单解决平台的问题
#ifdef
_WIN32
//Windows
extern
"C"
{
#include
"libavcodec/avcodec.h"
#include
"libavformat/avformat.h"
#include
"libswscale/swscale.h"
#include
"libavutil/imgutils.h"
#include
"SDL2/SDL.h"
};
#else
//Linux...
#ifdef
__cplusplus
extern
"C"
{
#endif
#include
<libavcodec/avcodec.h>
#include
<libavformat/avformat.h>
#include
<libswscale/swscale.h>
#include
<SDL2/SDL.h>
#include
<libavutil/imgutils.h>
#ifdef
__cplusplus
};
#endif
#endif
****************************************************************************************************************************************************
这里有一个技巧,在我们编写C一类的代码程序的时候,可以定义几个宏来当做一个编译开关。
类似于:
#define
OUTPUT_YUV420P
0
这里 如果我希望将解码以后的YUV240P数据保存为一个文件 只要将上面的宏改为1即可。
****************************************************************************************************************************************************
在这里 我们主要涉及几个结构体(这里只说明ffmpeg相关的结构体,sdl的就不说明了)
AVFormatContext
是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体。
AVCodecContext
AVCodecContext
是包含变量较多的结构体
AVCodec
AVCodec
是存储编解码器信息的结构体
AVFrame
一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。
AVPacket
AVPacket
是存储压缩编码数据相关信息的结构体
下文简单分析一下上述几个结构体的初始化和销毁函数。这些函数列表如下。
结构体
|
初始化
|
销毁
|
AVFormatContext
|
avformat_alloc_context()
|
avformat_free_context()
|
AVIOContext
|
avio_alloc_context()
|
|
AVStream
|
avformat_new_stream()
|
|
AVCodecContext
|
avcodec_alloc_context3()
|
|
AVFrame
|
av_frame_alloc();
av_image_fill_arrays()
|
av_frame_free()
|
AVPacket
|
av_init_packet();
av_new_packet()
|
av_free_packet()
|
****************************************************************************************************************************************************
接着就来说一下具体的流程
解码一个视频流的过程如下图
sdl2 显示yuv数据的流程图
****************************************************************************************************************************************************
首先,在定义好各种变量、结构体以后,就开始调用
av_register_all()函数,
该函数在所有基于ffmpeg的应用程序中几乎都是第一个被调用的。只有调用了该函数,才能使用复用器,编码器等
我们来看下这个函数,函数的定义是在libavformat目录下的avformat.h
/**
* Initialize libavformat and register all the muxers, demuxers and
* protocols. If you do not call this function, then you can select
* exactly which formats you want to support.
* 初始化libavFormat并注册所有的muxers、demuxers和协议。如果您不调用这个函数,那么您可以选择您想要支持的格式。
*
* @see av_register_input_format()
* @see av_register_output_format()
*/
void av_register_all(void);
函数的实现是在
libavformat目录下的allformats.c
void av_register_all(void)
{
static AVOnce control = AV_ONCE_INIT;
ff_thread_once(&control, register_all);
}
其实可以看得出来
函数通过
ff_thread_once()
调用
register_all() 而
register_all就
在
libavformat目录下的allformats.c (其实就在上边)
不难看出,这个函数会调用
avcodec_register_all();
一大堆的
REGISTER_MUXER ()
REGISTER_DEMUXER()
这个是解复用器的函数,可以看一个宏定义
#define REGISTER_MUXER(X, x) \
{ \
extern AVOutputFormat ff_##x##_muxer; \
if (CONFIG_##X##_MUXER) \
av_register_output_format(&ff_##x##_muxer); \
}
#define REGISTER_DEMUXER(X, x) \
{ \
extern AVInputFormat ff_##x##_demuxer; \
if (CONFIG_##X##_DEMUXER) \
av_register_input_format(&ff_##x##_demuxer); \
}
可以看得出来,实际上是调用(
define里面的##可能不太常见,它的含义就是拼接两个字符串
)
av_register_output_format(&ff_##x##_muxer);
av_register_input_format(&ff_##x##_demuxer);
****************************************************************************************************************************************************
再走深入一点,
avcodec_register_all()这个函数其实和
av_register_all() 差不多
是在libavcodec目录下的allcodecs.c实现的
void avcodec_register_all(void)
{
static AVOnce control = AV_ONCE_INIT;
ff_thread_once(&control, register_all);
}
函数通过
ff_thread_once()
调用
register_all() 而
register_all就
在
libavcodec目录下的allcodecs.c
(其实就在上边)
register_all就会调用各种硬件加速器、编码器、解码器以及解析器
#define REGISTER_HWACCEL(X, x) \
{ \
extern AVHWAccel ff_##x##_hwaccel; \
if (CONFIG_##X##_HWACCEL) \
av_register_hwaccel(&ff_##x##_hwaccel); \
}
#define REGISTER_ENCODER(X, x) \
{ \
extern AVCodec ff_##x##_encoder; \
if (CONFIG_##X##_ENCODER) \
avcodec_register(&ff_##x##_encoder); \
}
#define REGISTER_DECODER(X, x) \
{ \
extern AVCodec ff_##x##_decoder; \
if (CONFIG_##X##_DECODER) \
avcodec_register(&ff_##x##_decoder); \
}
#define REGISTER_ENCDEC(X, x) REGISTER_ENCODER(X, x); REGISTER_DECODER(X, x)
#define REGISTER_PARSER(X, x) \
{ \
extern AVCodecParser ff_##x##_parser; \
if (CONFIG_##X##_PARSER) \
av_register_codec_parser(&ff_##x##_parser); \
}
这里边又迁出了几个函数
av_register_hwaccel(&ff_##x##_hwaccel);
avcodec_register(&ff_##x##_encoder);
avcodec_register(&ff_##x##_decoder);
av_register_codec_parser(&ff_##x##_parser);
****************************************************************************************************************************************************
之后就来看下之前说的两个函数
av_register_output_format(&ff_##x##_muxer);
av_register_input_format(&ff_##x##_demuxer);
这两个函数是在libavformat目录下的format.c实现的
void av_register_input_format(AVInputFormat *format)
{
AVInputFormat **p = last_iformat;
// Note, format could be added after the first 2 checks but that implies that *p is no longer NULL
while(p != &format->next && !format->next && avpriv_atomic_ptr_cas((void * volatile *)p, NULL, format))
p = &(*p)->next;
if (!format->next)
last_iformat = &format->next;
}
void av_register_output_format(AVOutputFormat *format)
{
AVOutputFormat **p = last_oformat;
// Note, format could be added after the first 2 checks but that implies that *p is no longer NULL
while(p != &format->next && !format->next && avpriv_atomic_ptr_cas((void * volatile *)p, NULL, format))
p = &(*p)->next;
if (!format->next)
last_oformat = &format->next;
}
关于av_register_all()就先分解到这
****************************************************************************************************************************************************
之后就接着来
avformat_network_init()函数 (这个函数其实需要具体来看了)
函数的定义是在libavformat目录下的avformat.h
/**
* Do global initialization of network components. This is optional,
* but recommended, since it avoids the overhead of implicitly
* doing the setup for each session.
* 对网络组件进行全局初始化。这是可选的,但建议这样做,因为它避免了隐式为每个会话执行设置的开销。
*
* Calling this function will become mandatory if using network
* protocols at some major version bump.
* 如果在某些主要版本中使用网络协议,则调用此函数将成为强制性的。
*/
int avformat_network_init(void);
函数的实现是在
libavformat目录下的utils.c
int avformat_network_init(void)
{
#if CONFIG_NETWORK
int ret;
ff_network_inited_globally = 1;
if ((ret = ff_network_init()) < 0)
return ret;
if ((ret = ff_tls_init()) < 0)
return ret;
#endif
return 0;
}
****************************************************************************************************************************************************
好了 接下来看
avformat_alloc_context()
这个函数一看就是为了给
AVFormatContext结构体开辟内存空间的
函数的定义是
在libavformat目录下的avformat.h
/**
* Allocate an AVFormatContext. 分配一个avformatcontext。
* avformat_free_context() can be used to free the context and everything
* allocated by the framework within it.
*/
AVFormatContext *avformat_alloc_context(void);
函数的定义是在
libavformat目录下的options.c
AVFormatContext *avformat_alloc_context(void)
{
AVFormatContext *ic;
ic = av_malloc(sizeof(AVFormatContext));
if (!ic) return ic;
avformat_get_context_defaults(ic);
ic->internal = av_mallocz(sizeof(*ic->internal));
if (!ic->internal) {
avformat_free_context(ic);
return NULL;
}
ic->internal->offset = AV_NOPTS_VALUE;
ic->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
ic->internal->shortest_end = AV_NOPTS_VALUE;
return ic;
}
可以看得出来
主要就是调用了
av_malloc(sizeof(AVFormatContext));
avformat_get_context_defaults(ic);
****************************************************************************************************************************************************
avformat_get_context_defaults()函数其实就是赋初值的功能
就在
libavformat目录下的options.c
实现的
static void avformat_get_context_defaults(AVFormatContext *s)
{
memset(s, 0, sizeof(AVFormatContext));
s->av_class = &av_format_context_class;
s->io_open = io_open_default;
s->io_close = io_close_default;
av_opt_set_defaults(s);
}
****************************************************************************************************************************************************
好了,之前那些流程在编码的时候也是需要的,从这边开始就是解码所特有的。
avformat_open_input()
打开媒体的的过程开始于avformat_open_input,因此该函数的重要性不可忽视。
在这个函数中,主要
完成了:
输入输出结构体AVIOContext的初始化;输入数据的协议(例如RTMP,或者file)的识别(通过一套评分机制):1判断文件名的后缀 2读取文件头的数据进行比对;使用获得最高分的文件协议对应的URLProtocol,通过函数指针的方式,与FFMPEG连接(非专业用词);剩下的就是调用该URLProtocol的函数进行open,read等操作了该函数的定义是在libavformat目录下的avformat.h/*** Open an input stream and read the header. The codecs are not opened.* The stream must be closed with avformat_close_input().* 打开一个输入流并读取头部。编解码器没有打开。流必须用avFormat_CLOSE_INPUT()关闭。** @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context).* May be a pointer to NULL, in which case an AVFormatContext is allocated by this* function and written into ps.* Note that a user-supplied AVFormatContext will be freed on failure.* @param url URL of the stream to open.* @param fmt If non-NULL, this parameter forces a specific input format.* Otherwise the format is autodetected.* @param options A dictionary filled with AVFormatContext and demuxer-private options.* On return this parameter will be destroyed and replaced with a dict containing* options that were not found. May be NULL.** @return 0 on success, a negative AVERROR on failure.** @note If you want to use custom IO, preallocate the format context and set its pb field.*/int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
函数的实现是在libavformat目录下的utils.c由于代码有点长就不贴了具体的流程如图:****************************************************************************************************************************************************
之后就是
avformat_find_stream_info()函数了(这个函数其实是可以仔细去跟一下步骤,其实是把整个步骤都走了一遍的)
该函数可以读取一部分视音频数据并且获得一些相关的信息。
函数的声明位于libavformat\avformat.h
/**
* Read packets of a media file to get stream information. This
* is useful for file formats with no headers such as MPEG. This
* function also computes the real framerate in case of MPEG-2 repeat
* frame mode.
* 读取媒体文件的数据包以获取流信息。此对于没有头文件格式(如mpeg.this函数)
* 非常有用。在mpeg-2重复帧模式的情况下,此函数也计算实际帧。
* The logical file position is not changed by this function;
* examined packets may be buffered for later processing.
* 此函数不会更改逻辑文件位置;检查过的数据包可能会被缓冲以供以后处理。
*
* @param ic media file handle
* @param options If non-NULL, an ic.nb_streams long array of pointers to
* dictionaries, where i-th member contains options for
* codec corresponding to i-th stream.
* On return each dictionary will be filled with options that were not found.
* @return >=0 if OK, AVERROR_xxx on error
*
* @note this function isn't guaranteed to open all the codecs, so
* options being non-empty at return is a perfectly normal behavior.
*
* @todo Let the user decide somehow what information is needed so that
* we do not waste time getting stuff the user does not need.
*/
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
函数的实现是在libavformat目录下的utils.c
函数比较长,就不贴代码了
该函数主要用于给每个媒体流(音频/视频)的AVStream结构体赋值。我们大致浏览一下这个函数的代码,会发现它其实已经实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作。换句话说,该函数实际上已经“走通”的解码的整个流程。下面看一下除了成员变量赋值之外,该函数的几个关键流程。
1.查找解码器:find_decoder()2.打开解码器:avcodec_open2()3.读取完整的一帧压缩编码的数据:read_frame_internal()注:av_read_frame()内部实际上就是调用的read_frame_internal()。4.解码一些压缩编码数据:try_decode_frame()
****************************************************************************************************************************************************
之后的函数就是
avcodec_find_decoder()
我们可以在
libavformat目录下的utils.c
找到
AVCodec *avcodec_find_decoder(enum AVCodecID id)
{
return find_encdec(id, 0);
}
****************************************************************************************************************************************************
之后就是
avcodec_open2()
这个函数是在libavcodec目录下的avcodec.h里边定义的
/**
* Initialize the AVCodecContext to use the given AVCodec. Prior to using this
* function the context has to be allocated with avcodec_alloc_context3().
* 初始化avcodectext以使用给定的avcodece。在使用此函数之前,必须使用avcodec_alloc_context3()分配上下文。
*
* The functions avcodec_find_decoder_by_name(), avcodec_find_encoder_by_name(),
* avcodec_find_decoder() and avcodec_find_encoder() provide an easy way for
* retrieving a codec.
* avcodec_find_declder_by_name()、avcodec_find_encoder_by_name()、avcodec_find_decoder() 和 avcodec_find_encoder()
* 函数为检索编解码器提供了一种简单的方法。
*
* @warning This function is not thread safe!这个函数不是线程安全的!
*
* @note Always call this function before using decoding routines (such as
* @ref avcodec_receive_frame()).
* 总是调用这个函数之前使用解码例程(如 avcodec_receive_frame())。
*
* @code
* avcodec_register_all();
* av_dict_set(&opts, "b", "2.5M", 0);
* codec = avcodec_find_decoder(AV_CODEC_ID_H264);
* if (!codec)
* exit(1);
*
* context = avcodec_alloc_context3(codec);
*
* if (avcodec_open2(context, codec, opts) < 0)
* exit(1);
* @endcode
*
* @param avctx The context to initialize.
* @param codec The codec to open this context for. If a non-NULL codec has been
* previously passed to avcodec_alloc_context3() or
* for this context, then this parameter MUST be either NULL or
* equal to the previously passed codec.
* @param options A dictionary filled with AVCodecContext and codec-private options.
* On return this object will be filled with options that were not found.
*
* @return zero on success, a negative value on error
* @see avcodec_alloc_context3(), avcodec_find_decoder(), avcodec_find_encoder(),
* av_dict_set(), av_opt_find().
*/
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
函数是在libavcodec目录下的utils.c实现的
代码有点长 就不贴上来了
avcodec_open2()中最关键的一步就是调用AVCodec的init()方法初始化具体的编码器。AVCodec的init()是一个函数指针,指向具体编解码器中的初始化函数。
avcodec_open2()所做的工作,如下所列:
(1)为各种结构体分配内存(通过各种av_malloc()实现)。(2)将输入的AVDictionary形式的选项设置到AVCodecContext。(3)其他一些零零碎碎的检查,比如说检查编解码器是否处于“实验”阶段。(4)如果是编码器,检查输入参数是否符合编码器的要求(5)调用AVCodec的init()初始化具体的解码器。
****************************************************************************************************************************************************
之后就是
av_read_frame()
函数的作用是读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如H.264中一帧压缩数据通常对应一个NAL)。
声明位于libavformat\avformat.h
/**
* Return the next frame of a stream.返回流的下一个帧。
* This function returns what is stored in the file, and does not validate
* that what is there are valid frames for the decoder. It will split what is
* stored in the file into frames and return one for each call. It will not
* omit invalid data between valid frames so as to give the decoder the maximum
* information possible for decoding.
* 此函数返回存储在文件中的内容,而不验证解码器是否存在有效的帧。它将文件中存储的
* 内容分割成帧,并为每个调用返回一个。它不会省略有效帧之间的无效数据,以便给解
* 器最大的解码信息。
*
* If pkt->buf is NULL, then the packet is valid until the next
* av_read_frame() or until avformat_close_input(). Otherwise the packet
* is valid indefinitely. In both cases the packet must be freed with
* av_packet_unref when it is no longer needed. For video, the packet contains
* exactly one frame. For audio, it contains an integer number of frames if each
* frame has a known fixed size (e.g. PCM or ADPCM data). If the audio frames
* have a variable size (e.g. MPEG audio), then it contains one frame.
*
* pkt->pts, pkt->dts and pkt->duration are always set to correct
* values in AVStream.time_base units (and guessed if the format cannot
* provide them). pkt->pts can be AV_NOPTS_VALUE if the video format
* has B-frames, so it is better to rely on pkt->dts if you do not
* decompress the payload.
*
* @return 0 if OK, < 0 on error or end of file
*/
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
函数的实现
位于libavformat\utils.c
代码有点长直接贴过程
****************************************************************************************************************************************************
之后就是
avcodec_decode_video2
作用是解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。
该函数的声明位于libavcodec\avcodec.h
源代码位于libavcodec\utils.c
主要的工作就是:
(1)对输入的字段进行了一系列的检查工作:例如宽高是否正确,输入是否为视频等等。
(2)通过ret = avctx->codec->decode(avctx, picture, got_picture_ptr,&tmp)这句代码,调用了相应AVCodec的decode()函数,完成了解码操作。
(3)对得到的AVFrame的一些字段进行了赋值,例如宽高、像素格式等等。
其中第二部是关键的一步,它调用了AVCodec的decode()方法完成了解码。AVCodec的decode()方法是一个函数指针,指向了具体解码器的解码函数。
****************************************************************************************************************************************************
之后
avcodec_close()
该函数用于关闭编码器。avcodec_close()函数的声明位于libavcodec\avcodec.h,
/**
* Close a given AVCodecContext and free all the data associated with it
* (but not the AVCodecContext itself).
* 关闭给定的avcodectext并释放与其相关的所有数据(但不释放avcodectext本身)。
*
* Calling this function on an AVCodecContext that hasn't been opened will free
* the codec-specific data allocated in avcodec_alloc_context3() with a non-NULL
* codec. Subsequent calls will do nothing.
* 在尚未打开的avcodectext上调用此函数将释放在avcodec_alloc_context 3()中分配的与编解码
* 器特定的数据,其中包含一个非空codecc。随后的调用将不会起任何作用。
*
* @note Do not use this function. Use avcodec_free_context() to destroy a
* codec context (either open or closed). Opening and closing a codec context
* multiple times is not supported anymore -- use multiple codec contexts
* instead.
*/
int avcodec_close(AVCodecContext *avctx);
函数的实现
位于libavcodec\utils.c
该函数释放AVCodecContext中有关的变量,并且调用了AVCodec的close()关闭了解码器。
****************************************************************************************************************************************************
之后
avformat_close_input()
该函数用于关闭一个AVFormatContext,一般情况下是和avformat_open_input()成对使用的。(这一个是需要注意的)
函数的
声明位于libavformat\avformat.h
/**
* Close an opened input AVFormatContext. Free it and all its contents
* and set *s to NULL.
* 关闭打开的输入avformatcontext.释放它及其所有内容并将*s设置为空。
*/
void avformat_close_input(AVFormatContext **s);
函数的实现
位于libavformat\utils.c
****************************************************************************************************************************************************
其实可以参考一下大神的博客