FFmpeg源代码简单分析-架构图-解码

参考链接

函数背景色

  • 函数在图中以方框的形式表现出来。不同的背景色标志了该函数不同的作用:
    • 粉红色背景函数:FFmpeg的API函数。
    • 白色背景的函数:FFmpeg的内部函数。
    • 黄色背景的函数:URLProtocol结构体中的函数,包含处理协议(Protocol)的功能。
    • 绿色背景的函数:AVInputFormat结构体中的函数,包含处理封装格式(Format)的功能。
    • 蓝色背景的函数:AVCodec结构体中的函数,包含了编解码器(Codec)的功能。
  • PS:URLProtocol,AVInputFormat,AVCodec在FFmpeg开始运行并且注册完组件之后,都会分别被连接成一个个的链表。因此实际上是有很多的URLProtocol,AVInputFormat,AVCodec的。
  • 图中画出了解码一个输入协议是“文件”(其实就是打开一个文件。“文件”也被当做是一种广义的协议),封装格式为FLV,视频编码格式是H.264的数据的函数调用关系。 

区域

  • 整个架构图可以分为以下几个区域:
  • 左边区域——架构函数区域:这些函数并不针对某一特定的视频格式,通用架构,通用流程
  • 右上方黄色区域——协议处理函数区域:不同的协议(RTP,RTMP,FILE)会调用不同的协议处理函数。
  • 右边中间绿色区域——封装格式处理函数区域:不同的封装格式(MKV,FLV,MPEGTS,AVI)会调用不同的封装格式处理函数。
  • 右边下方蓝色区域——编解码函数区域:不同的编码标准(HEVC,H.264,MPEG2)会调用不同的编解码函数。

箭头线

  • 为了把调用关系表示的更明显,图中的箭头线也使用了不同的颜色:
  • 黑色箭头线:标志了函数之间的调用关系。
  • 红色的箭头线:标志了解码的流程。
  • 其他颜色的箭头线:标志了函数之间的调用关系。其中:
    • 调用URLProtocol结构体中的函数用黄色箭头线标识;
    • 调用AVInputFormat结构体中的函数用绿色箭头线标识;
    • 调用AVCodec结构体中的函数用蓝色箭头线标识。

函数所在的文件

  • 每个函数旁边标识了它所在的文件的路径。
  • 此外,还有一点需要注意的是,一些API函数内部也调用了另一些API函数。也就是说,API函数并不一定全部都调用FFmpeg的内部函数,他也有可能调用其他的API函数。
    • 例如从图中可以看出来,avformat_close_input()调用了avformat_free_context()和avio_close()。这些在内部代码中被调用的API函数也标记为粉红色。

现在的执行流程

模块介绍 

右上区域(URLProtocol协议处理函数)

  • URLProtocol结构体包含如下协议处理函数指针:
    • url_open():打开
    • url_read():读取
    • url_write():写入
    • url_seek():调整进度
    • url_close():关闭
  • 结构体代码
typedef struct URLProtocol {
    const char *name;
    int     (*url_open)( URLContext *h, const char *url, int flags);
    /**
     * This callback is to be used by protocols which open further nested
     * protocols. options are then to be passed to ffurl_open_whitelist()
     * or ffurl_connect() for those nested protocols.
     */
    int     (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);
    int     (*url_accept)(URLContext *s, URLContext **c);
    int     (*url_handshake)(URLContext *c);

    /**
     * Read data from the protocol.
     * If data is immediately available (even less than size), EOF is
     * reached or an error occurs (including EINTR), return immediately.
     * Otherwise:
     * In non-blocking mode, return AVERROR(EAGAIN) immediately.
     * In blocking mode, wait for data/EOF/error with a short timeout (0.1s),
     * and return AVERROR(EAGAIN) on timeout.
     * Checking interrupt_callback, looping on EINTR and EAGAIN and until
     * enough data has been read is left to the calling function; see
     * retry_transfer_wrapper in avio.c.
     */
    int     (*url_read)( URLContext *h, unsigned char *buf, int size);
    int     (*url_write)(URLContext *h, const unsigned char *buf, int size);
    int64_t (*url_seek)( URLContext *h, int64_t pos, int whence);
    int     (*url_close)(URLContext *h);
    int (*url_read_pause)(URLContext *h, int pause);
    int64_t (*url_read_seek)(URLContext *h, int stream_index,
                             int64_t timestamp, int flags);
    int (*url_get_file_handle)(URLContext *h);
    int (*url_get_multi_file_handle)(URLContext *h, int **handles,
                                     int *numhandles);
    int (*url_get_short_seek)(URLContext *h);
    int (*url_shutdown)(URLContext *h, int flags);
    const AVClass *priv_data_class;
    int priv_data_size;
    int flags;
    int (*url_check)(URLContext *h, int mask);
    int (*url_open_dir)(URLContext *h);
    int (*url_read_dir)(URLContext *h, AVIODirEntry **next);
    int (*url_close_dir)(URLContext *h);
    int (*url_delete)(URLContext *h);
    int (*url_move)(URLContext *h_src, URLContext *h_dst);
    const char *default_whitelist;
} URLProtocol;

 注意事项:

  • 【例子】不同的协议对应着上述接口有不同的实现函数,举几个例子:
  • File协议(即文件)对应的URLProtocol结构体ff_file_protocol:
  • ff_file_protocol结构体的文件在<libavformat/file.c>
    • url_open() -> file_open() -> open()
    • url_read() -> file_read() -> read()
    • url_write() -> file_write() -> write()
    • url_seek() -> file_seek() -> lseek()
    • url_close() -> file_close() -> close()
