AI翻译:Linux/内核的优秀代码设计

原文链接:https://leandromoreira.com/2019/08/02/linux-ffmpeg-source-internals-a-good-software-design/

介绍

学习 Linux/FFmpeg 的 C 语言部分代码是如何组织的,使其具有可扩展性并表现得像是为"多态性"而设计。特别地,我们将简要探讨 Linux 中"一切皆文件"的概念在源代码层面是如何工作的,以及 FFmpeg 如何快速轻松地添加对新格式和编解码器的支持。

优秀软件设计 - 简介

为了编写有用且长期可维护的软件,我们倾向于寻找模式并将它们归纳为抽象概念,Linux 和 FFmpeg 背后的开发者们似乎也是这样做的。

软件设计

当我们创建软件时,我们正在构建数据结构并定义它们的行为和依赖关系。我们创建和连接它们的方式可以被视为软件的设计/架构。

假设我们正在构建一个编码/解码视频和音频的媒体框架。AV1、H264、HEVC 和 AAC 这些编解码器都执行一些共同的操作,如果我们能够提供一个包含这些共同操作和数据的通用抽象,我们就可以使用这个概念,而不必依赖于特定编解码器做什么的具体实现。

多年来,许多开发者注意到,随着软件复杂性的增长,良好的软件设计是一个不错的投资。

这就是优秀软件设计背后的一个理念,依赖于松散耦合且边界明确的组件。

Ruby 实现示例

让我们用代码来实践这些概念。下面是一个快速的伪媒体流框架,为多种编解码器提供编码和解码功能:

class AV1def encode(bytes)enddef decode(bytes)end
endclass H264def encode(bytes)enddef decode(bytes)end
end# …supported_codecs = [AV1.new, H264.new, HEVC.new]class MediaFrameworkdef encode(type, bytes)codec = supported_codecs.find {|c| c.class.name.downcase == type}codec.encode(bytes)end
end

这段 Ruby 伪代码试图重现我们上面讨论的内容,这里有一个隐含的编解码器必须具有的操作概念,在这种情况下,操作是编码和解码。由于 Ruby 是一种动态类型语言,任何类只要提供这两个操作就可以作为我们的编解码器使用。

开发者有时会使用术语:合约、API、接口、行为和操作作为同义词。

这种设计可能被认为是好的,因为如果我们想添加一个新的编解码器,我们只需提供一个实现并将其添加到列表中,甚至列表也可以以动态方式构建。这样的代码似乎易于扩展和维护,因为它试图保持组件之间的链接较弱(低耦合),并且每个组件只做它应该做的事情(高内聚)。

Rails 框架甚至强制某种代码组织方式,它采用了模型-视图-控制器(MVC)架构。

Golang 实现

当我们使用 Golang 这样的静态类型语言时,我们需要更加正式地描述所需类型,但这仍然是可行的:

type Codec interface {Encode(data []int) ([]int, error)Decode(data []int) ([]int, error)
} type H264 struct {
}func (H264) Encode(data []int) ([]int, error) {// … 大量代码return data, nil
}var supportedCodecs := []Codec{H264{}, AV1{}}func Encode(codec string, data int[]) {// 在这里我们可以选择使用// supportedCodecs[0].Encode(data)
}

Golang 中的接口类型比 Java 的类似构造更强大,因为它的定义完全与实现分离,反之亦然。我们甚至可以使每个编解码器成为 ReadWriter 并在各处使用它。

C 语言实现

在 C 语言中,我们仍然可以创建相同的行为,但方式略有不同:

struct Codec
{*int (*encode)(*int);*int (*decode)(*int);
};*int h264_encode(int *bytes)
{
// …
}*int h264_decode(int *bytes)
{
// …
}struct Codec av1 =
{.encode = av1_encode,.decode = av1_decode
};struct Codec h264 =
{.encode = h264_encode,.decode = h264_decode
};int main(int argc, char *argv[])
{h264.encode(argv[1]);
}

我们首先在通用结构体中定义抽象操作(在这种情况下是函数),然后用具体代码填充它,比如 av1 编解码器的实际编码和解码代码。

许多其他语言也有类似的机制,可以调度方法或函数,就好像它们是约定协议的一部分,然后系统集成代码只需处理这些高级抽象。

Linux 内核 - 一切皆文件

你是否听说过 Linux 中"一切皆文件"的表达?这个想法是为 Linux 中所有类型的资源提供一个通用接口,例如,Linux 将网络套接字、特殊文件(如 /proc/cpuinfo)甚至 USB 设备都视为文件。

