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

开发笔记:FFmpeg代码架构

篇首语:本文由编程笔记#小编为大家整理,主要介绍了FFmpeg代码架构相关的知识,希望对你有一定的参考价值。 F

篇首语:本文由编程笔记#小编为大家整理,主要介绍了FFmpeg代码架构相关的知识,希望对你有一定的参考价值。






FFmpeg模块分类

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



  • libavformat,format,格式封装

  • libavcodec,codec,编码、解码

  • libavutil,util,通用音视频工具,像素、IO、时间等工具

  • libavfilter,filter,过滤器,可以用作音视频特效处理

  • libavdevice,device,设备(摄像头、拾音器)

  • libswscale,scale,视频图像缩放,像素格式互换

  • libavresample,resample,重采样

  • libswresample,也是重采样,类似图像缩放

  • libpostproc,后期处理

对于入门来说,最重要的是前面三个,也就是format、codec、util,这三个是最基本的库,我们先理一下这三个库的基本结构:

FFmpeg中的Context

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



  • AVFormatContext

  • AVCodecContext

  • AVCodecParserContext

  • AVIOContext

  • AVFilterContext

当然还有很多Context,上面只是列出比较典型的几种,一看这种命名规则就和面向对象中的命名很类似。
Context是持有的上下文,是数据链路传递过程中的持有数据的对象。
其实这是FFmpeg在运用面向对象的思想来编程。XxxxContext可以看做是C语言“类”的实现。
C语言没有类的语法特征,但可以用结构体struct来描述一组元素的集合。如果把XxxxContext看做类,成员变量显然可以用结构体struct来模拟。

下面一个简单的例子表示下:

struct AVFormatContext { iformat; oformat;}avformat_alloc_context(); avformat_free_context();

class AVFormatContext { private: iformat; oformat; public: AVFormatContext(); ~AVFormatContext();}

其实FFmpeg中的XxxxContext的写法就是按照面向对象的语法设计的。对面向对象比较熟悉的同学其实看到这些命名应该比较亲切。

AVFormatContext

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

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



  • 通用的函数,例如创建和销毁,等价于C++的构造函数和析构函数。

  • 对输入视频流的读操作,用于输入处理,也就是使用解封装器Demuxer对视频流进行操作,是读操作。

  • 对输出视频流的写操作,用于输出处理,也就是使用封装器Muxer对视频流进行操作,是写操作。

iformat对应的是AVInputFormat,oformat对应的是AVOutputFormat,正好说一下AVFormatContext和AVInputFormat/AVOutputFormat的区别。
AVFormatContext持有的是传递过程中的数据,这些数据在整个传递路径上都存在,或者都可以复用,AVInputFormat/AVOutputFormat中包含的是动作,包含着如何解析得到的这些数据。

AVStream **streams; 是媒体文件中包含的流数据,几条流,媒体流中分别是音频、视频、字幕等等。



  • avformat_alloc_context() 创建输入媒体文件的AVFormatContext

  • avformat_alloc_output_context2() 创建输出媒体文件的AVFormatContext

  • av_dump_format() 打印format详情

  • avformat_open_input() 打开媒体文件,探知媒体文件的封装格式。

  • avformat_close_input() 关闭媒体文件

  • avformat_find_stream_info() 探知媒体文件中的流信息,几条流,每条流的基本信息。

  • av_read_frame() 读取媒体文件中每一帧数据,这是未解码之前的帧

  • avformat_write_header() 写入输出文件的媒体头部信息

  • av_interleaved_write_frame() 写入输出文件的帧信息,此帧信息已经调整了帧与帧之间的关联了。

  • av_write_uncoded_frame() 写入输出文件的未编码的帧信息

  • av_write_frame() 写入输出文件的已编码的帧信息

  • av_write_trailer() 写入输出文件的媒体尾部信息

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

读视频流程:



  • 1.创建avformat上下文
    AVFormatContext *ifmt_ctx = avformat_alloc_context()

  • 2.打开视频文件
    avformat_open_input(&ifmt_ctx, in_filename, 0, 0)

  • 3.持续读取视频帧
    while(...) {
    av_read_frame(ifmt_ctx, &pkt)
    }

  • 4.关闭avformat上下文
    avformat_close_input(&ifmt_ctx)