const URLProtocol ff_file_protocol = {
    .name                = "file",
    .url_open            = file_open,
    .url_read            = file_read,
    .url_write           = file_write,
    .url_seek            = file_seek,
    .url_close           = file_close,
    .url_get_file_handle = file_get_handle,
    .url_check           = file_check,
    .url_delete          = file_delete,
    .url_move            = file_move,
    .priv_data_size      = sizeof(FileContext),
    .priv_data_class     = &file_class,
    .url_open_dir        = file_open_dir,
    .url_read_dir        = file_read_dir,
    .url_close_dir       = file_close_dir,
    .default_whitelist   = "file,crypto,data"
};
  • RTMP协议(libRTMP)对应的URLProtocol结构体ff_librtmp_protocol:
  • ff_librtmp_protocol结构体的文件在<libavformat/protocol>
    • url_open() -> rtmp_open() -> RTMP_Init(), RTMP_SetupURL(), RTMP_Connect(), RTMP_ConnectStream()
    • url_read() -> rtmp_read() -> RTMP_Read()
    • url_write() -> rtmp_write() -> RTMP_Write()
    • url_seek() -> rtmp_read_seek() -> RTMP_SendSeek()
    • url_close() -> rtmp_close() -> RTMP_Close()
RTMP_CLASS(rtmp)
const URLProtocol ff_librtmp_protocol = {
    .name                = "rtmp",
    .url_open            = rtmp_open,
    .url_read            = rtmp_read,
    .url_write           = rtmp_write,
    .url_close           = rtmp_close,
    .url_read_pause      = rtmp_read_pause,
    .url_read_seek       = rtmp_read_seek,
    .url_get_file_handle = rtmp_get_file_handle,
    .priv_data_size      = sizeof(LibRTMPContext),
    .priv_data_class     = &librtmp_class,
    .flags               = URL_PROTOCOL_FLAG_NETWORK,
};
  • UDP协议对应的URLProtocol结构体ff_udp_protocol:
  • ff_udp_protocol结构体位于<libavformat/udp.c>
    • url_open() -> udp_open()
    • url_read() -> udp_read()
    • url_write() -> udp_write()
    • url_seek() -> udp_close()
    • url_close() -> udp_close()
const URLProtocol ff_udp_protocol = {
    .name                = "udp",
    .url_open            = udp_open,
    .url_read            = udp_read,
    .url_write           = udp_write,
    .url_close           = udp_close,
    .url_get_file_handle = udp_get_file_handle,
    .priv_data_size      = sizeof(UDPContext),
    .priv_data_class     = &udp_class,
    .flags               = URL_PROTOCOL_FLAG_NETWORK,
};

右中区域(AVInputFormat封装格式处理函数)

  • AVInputFormat包含如下封装格式处理函数指针:
    • read_probe():检查格式
    • read_header():读取文件头
    • read_packet():读取一帧数据
    • read_seek():调整进度
    • read_close():关闭

 注意事项:

  • 【例子】不同的封装格式对应着上述接口有不同的实现函数,举几个例子:
  • FLV封装格式对应的AVInputFormat结构体ff_flv_demuxer:
    • read_probe() -> flv_probe() –> probe()
    • read_header() -> flv_read_header() -> create_stream() -> avformat_new_stream()
    • read_packet() -> flv_read_packet()
    • read_seek() -> flv_read_seek()
    • read_close() -> flv_read_close()
  • FFmpeg: libavformat/flvdec.c File Reference
= {
    .name           = "flv",
    .long_name      = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    .priv_data_size = sizeof(FLVContext),
    .read_probe     = flv_probe,
    .read_header    = flv_read_header,
    .read_packet    = flv_read_packet,
    .read_seek      = flv_read_seek,
    .read_close     = flv_read_close,
    .extensions     = "flv",
    .priv_class     = &flv_class,
}
  • MKV封装格式对应的AVInputFormat结构体ff_matroska_demuxer:
    • read_probe() -> matroska_probe()
    • read_header() -> matroska_read_header()
    • read_packet() -> matroska_read_packet()
    • read_seek() -> matroska_read_seek()
    • read_close() -> matroska_read_close()
  • 参考链接:FFmpeg: libavformat/matroskadec.c File Reference