这是一个强大的思想,可以使编写或使用 Linux 程序变得容易,因为我们可以依赖于这个叫做"文件"的抽象所提供的一组众所周知的操作。让我们看看实际应用:

# 第一个例子最简单,我们只是在读取一个纯文本文件
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
…# 现在,我们认为在读取一个文件,但实际上不是!(技术上来说是的...)
$ cat /proc/meminfo
MemTotal:        2046844 kB
MemFree:          546984 kB
MemAvailable:    1535688 kB
Buffers:          162676 kB
Cached:           892000 kB# 最后,我们打开一个文件(使用 fd=3)用于读写
# 这个"文件"实际上是一个套接字,然后我们向这个文件发送请求 >&3
# 并从同一个"文件"中读取数据
$ exec 3<> /dev/tcp/www.google.com/80
$ printf 'HEAD / HTTP/1.1\nHost: http://www.google.com\nConnection: close\n\n' >&3
$ cat <&3
HTTP/1.1 200 OK
Date: Wed, 21 Aug 2019 12:48:40 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
# ...更多响应头...

这只有在文件的概念(数据结构和操作)被设计为子系统之间的主要通信方式时才可能实现。以下是 file_operations API 的概要:

struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//…
}

struct file_operations 定义了文件概念可以做什么的预期操作。

const struct file_operations ext4_dir_operations = {.llseek     = ext4_dir_llseek,.read       = generic_read_dir,//..
};

这里我们可以看到 ext4 文件系统对目录操作的实现。

static const struct file_operations proc_cpuinfo_operations = {.open       = cpuinfo_open,.read       = seq_read,.llseek     = seq_lseek,.release    = seq_release,
};

甚至 cpuinfo proc 文件也是基于这个抽象实现的。当你在 Linux 下操作文件时,实际上是在与 VFS 系统打交道,这个系统会委托给适当的文件实现。

FFmpeg - 格式

以下是 FFmpeg 流程/架构的概述,显示内部组件主要链接到 AVCodec 等抽象概念,而不是直接链接到它们的实现,如 H264、AV1 等。

对于输入文件,FFmpeg 创建一个名为 AVInputFormat 的结构,它由任何希望作为输入使用的格式(视频容器)实现。MKV 文件用其实现填充这个结构,MP4 格式也一样。

typedef struct AVInputFormat {const char *name;const char *long_name;const char *extensions;const char *mime_type;ff_const59 struct AVInputFormat *next;int raw_codec_id;int priv_data_size;int (*read_probe)(const AVProbeData *);int (*read_header)(struct AVFormatContext *);}// matroskaAVInputFormat ff_matroska_demuxer = {.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"
};// mov (mp4)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,
};

这种设计允许新的编解码器、格式和协议更容易集成和发布。DAV1d(一种开源 AV1 实现)于今年 5 月集成到 FFmpeg 中,你可以查看提交差异了解它是多么容易实现。最终,它需要将自己注册为可用的编解码器并遵循预期的操作。

+AVCodec ff_libdav1d_decoder = {
+    .name           = "libdav1d",
+    .long_name      = NULL_IF_CONFIG_SMALL("dav1d AV1 decoder by VideoLAN"),
+    .type           = AVMEDIA_TYPE_VIDEO,
+    .id             = AV_CODEC_ID_AV1,
+    .priv_data_size = sizeof(Libdav1dContext),
+    .init           = libdav1d_init,
+    .close          = libdav1d_close,
+    .flush          = libdav1d_flush,
+    .receive_frame  = libdav1d_receive_frame,
+    .capabilities   = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AUTO_THREADS,
+    .caps_internal  = FF_CODEC_CAP_INIT_THREADSAFE | FF_CODEC_CAP_INIT_CLEANUP |
+                      FF_CODEC_CAP_SETS_PKT_DTS,
+    .priv_class     = &libdav1d_class,
+    .wrapper_name   = "libdav1d",
+};

无论我们使用什么语言,我们都可以(或至少尝试)构建具有低耦合和高内聚的软件,这两个基本特性可以使软件更容易维护和扩展。

找到具有 2 个许可证类型的类似代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/896843.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

7.9K star!跨平台开发从未如此简单,这个开源框架让APP开发效率飙升!

Lynx 是一个革命性的跨平台开发框架,使用 TypeScript 开发即可同时构建 iOS、Android 和 Web 应用。通过创新的布局引擎和原生渲染技术,让开发者用一套代码实现三端同屏效果,大大提升整体的开发效率!嗨,大家好,我是小华同学,关注我们获得“最新、最全、最优质”开源项目…