写视频流程:



  • 1.创建输出上下文
    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename)

  • 2.写格式头部
    avformat_write_header(ofmt_ctx, NULL)

  • 3.持续输出帧
    while(...) {
    av_interleaved_write_frame(ofmt_ctx, &pkt)
    }

  • 4.写格式尾部
    av_write_trailer(ofmt_ctx)

  • 5.关闭上下文
    avformat_free_context(ofmt_ctx)

AVInputFormat

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

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

demuxer的种类很多,而且是可配置的,demuxer有多少,可以看一下demuxer_list.c文件,太多了,不一一列举了,我们举一个mp4 demuxer的例子。

下面是mp4视频格式的解封装器ff_mov_demuxer,在mov.c中:

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,};

看到了有几个函数指针:



  • read_probe
    探测一下什么封装格式

  • read_header
    读取格式头部数据

  • read_packet
    读取解封装之后的数据包

  • read_close
    关闭对象

  • read_seek
    格式的seek读取控制

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

AVOutputFormat

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

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

与demuxer类似,muxer的种类很多,可以看一下muxer_list.c文件。
下面看一下mp3的muxer,在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,};

上面也有对应的指针函数,是demuxer的反过程。

AVCodecContext

跟AVFormatContext类似,我们也是通过AVCodecContext对编码器Encoder解码器Decoder操作,一般也不直接操作编解码器。所以需要实现编解码,一般都要跟AVCodecContext打交道。

图片

图片

和demuxer与muxer一样,codec也有decode和encode之分,具体可以参考codec_list.c文件:
查看ff_libx264_encoder,在libx264.c中:

AVCodec ff_libx264_encoder = { .name = "libx264", .long_name = NULL_IF_CONFIG_SMALL("libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"), .type = AVMEDIA_TYPE_VIDEO, .id = AV_CODEC_ID_H264, .priv_data_size = sizeof(X264Context), .init = X264_init, .encode2 = X264_frame, .close = X264_close, .capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AUTO_THREADS | AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE, .priv_class = &x264_class, .defaults = x264_defaults, .init_static_data = X264_init_static, .caps_internal = FF_CODEC_CAP_INIT_CLEANUP, .wrapper_name = "libx264",};

其中核心的函数就是encode2,对应X264_frame函数

FFmpeg中的Parser

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

parser的全局声明在parsers.c,具体的定义在list_parser.c
看一下h264_parser.c中的ff_h264_parser例子:

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,};

H264ParseContext结构中是H264格式的帧数据定义。

typedef struct H264ParseContext { ParseContext pc; H264ParamSets ps; H264DSPContext h264dsp; H264POCContext poc; H264SEIContext sei; int is_avc; int nal_length_size; int got_first; int picture_structure; uint8_t parse_history[6]; int parse_history_count; int parse_last_mb; int64_t reference_dts; int last_frame_num, last_picture_structure;} H264ParseContext;

这儿大家简单看下,其中H264ParamSets很重要,H264关键的参数都在这儿定义:我们熟知的sps、pps都在这儿定义,有了这两个定义,我们方便在宏块中快速找到当前帧的属性。

typedef struct H264ParamSets { AVBufferRef *sps_list[MAX_SPS_COUNT]; AVBufferRef *pps_list[MAX_PPS_COUNT]; AVBufferRef *pps_ref; AVBufferRef *sps_ref; /* currently active parameters sets */ const PPS *pps; const SPS *sps;} H264ParamSets;

小结



  • FFmpeg的学习过程很难,梳理清楚结构,整体的代码脉络就比较清楚了,但是libavfilter等核心模块本文没有讲。这个模块非常庞大,而且可以自定义,后续会单独讲一下。

  • 在实践中学习FFmpeg进步会快一些。下面提供一些实践的思路。
    FFmpeg代码结构
    FFmpeg交叉编译
    FFmpeg解封装
    FFmpeg重封装
    FFmpeg解码
    FFmpeg分离音视频流