= {
    .name           = "matroska,webm",
    .long_name      = NULL_IF_CONFIG_SMALL("Matroska / WebM"),
    .extensions     = "mkv,mk3d,mka,mks",
    .priv_data_size = sizeof(MatroskaDemuxContext),
    .read_probe     = matroska_probe,
    .read_header    = matroska_read_header,
    .read_packet    = matroska_read_packet,
    .read_close     = matroska_read_close,
    .read_seek      = matroska_read_seek,
    .mime_type      = "audio/webm,audio/x-matroska,video/webm,video/x-matroska"
}
  • MPEG2TS封装格式对应的AVInputFormat结构体ff_mpegts_demuxer:
    • read_probe() -> mpegts_probe()
    • read_header() -> mpegts_read_header()
    • read_packet() -> mpegts_read_packet() 
    • read_close() -> mpegts_read_close()
  • 参考链接:FFmpeg: libavformat/mpegts.c File Reference
= {
    .name           = "mpegts",
    .long_name      = NULL_IF_CONFIG_SMALL("MPEG-TS (MPEG-2 Transport Stream)"),
    .priv_data_size = sizeof(MpegTSContext),
    .read_probe     = mpegts_probe,
    .read_header    = mpegts_read_header,
    .read_packet    = mpegts_read_packet,
    .read_close     = mpegts_read_close,
    .read_timestamp = mpegts_get_dts,
    .flags          = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,
    .priv_class     = &mpegts_class,
}
  • AVI封装格式对应的AVInputFormat结构体ff_avi_demuxer:
    • read_probe() -> avi_probe()
    • read_header() -> avi_read_header()
    • read_packet() -> avi_read_packet()
    • read_seek() -> avi_read_seek()
    • read_close() -> avi_read_close()
  • 参考链接:FFmpeg: libavformat/avidec.c File Reference
= {
    .name           = "avi",
    .long_name      = NULL_IF_CONFIG_SMALL("AVI (Audio Video Interleaved)"),
    .priv_data_size = sizeof(AVIContext),
    .extensions     = "avi",
    .read_probe     = avi_probe,
    .read_header    = avi_read_header,
    .read_packet    = avi_read_packet,
    .read_close     = avi_read_close,
    .read_seek      = avi_read_seek,
    .priv_class = &demuxer_class,
}

右下区域(AVCodec编解码函数)

  • AVCodec包含如下编解码函数指针:
    • init():初始化
    • decode():解码一帧数据
    • close():关闭
  • 参考链接:FFmpeg: AVCodec Struct Reference
  • 上述参考链接里面包含的结构体包含上述编解码函数指针
  • 但是新版本好像没有了
typedef struct AVCodec {
    /**
     * Name of the codec implementation.
     * The name is globally unique among encoders and among decoders (but an
     * encoder and a decoder can share the same name).
     * This is the primary way to find a codec from the user perspective.
     */
    const char *name;
    /**
     * Descriptive name for the codec, meant to be more human readable than name.
     * You should use the NULL_IF_CONFIG_SMALL() macro to define it.
     */
    const char *long_name;
    enum AVMediaType type;
    enum AVCodecID id;
    /**
     * Codec capabilities.
     * see AV_CODEC_CAP_*
     */
    int capabilities;
    uint8_t max_lowres;                     ///< maximum value for lowres supported by the decoder
    const AVRational *supported_framerates; ///< array of supported framerates, or NULL if any, array is terminated by {0,0}
    const enum AVPixelFormat *pix_fmts;     ///< array of supported pixel formats, or NULL if unknown, array is terminated by -1
    const int *supported_samplerates;       ///< array of supported audio samplerates, or NULL if unknown, array is terminated by 0
    const enum AVSampleFormat *sample_fmts; ///< array of supported sample formats, or NULL if unknown, array is terminated by -1
#if FF_API_OLD_CHANNEL_LAYOUT
    /**
     * @deprecated use ch_layouts instead
     */
    attribute_deprecated
    const uint64_t *channel_layouts;         ///< array of support channel layouts, or NULL if unknown. array is terminated by 0
#endif
    const AVClass *priv_class;              ///< AVClass for the private context
    const AVProfile *profiles;              ///< array of recognized profiles, or NULL if unknown, array is terminated by {FF_PROFILE_UNKNOWN}

    /**
     * Group name of the codec implementation.
     * This is a short symbolic name of the wrapper backing this codec. A
     * wrapper uses some kind of external implementation for the codec, such
     * as an external library, or a codec implementation provided by the OS or
     * the hardware.
     * If this field is NULL, this is a builtin, libavcodec native codec.
     * If non-NULL, this will be the suffix in AVCodec.name in most cases
     * (usually AVCodec.name will be of the form "<codec_name>_<wrapper_name>").
     */
    const char *wrapper_name;

    /**
     * Array of supported channel layouts, terminated with a zeroed layout.
     */
    const AVChannelLayout *ch_layouts;
} AVCodec;

 注意事项:

  • 【例子】不同的编解码器对应着上述接口有不同的实现函数,举几个例子:
  • HEVC解码对应的AVCodec结构体ff_hevc_decoder:
    • init() -> hevc_decode_init()
    • decode() -> hevc_decode_frame() -> decode_nal_units()
    • close() -> hevc_decode_free()
  • 参考链接:FFmpeg: libavcodec/hevc.c File Reference
