热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

大华摄像头解码ffmpeg_刻意练习FFmpeg系列:通过思维导图快速了解FFmpeg源码整体结构体...

前言本文将整体性地介绍FFmpeg的代码结构。如果你还不是非常清楚理解音视频的「格式Format」和「编码Codec」,可以看一下之前的文章。kamuel࿱
68bfd10bc58b5b602b31b36ca442fc34.png

前言

本文将整体性地介绍FFmpeg的代码结构。如果你还不是非常清楚理解音视频的「格式Format」和「编码Codec」,可以看一下之前的文章。

kamuel:刻意练习FFmpeg系列:音视频基础概念格式和编码​zhuanlan.zhihu.com
f3053e952eff98da0759ffaf5e0164f7.png

心智模型:C语言的类

如果你看过FFmpeg的代码,就很容易发现,FFmpeg里有各式各样的结构体,有一类结构体的命名规则比较类似,都是XxxxContext。

dada34de69aff709251246661a348ac8.png
XXXContext

其实这是FFmpeg在运用面向对象的思想来编程。XxxxContext可以看做是C语言“类”的实现。

C语言没有类的语法特征,但可以用结构体struct来描述一组元素的集合。如果把XxxxContext看做类,成员变量显然可以用结构体struct来模拟。

但成员函数呢?如果你学习过Python的类,就知道成员函数里,第一个参数self是指代本对象本身。其实C++的内部实现也是,成员函数的第一个参数,隐式地传递着this指针。所以用C语言来描述对象,只需要显式地在函数的第一个参数传递XxxxContext结构体的指针就可以了。

b24e31d1f08347a7374e1233987181df.png
C语言的类 vs C++语言的类

FFmpeg的模块布局

打开FFmpeg源码,会发现有一系列libavxxx的模块,这些模块很好地划分了代码的结构和分工。

  1. libavformat,format,格式封装
  2. libavcodec,codec,编码、解码
  3. libavutil,util,通用音视频工具,像素、IO、时间等工具
  4. libavfilter,filter,过滤器,可以用作音视频特效处理
  5. libavdevice,device,设备(摄像头、拾音器)
  6. libswscale,scale,视频图像缩放,像素格式互换
  7. libavresample,resample,重采样
  8. libswresample,也是重采样,类似图像缩放
  9. libpostproc,后处理,??

对于入门来说,最重要的是前面三个,也就是format、codec、util,其他的可以慢慢熟悉。

下面重点画出format、codec、util三大核心模块的功能。

05ba3bb330d04c9ac0c6c7c32857ab22.png
FFmpeg的核心模块

libavformat 格式处理常见“类”

AVFormatContext,打开文件总需要它

之前介绍过,「格式Format」是音视频的一个核心概念,所以在FFmpeg里你需要经常与AVFormatContext打交道。因为一般不是直接操作「解封装器Demuxer」和「封装器Muxer」,而是通过AVFormatContext来操作它们。

常用的 AVFormatContext 的操作,可以分为3类:

  1. 通用的函数,例如创建和销毁,等价于C++的构造函数和析构函数。
  2. 对输入视频流的读操作,用于输入处理,也就是使用「解封装器Demuxer」对视频流进行操作,是读操作。
  3. 对输出视频流的写操作,用于输出处理,也就是使用「封装器Muxer」对视频流进行操作,是写操作。

为了方便查看,总结为一个思维导图

2a98e66405154f32aa1777d3714bdf1d.png
AVFormatContext

对于AVFormatContext的使用,主要就是读视频和写视频,下面是流程图。

59b211a08bc658440e51bcafbd0e015c.png
用AVFormatContext读视频
ca2e3d36dde6a75b9680494953712379.png
用AVFormatContext写视频

AVInputFormat,传说中的「解封装器Demuxer」

「解封装器Demuxer」,正式的结构体是AVInputFormat,其实是一个接口,功能是对封装后的格式容器解开获得编码后的音视频的工具。简单说,就是拆包工具。

我们所知道的各种多媒体格式,例如MP4、MP3、FLV等格式的读取,都有AVInputFormat的具体实现。

下面是mp4视频格式的解封装器ff_mov_demuxer。