推荐阅读
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 大类|电阻器_使用Requests、Etree、BeautifulSoup、Pandas和Path库进行数据抓取与处理 | 将指定区域内容保存为HTML和Excel格式
    大类|电阻器_使用Requests、Etree、BeautifulSoup、Pandas和Path库进行数据抓取与处理 | 将指定区域内容保存为HTML和Excel格式 ... [详细]
  • Hadoop的文件操作位于包org.apache.hadoop.fs里面,能够进行新建、删除、修改等操作。比较重要的几个类:(1)Configurati ... [详细]
  • com.sun.javadoc.PackageDoc.exceptions()方法的使用及代码示例 ... [详细]
  • 解决问题:1、批量读取点云las数据2、点云数据读与写出3、csf滤波分类参考:https:github.comsuyunzzzCSF论文题目ÿ ... [详细]
  • Python 伦理黑客技术:深入探讨后门攻击(第三部分)
    在《Python 伦理黑客技术:深入探讨后门攻击(第三部分)》中,作者详细分析了后门攻击中的Socket问题。由于TCP协议基于流,难以确定消息批次的结束点,这给后门攻击的实现带来了挑战。为了解决这一问题,文章提出了一系列有效的技术方案,包括使用特定的分隔符和长度前缀,以确保数据包的准确传输和解析。这些方法不仅提高了攻击的隐蔽性和可靠性,还为安全研究人员提供了宝贵的参考。 ... [详细]
  • 利用ZFS和Gluster实现分布式存储系统的高效迁移与应用
    本文探讨了在Ubuntu 18.04系统中利用ZFS和Gluster文件系统实现分布式存储系统的高效迁移与应用。通过详细的技术分析和实践案例,展示了这两种文件系统在数据迁移、高可用性和性能优化方面的优势,为分布式存储系统的部署和管理提供了宝贵的参考。 ... [详细]
  • 过去查询Mysql的时候,都见3306对所有端口开放着,感觉不安全。netstat -anlp | grep mysqltcp 0&am ... [详细]
  • ECharts 官方提供了丰富的图表示例,但实际项目中往往需要从后端动态获取数据。本文将详细介绍如何从后端获取数据并将其转换为 ECharts 所需的 JSON 格式,以实现动态饼图的展示。 ... [详细]
  • javascript分页类支持页码格式
    前端时间因为项目需要,要对一个产品下所有的附属图片进行分页显示,没考虑ajax一张张请求,所以干脆一次性全部把图片out,然 ... [详细]
  • 如何将Python与Excel高效结合:常用操作技巧解析
    本文深入探讨了如何将Python与Excel高效结合,涵盖了一系列实用的操作技巧。文章内容详尽,步骤清晰,注重细节处理,旨在帮助读者掌握Python与Excel之间的无缝对接方法,提升数据处理效率。 ... [详细]
  • Java Socket 关键参数详解与优化建议
    Java Socket 的 API 虽然被广泛使用,但其关键参数的用途却鲜为人知。本文详细解析了 Java Socket 中的重要参数,如 backlog 参数,它用于控制服务器等待连接请求的队列长度。此外,还探讨了其他参数如 SO_TIMEOUT、SO_REUSEADDR 等的配置方法及其对性能的影响,并提供了优化建议,帮助开发者提升网络通信的稳定性和效率。 ... [详细]
  • 在Linux系统中,网络配置是至关重要的任务之一。本文详细解析了Firewalld和Netfilter机制,并探讨了iptables的应用。通过使用`ip addr show`命令来查看网卡IP地址(需要安装`iproute`包),当网卡未分配IP地址或处于关闭状态时,可以通过`ip link set`命令进行配置和激活。此外,文章还介绍了如何利用Firewalld和iptables实现网络流量控制和安全策略管理,为系统管理员提供了实用的操作指南。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 在处理 XML 数据时,如果需要解析 `` 标签的内容,可以采用 Pull 解析方法。Pull 解析是一种高效的 XML 解析方式,适用于流式数据处理。具体实现中,可以通过 Java 的 `XmlPullParser` 或其他类似的库来逐步读取和解析 XML 文档中的 `` 元素。这样不仅能够提高解析效率,还能减少内存占用。本文将详细介绍如何使用 Pull 解析方法来提取 `` 标签的内容,并提供一个示例代码,帮助开发者快速解决问题。 ... [详细]
author-avatar
行侠客人生_983
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有