= {
    .name                  = "hevc",
    .long_name             = NULL_IF_CONFIG_SMALL("HEVC (High Efficiency Video Coding)"),
    .type                  = AVMEDIA_TYPE_VIDEO,
    .id                    = AV_CODEC_ID_HEVC,
    .priv_data_size        = sizeof(HEVCContext),
    .priv_class            = &hevc_decoder_class,
    .init                  = hevc_decode_init,
    .close                 = hevc_decode_free,
    .decode                = ,
    .flush                 = hevc_decode_flush,
    .update_thread_context = hevc_update_thread_context,
    .init_thread_copy      = hevc_init_thread_copy,
    .capabilities          = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY |
                             AV_CODEC_CAP_SLICE_THREADS | AV_CODEC_CAP_FRAME_THREADS,
    .profiles              = NULL_IF_CONFIG_SMALL(profiles),
}
  • H.264解码对应的AVCodec结构体ff_h264_decoder:
    • init() -> ff_h264_decode_init()
    • decode() -> h264_decode_frame() -> decode_nal_units()
    • close() -> h264_decode_end()
  • 参考链接:FFmpeg: libavcodec/h264.c File Reference
= {
    .name                  = "h264",
    .long_name             = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
    .type                  = AVMEDIA_TYPE_VIDEO,
    .id                    = AV_CODEC_ID_H264,
    .priv_data_size        = sizeof(H264Context),
    .init                  = ff_h264_decode_init,
    .close                 = h264_decode_end,
    .decode                = h264_decode_frame,
    .capabilities          =  AV_CODEC_CAP_DR1 |
                             AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS |
                             AV_CODEC_CAP_FRAME_THREADS,
    .flush                 = flush_dpb,
    .init_thread_copy      = ONLY_IF_THREADS_ENABLED(decode_init_thread_copy),
    .update_thread_context = ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context),
    .profiles              = NULL_IF_CONFIG_SMALL(profiles),
    .priv_class            = &h264_class,
}
  • VP8解码(libVPX)对应的AVCodec结构体ff_libvpx_vp8_decoder:
    • init() -> vpx_init() -> vpx_codec_dec_init()
    • decode() -> vp8_decode() -> vpx_codec_decode(), vpx_codec_get_frame()
    • close() -> vp8_free() -> vpx_codec_destroy()
  • 版本差异
  • 参考链接:FFmpeg: libavcodec/libvpxdec.c Source File
AVCodec ff_libvpx_vp8_decoder = {
    .name           = "libvpx",
    .type           = AVMEDIA_TYPE_VIDEO,
    .id             = AV_CODEC_ID_VP8,
    .priv_data_size = sizeof(VP8Context),
    .init           = vp8_init,
    .close          = vp8_free,
    .decode         = vp8_decode,
    .capabilities   = CODEC_CAP_AUTO_THREADS | CODEC_CAP_DR1,
    .long_name      = NULL_IF_CONFIG_SMALL("libvpx VP8"),
};
= {
    .name           = "mpeg2video",
    .long_name      = NULL_IF_CONFIG_SMALL("MPEG-2 video"),
    .type           = AVMEDIA_TYPE_VIDEO,
    .id             = AV_CODEC_ID_MPEG2VIDEO,
    .priv_data_size = sizeof(Mpeg1Context),
    .init           = mpeg_decode_init,
    .close          = mpeg_decode_end,
    .decode         = mpeg_decode_frame,
    .capabilities   = AV_CODEC_CAP_DRAW_HORIZ_BAND | AV_CODEC_CAP_DR1 |
                      AV_CODEC_CAP_TRUNCATED | AV_CODEC_CAP_DELAY |
                      AV_CODEC_CAP_SLICE_THREADS,
    .flush          = flush,
    .max_lowres     = 3,
    .profiles       = NULL_IF_CONFIG_SMALL(mpeg2_video_profiles),
}

 注意事项

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值