国内头部HR SaaS厂商的薪酬管理实践:以标准化功能满足复杂薪酬管理需求

易路的成功案例证明了其在薪酬数字化管理转型中的领导地位,为其他企业提供了宝贵的参考和启示。随着易路的不断创新和优化,我们有理由相信它将继续引领行业,帮助企业实现薪酬管理的战略性业务支撑,为企业在激烈的市场竞争中提供强大的人力资源支持,实现企业与员工的共同发…

源码安装Rpcapd,用于 wireshark 远程抓包

背景 libpcap 是一个基础且关键的网络数据包捕获库,为 Wireshark、tcpdump 等流行工具提供核心功能支持。其中,rpcapd(Remote Packet Capture Daemon)组件允许在远程系统上进行数据包捕获,这一功能让我们能够从一个中心位置监控多个远程网络接入点,而无需在每个监控点都部署…

3.10 计数基础排列与组合

1.1 基本计数原则:乘积法则 1.1.1总共有多少种不同的长度为7的位串(位串:可视为一个数组,长度为7) A:2^7=128 1.1.2 计数有穷集的子集|S|表示长度;幂集:幂集(Power Set)是集合论中的一个基本概念。给定一个集合 S,其幂集 P(S) 是包含 S 所有子集的集合,包括空集和 S…

Nginx 常用功能,反向代理笔记

前言 本文是runoob教程的搬运,稍微修改了原文中的一些错误拼写的问题,顺便对一些概念进行了更详细的解释,欢迎批评指正!Nginx常用功能Http代理,反向代理:作为web服务器最常用的功能之一,尤其是反向代理。 这里我给来2张图,对正向代理与反向代理做个诠释,具体细节,大家…

Oracle 19c 数据库实战:从单机部署到 DG 高可用架构搭建

前言:在当今数字化时代,数据已成为企业最宝贵的资产之一。而数据库作为数据存储和管理的核心工具,其重要性不言而喻。Oracle 数据库作为全球领先的商业数据库管理系统,以其卓越的性能、可靠性和强大的功能,广泛应用于企业的关键业务系统中。无论是大型企业的 ERP、CRM 系统…

002TypeScript开发实战

如果读取不到,情况下: 1、建好项目后我们在这里写一个ts语法,让项目跑起来npm run dev 2、在src中新建文件demo.vue

拯救你的排版噩梦,搞定Deepseek到WPS的完美转换!

我们在使用DeepSeek时,好不容易生成的文案复制到WPS之后,排版却全部乱掉了。别急,今天教你一招,让排版从此不再乱!第一步:下载LibreOffice。打开这个网址:http://www.libreoffice.org 点击顶部的“Download”,选择第一个菜单,然后点击黄色按钮开始下载。如果觉得浏览…

Deepseek学习随笔(16)--- 腾讯研究院发布:AI图景解码50个年度关键词(附网盘链接)

随着人工智能技术的迅猛发展,AI正在深刻改变我们的生活和工作方式。腾讯研究院发布的《AI图景解码50个年度关键词》报告,通过梳理50个关键词,全面展现了AI领域的最新进展、技术趋势和未来方向。这份报告不仅为AI从业者提供了宝贵的参考,也为普通读者打开了了解AI的窗口。本…

不再头痛!算法备案自评估报告要点分析

算法备案材料包含众多报告,其中最难的莫过于《算法安全自评估报告》。本人经手过几十份该材料,今天就结合个人经验总结下自评估报告的要点及一些容易踩的坑,希望可以帮助大家。有其它问题也可以参考这篇文章了解。 一、自评估报告常见问题 1、前后矛盾 报告内部的算法、风控…

C语言中标准输出的缓冲机制

什么是缓冲区 缓存区是内存空间的一部分,再内存中,内存空间会预留一定的存储空间,这些存储空间是用来缓冲输入和输出的数据,预留的这部分空间就叫做缓冲区。 其中缓冲区还会根据对应的是输入设备还是输出设备分为输入缓冲区和输出缓冲区。 为什么需要缓冲? 直接操作硬件(…

k8s回调函数-cnblog

回调函数 Kubernetes 为容器提供了生命周期回调。 回调使容器能够了解其管理生命周期中的事件,并在执行相应的生命周期回调时运行在处理程序中实现的代码。Kubernetes 支持 PostStart 和 PreStop 事件。 当一个容器启动后,Kubernetes 将立即发送 PostStart 事件;在容器被终结…