你可以看到AVInputFormat提供的是类似接口一样的功能,而ff_mov_demuxer是其的一个具体实现。FFmpeg其实本身的逻辑并不复杂,只是由于支持的格式特别丰富,所以代码才如此多。如果我们先把大部分格式忽略掉,重点关注FFmpeg对其中几个格式的实现,可以更好理解FFmpeg。

AVInputFormat ff_mov_demuxer = {.name = "mov,mp4,m4a,3gp,3g2,mj2",.long_name = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),.priv_class = &mov_class,.priv_data_size = sizeof(MOVContext),.extensions = "mov,mp4,m4a,3gp,3g2,mj2",.read_probe = mov_probe, // 这是一个函数.read_header = mov_read_header, // 这是一个函数.read_packet = mov_read_packet, // 这是一个函数.read_close = mov_read_close, // 这是一个函数.read_seek = mov_read_seek, // 这是一个函数.flags = AVFMT_NO_BYTE_SEEK | AVFMT_SEEK_TO_PTS,
};

下面是「解封装器 Demuxer」的思维导图。

78e65ad22dd7462048490291b0c14e18.png
解封装器 Demuxer

AVOutputFormat,传说中的「封装器Muxer」

「封装器 Muxer」,对应的结构体是AVOutputFormat,也是一个接口,功能是对编码后的音视频封装进格式容器的工具。简单说,就是打包工具。

跟「解封装器 Demuxer」类似,也是MP4、MP3、FLV等格式的实现,差别是「封装器 Muxer」用于输出。

同样通过一个例子来理解,这是MP3的「封装器 Muxer」,libavformat/mp3enc.c。

AVOutputFormat ff_mp3_muxer = {.name = "mp3",.long_name = NULL_IF_CONFIG_SMALL("MP3 (MPEG audio layer 3)"),.mime_type = "audio/mpeg",.extensions = "mp3",.priv_data_size = sizeof(MP3Context),.audio_codec = AV_CODEC_ID_MP3,.video_codec = AV_CODEC_ID_PNG,.write_header = mp3_write_header,.write_packet = mp3_write_packet,.write_trailer = mp3_write_trailer,.query_codec = query_codec,.flags = AVFMT_NOTIMESTAMPS,.priv_class = &mp3_muxer_class,
};

下面是 「封装器 Muxer」的思维导图。

c04a3304eb37e0ffb7a2fed596e1bce7.png
用AVFormatContext读视频

libavcodec 编码解码处理常见“类”

操作编解码器的 AVCodecContext

跟AVFormatContext类似,我们也是通过AVCodecContext对「编码器Encoder」和「解码器Decoder」操作,一般也不直接操作编解码器。所以需要实现编解码,一般都要跟AVCodecContext打交道。思维导图如下:

9714c537d2022753bd8ac5e8a53fe62d.png
AVCodecContext

「编码器Encoder」和「解码器Decoder」的公共接口AVCodec

跟格式对应有「解封装器 Demuxer」和「封装器 Muxer」一样,编解码有「编码器Encoder」和「解码器Decoder」。不过差异在于,「编码器Encoder」和「解码器Decoder」使用相同的公共接口AVCodec,因为编解码的过程有较多相似的地方。

为了方便理解,同样先举一个具体的例子,下面是利用libopenh264库实现的编码器例子ff_libopenh264_encoder。

