最简单的播放器(只是简单的分析解码这一部分)

在了解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
****************************************************************************************************************************************************
其实可以参考一下大神的博客













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值