AVCodec ff_libopenh264_encoder = {.name = "libopenh264",.long_name = NULL_IF_CONFIG_SMALL("OpenH264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),.type = AVMEDIA_TYPE_VIDEO,.id = AV_CODEC_ID_H264,.priv_data_size = sizeof(SVCContext),.init = svc_encode_init,.encode2 = svc_encode_frame, // 编码.close = svc_encode_close,.capabilities = AV_CODEC_CAP_AUTO_THREADS,.caps_internal = FF_CODEC_CAP_INIT_THREADSAFE | FF_CODEC_CAP_INIT_CLEANUP,.pix_fmts = (const enum AVPixelFormat[]){ AV_PIX_FMT_YUV420P,AV_PIX_FMT_NONE },.priv_class = &class,.wrapper_name = "libopenh264",
};

而h264的一个解码器具体实现是 ff_libopenh264_decoder。可以看到,差异是编码器实现encode2()函数,解码器实现decode()函数。

AVCodec ff_libopenh264_decoder = {.name = "libopenh264",.long_name = NULL_IF_CONFIG_SMALL("OpenH264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),.type = AVMEDIA_TYPE_VIDEO,.id = AV_CODEC_ID_H264,.priv_data_size = sizeof(SVCContext),.init = svc_decode_init,.decode = svc_decode_frame, // 解码.close = svc_decode_close,.capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_DR1,.caps_internal = FF_CODEC_CAP_SETS_PKT_DTS | FF_CODEC_CAP_INIT_THREADSAFE |FF_CODEC_CAP_INIT_CLEANUP,.bsfs = "h264_mp4toannexb",.wrapper_name = "libopenh264",
};

「编解码器公共接口 AVCodec」的思维导图。

89cd9b9d023a5de942c82d9516f584b3.png
编解码器公共接口 AVCodec

「解析器 Parser」,将输入流转换为帧的数据包

由于解码器的输入是一个完整的帧数据包,而无论是网络传输还是文件读取,一般都是固定的buffer来读取的,而不是安装格式的帧大小来读取,所以我们需要解析器Parser将流整理成一个一个的Frame数据包。

先看一个具体的例子ff_h264_parser,这是从格式输入流中获取h264压缩的帧数据包。

AVCodecParser ff_h264_parser = {.codec_ids = { AV_CODEC_ID_H264 },.priv_data_size = sizeof(H264ParseContext),.parser_init = init,.parser_parse = h264_parse,.parser_close = h264_close,.split = h264_split,
};

下面是「解析器 Parser」的思维导图。

239d79f836f0dd50b8955fbf76b301e9.png
解析器Parser

总结

上面就是FFmpeg的最核心代码的逻辑了,后面的学习里,有必要刻意地关注一下这些结构体及其相关方法,很快你就能对FFmpeg有整体性的把握了。

写文章不容易,但以文会友,乃一桩快事,如果需要原版的思维导图,可以私信我,或者添加微信,请注明ffmpeg

2df7f2520fd4a1accc44e1af87b4f502.png



推荐阅读
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文介绍了机器学习手册中关于日期和时区操作的重要性以及其在实际应用中的作用。文章以一个故事为背景,描述了学童们面对老先生的教导时的反应,以及上官如在这个过程中的表现。同时,文章也提到了顾慎为对上官如的恨意以及他们之间的矛盾源于早年的结局。最后,文章强调了日期和时区操作在机器学习中的重要性,并指出了其在实际应用中的作用和意义。 ... [详细]
  • Ihavethefollowingonhtml我在html上有以下内容<html><head><scriptsrc..3003_Tes ... [详细]
  • WhenIusepythontoapplythepymysqlmoduletoaddafieldtoatableinthemysqldatabase,itdo ... [详细]
  • IOS开发之短信发送与拨打电话的方法详解
    本文详细介绍了在IOS开发中实现短信发送和拨打电话的两种方式,一种是使用系统底层发送,虽然无法自定义短信内容和返回原应用,但是简单方便;另一种是使用第三方框架发送,需要导入MessageUI头文件,并遵守MFMessageComposeViewControllerDelegate协议,可以实现自定义短信内容和返回原应用的功能。 ... [详细]
  • 实现一个通讯录系统,可添加、删除、修改、查找、显示、清空、排序通讯录信息
    本文介绍了如何实现一个通讯录系统,该系统可以实现添加、删除、修改、查找、显示、清空、排序通讯录信息的功能。通过定义结构体LINK和PEOPLE来存储通讯录信息,使用相关函数来实现各项功能。详细介绍了每个功能的实现方法。 ... [详细]
  • Iamtryingtocreateanarrayofstructinstanceslikethis:我试图创建一个这样的struct实例数组:letinstallers: ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • 《2017年3月全国计算机等级考试二级C语言上机题库完全版》由会员分享,可在线阅读,更多相关《2017年3月全国计算机等级考试二级C语言上机题库完全版( ... [详细]
author-avatar
幼俐佩其392
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有