高性能流媒体硬解码

目录

  • 高性能流媒体硬解码
    • 前言
    • 1. What、Why and How
      • 1.1 What
      • 1.2 Why?
      • 1.3 How?
    • 2. 离散傅里叶变换(DFT)
      • 2.1 JPEG编码
      • 2.2 哈夫曼编码(Huffman Coding)
    • 3. 视频流媒体前置知识
      • 3.1 视频文件的构成
      • 3.2 视频的编码与封装
      • 3.3 H264编码的分层
      • 3.4 RTSP之RTP(Real-time Transport Protocol)
      • 3.5 RTSP之RTSP(Real Time Streaming Protocol)
      • 3.6 硬件解码
    • 4. hard_decode_trt
      • 4.1 环境配置
      • 4.2 编译FFmpeg-4.2
        • 4.2.1 下载FFmpeg
        • 4.2.2 编译FFmpeg
        • 4.2.3 设置环境变量
        • 4.2.4 验证
        • 4.2.5 卸载FFmpeg
      • 4.3 NVIDIA VIDEO_CODEC_SDK
      • 4.4 配置Makefile
      • 4.5 运行
    • 结语
    • 下载链接
    • 参考

高性能流媒体硬解码

前言

最近学习了下视频流媒体的相关知识,并对高性能流媒体硬解码框架 hard_decode_trt 进行了简单的学习。通过 FFmpeg 和 NVDEC 配合 tensorRT_Pro 完成在 Ubuntu20.04 主机上的硬件解码操作。

本文主要分享视频流媒体的相关知识以及 hard_decode_trt 框架的简单分析,博主为初学者,欢迎交流讨论,若有问题欢迎各位看官批评指正!!!😘

1. What、Why and How

问题:什么是视频流媒体编解码?为什么需要编解码?如何去实现编解码?

1.1 What

什么是视频流媒体编解码?(from chatGPT)

视频流编解码是指将视频数据进行压缩编码和解压缩解码的过程。在这个过程中,视频数据经过压缩编码之后,可以在网络传输、存储等环节中占用更小的带宽和存储空间,从而提高传输和、存储效率。而在视频数据被接收后,需要进行解压缩解码操作,还原出原始的视频数据,以便于观看和处理。

在视频流编码中,常用的编码标准包括 H.264、H.265、VP9 等,我们常用的就是 H.264 编码格式

在视频流解码过程中,会有软解码和硬解码两种。软解码是指通过软件算法将压缩后的视频流解码成原始的视频帧数据,而硬解码则是利用硬件加速的方式来解码视频流。

在软解码中,计算机需要使用解码器软件,如 FFmpeg、VLC 等,来对视频流进行解码。解码器会对视频流进行解压缩、解码、重构等,最终将其转化为原始的视频帧数据。

硬解码则是使用专门的硬件加速器,如 GPU、DSP 等,来解码视频流(例如 NVIDIA 的 NVDEC)。相较于软解码,硬解码可以提供更高的解码速度和更低的 CPU 占用率,尤其对于高分辨率、高比特率的视频流解码效果更为明显。

1.2 Why?

为什么要学习视频流媒体编解码?

视频流媒体编解码是现代多媒体技术中的核心内容之一,掌握视频流媒体编解码技术可以实现高效的视频压缩、传输和播放,具有广泛的应用前景。如视频直播、视频会议、视频监控、视频点播。

我们先不考虑那么多,先来对实际的一个问题进行思考🧐。

假设我们在服务器上部署了一个异常事件检测模型,通过网络摄像头进行监控,要求实现:

1.实时监测摄像头中的画面,如果出现特定的异常事件,则发送通知到服务器

2.同时储存事件中发生时的一张图,以及该事件发生时前后 5 秒钟的视频画面

重点考虑:

1.对于前后 5 秒钟视频的缓冲应该如何处理?假设储存前后 5 秒的视频画面,按照 1920x1080@25fps 计算,每一帧需要 5.93MB(1920*1080*3/1024/1024) 空间,总共需要储存 250帧 = 1483.15MB

2.对于多路视频同时处理,需要尽可能高的计算速度和尽可能少的空间占用,如果按照上述储存方式的话,则 5 秒需要 1.48GB,如果多路的话则可能直接报废了😂

如果有对视频流媒体编解码有一定了解的话,我们可以自己对视频文件进行编解码,提高计算速度并减少占用空间,简单来说,我们假设监控画面场景变化较小,1 秒钟只取一个关键帧即 I 帧,剩余 24 帧为前向预测帧即 P 帧,那么

对于 1920x1080 的监控视频画面,参考的大小为:

I帧 = 165KB

P帧 = 5KB,如果运动量大可估计为 10KB

对于 GoP = 25 的监控视频,一秒钟大小为 165KB + 10KB * 24 = 405KB 也就是说,1920x1080@25fps 的 10 秒钟视频大小预计为 3.95MB

可以看到储存视频的空间大大减少,同时后续在解码时我们还可以通过硬解码大大提高速度。

对视频流媒体编解码底层的简单了解,可以加深我们对实际应用场景的理解,方便我们的开发工作。

1.3 How?

如何去学习视频流编解码?

博主是通过杜老师写的开源项目 hard_decode_trt 来学习硬件解码并配合 TensorRT

2. 离散傅里叶变换(DFT)

我们先从离散傅里叶变化出发来探讨图像、视频压缩的原理(from chatGPT)

离散傅里叶变换(Discrete Fourier Transform,DFT)是傅里叶变化在离散域的推广,是一种信号分析和处理的常用工具。它实际上是将输入序列转换为一组基函数的线性组合,这些基函数是正弦和余弦函数的离散版本。离散傅里叶变换常用于将时域信号(例如图像和视频帧)转换为频域表示,从而可以通过对频域系数的量化和编码来实现数据压缩。

在图像压缩中,DFT 通常用于实现基于变换的压缩方法,例如 JPEG 压缩。在这种方法中,图像首先被分割成 8x8 的块,然后对每个块进行 DCT (离散余弦变换)。DCT 将块中的像素转换为一组系数,其中每个系数对应于一个特定的空间频率。由于大多数自然图像具有许多相似的频率成分,DCT 系数通过可以通过量化来减小其数值范围,从而实现压缩。

在视频压缩中,DFT 同样用于频域编码方法,例如 H.264 和 HEVC。这些编码器将每个视频帧分割为多个宏块,并对每个宏块进行变换和量化。在 H.264 和 HEVC 中,变换通常是离散余弦变换(DCT)或离散正弦变换(DST),量化是通过将变换系数除以一个量化因子来实现的,编码器还使用运动估计来寻找每个宏块的最佳参考帧,并使用帧内和帧间预测来进一步减小数据量。

总的来说,离散傅里叶变换在图像和视频压缩中发挥着重要作用,通过将信号转换到频域上,可以实现对信号中重要信息的捕捉和压缩,从而实现更高效的数据传输和存储。

更多细节描述可参考:

https://www.zhihu.com/question/22611929?sort=created

https://www.cnblogs.com/wyuzl/p/7880124.html

2.1 JPEG编码

JPEG(联合图像专家小组,Joint Photographic Experts Group)

  • 一种有损的图像压缩技术
  • https://www.cnblogs.com/buaaxhzh/p/9138307.html

编码流程如下:

  • 1.采样

    • 颜色空间转换使用 RGB ➡ YUV,Y 通道代表细节,UV 表示颜色。细节比颜色重要,对 Y 通道轻度压缩,对 UV 可以做更多的压缩,使得人眼难以发现变化

    • 例如一个像素用一个 Y,而 4 个像素共用一个 UV,则 YUV 配比是 4:1:1

  • 2.分块

    • 对图像分为 8*8,共 nxm 个块,分别在 YUV 分量上进行 DCT(离散余弦变换) 变换
  • 3.DCT

    • 以 DCT 矩阵的方式实现加速 DCT 的运算

    • DCT 可以简化为 T B T T TBT^T TBTT,逆变换为 T T B T T^TBT TTBT,这里 T T T 为 DCT 的系数矩阵

    • 图像能量大部分集中在左上角,给后续压缩做准备

  • 4.系数量化

    • DCT 的系数值为实数,通过量化变为整数,接近0的为0,整数相比实数而言,更容易压缩。另外量化后 DCT 系数充满大量 0
  • 5.Zig-zag 扫描

    • 对于左上角第一个分量为直流分量(DC),其它 63 个为交流分量(AC),对于 DC 采用与其它块的差分编码,对于 AC 采用 Zig-zag 扫描并进行游程长度编码(RLE)

    • 由于量化后矩阵的值集中在左上角,三角形排布,因此采用 Z 字形扫描并进行 RLE

      • 例如 31,45,0,0,0,0,23,0,-30,-8,0,0,0,0,0,0,…,0
      • 编码后为:0,31,0,45,4,23,1,-30,0,-8,0,0
      • 编码后为:(0,31),(0,45),(4,23),(1,-30),(0,-8),EOB
        • EOB 为 (0,0),表示后面全都是 0 了
        • 也因此,对于 0 比较多的数据,RLE 能够极大的对数据进行压缩
  • 6.熵编码-huffman

    • 对数据进行无损的哈夫曼编码

    • 最后数据的压缩比通常可以做到十分之一左右

2.2 哈夫曼编码(Huffman Coding)

哈夫曼编码是一种常用的无损压缩算法,常用于 JPEG 图像压缩中,其基本思想是将频率较高的符号用较短的二进制码表示,而将频率较低的符号用较长的二进制码表示,以此来减少数据的存储和传输量,达到压缩的目的。(from chatGPT)

  • https://blog.csdn.net/qq_29519041/article/details/81428934

具体流程如下:

  • 1.统计源数据中各个符号出现的频率
  • 2.根据符号频率构建哈夫曼树,构建的过程中,可以通过不断选择频率最小的两个符号来构建树
  • 3.根据哈夫曼给每个符号编码,编码的规则是从根节点到叶子节点,每次走左边子树则编码为 0,每次走右边子树则编码为 1,最终得到每个符号对应的哈夫曼编码
  • 4.将源数据中的每个符号都用对应的哈夫曼编码进行替换,得到压缩后的数据。
  • 5.在传输过程中,需要将哈夫曼编码的长度也一并发送给对方,以便接收方正确地解码。

哈夫曼编码的优点是压缩效率高,而且压缩后的数据可以无损还原。在 JPEG 图像压缩中,哈夫曼编码通常和离散余弦变换(DCT)结合使用,可以进一步提高压缩效率。

现在通过代码来实现下整个哈夫曼编码流程,以下代码均 Copy 自杜老师关于哈夫曼编码的讲解

1.定义需要编码的字符串text,并对其每一个字符进行词频统计


import numpy as np
import cv2
import matplotlib.pyplot as plt# import pygraphviz as pgv
# sudo apt-get install graphviz graphviz-dev
# pip install pygraphviz
# http://www.graphviz.org/doc/info/attrs.htmltext = "AAABBBBBBBBBBBBCCCCCDDEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEFFF"
freq_map = dict()
for i in range(len(text)):data = text[i]if data not in freq_map:freq_map[data] = 0freq_map[data] += 1freq_map = list(freq_map.items())
print(freq_map)

输出如下:

[('A', 3), ('B', 12), ('C', 5), ('D', 2), ('E', 31), ('F', 3)]

2.定义二叉树的节点类

class TreeNode:def __init__(self):self.key   = Noneself.value = Noneself.left  = Noneself.right = None

3.根据词频,构建哈夫曼树

  • a).如果词汇表中词大于 1 时继续,否则结束
  • b).查找词频表中最小的 2 个词,记为 a、b,且 a 的频率小于等于 b 的频率
  • c).将 ab 节点频率相加作为一个新的节点,记为 c,设置 a 为 c 的左孩子,b 为 c 的右孩子
  • d).将 c 加入词频表中,并且删掉词频表中的 a、b 节点
  • e).继续步骤 a)
freqs = []
for key, value in freq_map:item = TreeNode()item.key   = keyitem.value = valuefreqs.append(item)while len(freqs) > 1:sorted_freqs = sorted(freqs, key = lambda x : x.value)a = sorted_freqs[0]b = sorted_freqs[1]c = TreeNode()c.key   = a.key   + b.keyc.value = a.value + b.valuec.left  = ac.right = bfreqs = [c] + sorted_freqs[2:]def print_tree(item, tab = 0, name = None):if item is None:returntab_string = "".join(["\t"] * tab)print(f"{tab_string} {name}: node = {item.key}[{item.value}]")print_tree(item.left, tab+1, "left")print_tree(item.right, tab+1, "right")tree = freqs[0]
print_tree(tree)

输出如下:

 None: node = BCFDAE[56]left: node = BCFDA[25]left: node = B[12]right: node = CFDA[13]left: node = C[5]right: node = FDA[8]left: node = F[3]right: node = DA[5]       left: node = D[2] right: node = A[3]right: node = E[31]

4.将构建的哈夫曼树绘制为图储存下来

def add_to_graph(G, parent, item, name):if item is None:returnG.add_edge(f"{parent.key}:{parent.value}", f"{item.key}:{item.value}", label=name)add_to_graph(G, item, item.left, "0")add_to_graph(G, item, item.right, "1")def add_graph(G, tree):add_to_graph(G, tree, tree.left, "0")add_to_graph(G, tree, tree.right, "1")A = pgv.AGraph()
add_graph(A, tree)A.layout(prog='dot')
A.draw('graph.png')
image = cv2.imread("graph.png")
plt.figure(figsize=(20, 20))
plt.imshow(image)

哈夫曼树如下图所示:

在这里插入图片描述

图2-1 哈夫曼树

5.根据哈夫曼树,对给定频率表中的每一个字符,分配二进制串

  • 分配的二进制串,即为该符号在哈夫曼树中的路径,往左为0,往右为1,找到该符号所需要经过的路径
  • 二进制串的特性是,出现频率越高的符号会有越短的路径,出现频率较低的符号,会有越长的路径
def find_path_code(tree, key, value, name = ""):if tree is None:return Noneif tree.key == key:return namefound_name = Noneif value < tree.value:found_name = find_path_code(tree.left, key, value, name + "0")if found_name is None:found_name = find_path_code(tree.right, key, value, name + "1")return found_namefreq_coded = {}for key, value in freq_map:coded = find_path_code(tree, key, value, "")freq_coded[key] = codedprint(freq_coded)

输出如下:

{'A': '01111', 'B': '00', 'C': '010', 'D': '01110', 'E': '1', 'F': '0110'}

6.使用已经分配好的二进制串,对给定字符串进行编码

  • 因为是二进制,因此转换为 byte 需要除以 8
def encode_text(text, freq_coded):encoded = ""for word in text:encoded += freq_coded[word]return encodedencoded = encode_text(text, freq_coded)encoded_length = len(encoded) / 8
raw_length = len(text)
compress_rate = encoded_length / raw_length * 100print("Encode后的表示:", encoded)
print("压缩后的长度(byte):", encoded_length)
print("压缩前的长度(byte):", raw_length)
print(f"压缩率:{compress_rate:.2f}%")

输出如下:

Encode后的表示: 01111011110111100000000000000000000000001001001001001001110011101111111111111111111111111111111011001100110
压缩后的长度(byte)13.375
压缩前的长度(byte)56
压缩率:23.88%

7.根据给定的编码后二进制串,以及哈夫曼树,对数据进行解码还原

  • 根据给定的串,在哈夫曼树中查找,直到找到叶子节点,即表示可以吐出一个字符,然后进入下一个环节继续从根节点找
def find_text_with_path(tree, path, cursor = 0):if tree.left is None and tree.right is None:return tree.key, cursorp = path[cursor]node = Noneif p == "0":node = tree.leftelse:node = tree.rightreturn find_text_with_path(node, path, cursor + 1)def decode_text(encoded_text, tree):output, cursor = find_text_with_path(tree, encoded_text, 0)while cursor < len(encoded_text):word, cursor = find_text_with_path(tree, encoded_text, cursor)output += wordreturn outputprint(decode_text(encoded, tree))

输出如下:

AAABBBBBBBBBBBBCCCCCDDEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEFFF

完整的示例代码如下


import numpy as np
import cv2
import matplotlib.pyplot as pltimport pygraphviz as pgv
# sudo apt-get install graphviz graphviz-dev
# pip install pygraphviz
# http://www.graphviz.org/doc/info/attrs.html# ==================== 步骤1.统计字符串每个字符词频 ====================
text = "AAABBBBBBBBBBBBCCCCCDDEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEFFF"
freq_map = dict()
for i in range(len(text)):data = text[i]if data not in freq_map:freq_map[data] = 0freq_map[data] += 1freq_map = list(freq_map.items())
print(freq_map)# ==================== 步骤2.定义二叉树的节点类 ====================
class TreeNode:def __init__(self):self.key   = Noneself.value = Noneself.left  = Noneself.right = None# ==================== 步骤3.根据词频,构建哈夫曼树 ====================
freqs = []
for key, value in freq_map:item = TreeNode()item.key   = keyitem.value = valuefreqs.append(item)while len(freqs) > 1:sorted_freqs = sorted(freqs, key = lambda x : x.value)a = sorted_freqs[0]b = sorted_freqs[1]c = TreeNode()c.key   = a.key   + b.keyc.value = a.value + b.valuec.left  = ac.right = bfreqs = [c] + sorted_freqs[2:]def print_tree(item, tab = 0, name = None):if item is None:returntab_string = "".join(["\t"] * tab)print(f"{tab_string} {name}: node = {item.key}[{item.value}]")print_tree(item.left, tab+1, "left")print_tree(item.right, tab+1, "right")tree = freqs[0]
print_tree(tree)# ==================== 步骤4.将构建的哈夫曼树绘制为图储存下来 ====================
def add_to_graph(G, parent, item, name):if item is None:returnG.add_edge(f"{parent.key}:{parent.value}", f"{item.key}:{item.value}", label=name)add_to_graph(G, item, item.left, "0")add_to_graph(G, item, item.right, "1")def add_graph(G, tree):add_to_graph(G, tree, tree.left, "0")add_to_graph(G, tree, tree.right, "1")A = pgv.AGraph()
add_graph(A, tree)A.layout(prog='dot')
A.draw('graph.png')
image = cv2.imread("graph.png")
plt.figure(figsize=(20, 20))
plt.imshow(image)# ==================== 步骤5.根据哈夫曼树,对给定频率表中的每一个字符,分配二进制串 =======
def find_path_code(tree, key, value, name = ""):if tree is None:return Noneif tree.key == key:return namefound_name = Noneif value < tree.value:found_name = find_path_code(tree.left, key, value, name + "0")if found_name is None:found_name = find_path_code(tree.right, key, value, name + "1")return found_namefreq_coded = {}for key, value in freq_map:coded = find_path_code(tree, key, value, "")freq_coded[key] = codedprint(freq_coded)# ==================== 步骤6.使用已经分配好的二进制串,对给定字符串进行编码 =======
def encode_text(text, freq_coded):encoded = ""for word in text:encoded += freq_coded[word]return encodedencoded = encode_text(text, freq_coded)encoded_length = len(encoded) / 8
raw_length = len(text)
compress_rate = encoded_length / raw_length * 100print("Encode后的表示:", encoded)
print("压缩后的长度(byte):", encoded_length)
print("压缩前的长度(byte):", raw_length)
print(f"压缩率:{compress_rate:.2f}%")# ==================== 步骤7.根据给定的编码后二进制串,以及哈夫曼树,对数据进行解码还原 =======
def find_text_with_path(tree, path, cursor = 0):if tree.left is None and tree.right is None:return tree.key, cursorp = path[cursor]node = Noneif p == "0":node = tree.leftelse:node = tree.rightreturn find_text_with_path(node, path, cursor + 1)def decode_text(encoded_text, tree):output, cursor = find_text_with_path(tree, encoded_text, 0)while cursor < len(encoded_text):word, cursor = find_text_with_path(tree, encoded_text, cursor)output += wordreturn outputprint(decode_text(encoded, tree))

3. 视频流媒体前置知识

先来看看视频流媒体的一些知识,参考自杜老师的 CV 之视频流媒体以及 chatGPT,以下内容均 copy 自 CV 之视频流媒体 PPT 中的内容。

3.1 视频文件的构成

大多视频文件都是类似的形式存在:

在这里插入图片描述

图3-1 视频文件的组成

其中,

  • SPS(Sequence Parameter Set):序列参数集,SPS 包含了视频序列(一系列连续的图像帧)的参数,例如分辨率、帧率、图像采样方式、颜色空间等等。
  • PPS(Picture Parameter Set):图像参数集,PPS 包含了与特定视频帧相关的参数,例如图像类型、参考帧索引等等。
  • SPS 和 PPS 是 H.264 和 H.265 等视频编码标准中的两个关键元素。它们对于视频编码和解码非常重要,可以影响到视频的质量、压缩率、延迟等方面。
  • GoP(Group of Pictures):一个 Group 的大小,例如在上面 GoP = 6,GoP 是视频压缩中的一个重要概念。在视频编码过程中,视频帧被分为不同的组,每组包含若干个连续的视频帧。在一个 GoP 中,通常包含一个关键帧即 I 帧和多个前向预测帧即 P 帧和双向预测帧即 B 帧。场景常常不动的时候,GoP 可以给非常大,减少 I 帧频率

I、P、B帧的概念如下:

  • I帧:关键帧(Intra-coded pictures)只可含有节点宏区块,就像传统的将一张张图片作压缩
    • I 帧最大,内容最全,影响也最大,因为 P 帧和 B 帧需要参考 I 帧
    • 如果显示花屏,则等待下一个 I 帧到来,花屏基本就解除了
    • I 帧也是 IDR 帧(Instantaneous Decoding Refresh),即遇到 I 帧立即刷新显示,并且历史的解码队列清空
  • P帧:前向预测帧(Predictive pictures)含有节点宏区块或预测宏区块,相对于之前的帧,编码器不用记录下 P-frame 中没有改变的 pixel
    • P 帧只储存相对于前一帧发生改变的部分,不用记录没有改变的像素。并且会对运动进行预测。压缩率高于 I 帧
  • B帧:双向预测帧(Bi-predictive pictures)含有节点、预测和前后预测宏区块
    • B 帧具有双向预测能力,相比 P 帧压缩率更高,运动预测更准确。B 帧的数据越多压缩比越好
  • 如果没有 I 帧的存在,则 P、B 解码出的图像会只有运动部分,造成花屏等问题

3.2 视频的编码与封装

视频的编码:将视频画面序列通过一定算法压缩并编码储存,例如 H.264

音频的编码:将音频的序列通过一定算法压缩并编码储存,例如 ACC(Advanced Audio Coding)

视频的封装:将音视频、字幕等信息统一进行封装(或者交错封装),使得形成完整的多媒体文件。主要解决音视频、字幕等信息的封装、播放问题。例如播放时的画面声音同步、字幕同步对齐等。封装格式是容器,储存的内容是编码后的数据

下面是一些常见的编码格式,参考自视频编码与封装

编码格式编码方式特点
h.264(mpeg4)帧间编码网络传播最佳
mpeg2(dvd)帧间编码过时
h.265(hevc)帧间编码未普及
proes(apple)帧内编码高效&优良
dhxhd/hr(avid)帧内编码win支持最佳
cineform(gropro)帧内编码最佳

下面是一些常见的封装格式,参考自视频编码与封装

封装格式对编码的支持
Mov(apple)优良
mp4(动态图像专家组)最佳
mkv/ogg(开源)软件支持不足
wmv(Microsoft)兼容性低下
AVCHD(松下/索尼)结构复杂(通过文件的方式组织)
avi旧,不支持新编码

视频的编码和封装可用下图表示:

在这里插入图片描述

图3-2 视频的编码和封装示例

3.3 H264编码的分层

copy 自【H264】码流结构详解,强烈建议阅读原文!!!

从码流功能的角度可以将 H264 编码分为两层:视频编码层(VCL)和网络提取层(NAL)

  • VCL(Video Coding Layer):进行视频编解码,包括预测(帧间预测和帧内预测),即对视频序列进行编码后得到的裸数据
  • NAL(Network Abstract Layer):负责以网络所要求的恰当的方式对 VCL 数据进行打包和传送

我们需要关注 NAL 的组成单元——NALU

NALU(Network Abstract Layer-Unit):网络提取层单元,对 NAL 的封装,具有标头,头中又储存着 NAL 中的数据类型,例如 SPS、PPS 还是 IDR 等等。NALU 通常头部加入以 0x00,0x00,0x00,0x01 或者 0x00,0x00,0x01 作为起始

NALU 的格式如下图(引用自H264 PDF)所示:

在这里插入图片描述

图3-3 NALU的格式

很明显,NALU 由头和身体两个部分组成:

  • 头:一般存储标志信息,譬如 NALU 的类型。
  • 身体:存储真正的数据

关于 H264 更详细的分层结构如下图所示:

在这里插入图片描述

图3-4 H264详细分层结构
  • 第一层:比特流。该层有两种格式:Annexb 格式和 RTP 格式

  • 第二层:NAL Unit 层。包含了 NAL Header 和 NAL Body 信息

  • 第三层:Slice 层。一帧视频图像可编码成一个或者多个片,每片包含整数个宏块,即每片至少一个宏块,最多时包含整个图像的宏块。

    • 有两个概念比较模糊,切片(Slice)和宏块(macroblock),它们都是 VCL 中的概念(from chatGPT)
    • 在视频编码 VCL 中,Slice 是指将一个帧分成多个小块进行编码,每个小块称为 Slice,它是压缩后得数据流中的基本单元
    • 一个 Slice 可以由多个宏块(macroblock)组成,而宏块是视频压缩中最小的处理单元,它包含了一组连续的像素块,通常为 16x16 像素的正方形区域。宏块被编码后,就可以通过 VLC 进行压缩编码了。
  • 第四层:Slice Data 层

  • 第五层:PCM 类

  • 第六层:残差层

3.4 RTSP之RTP(Real-time Transport Protocol)

RTP 实时传输协议,约定了实时传输数据时的交互方式,是一个网络传输的协议,1996年提出在 RFC 1889 中公布

其报文结构如下图所示,参考自https://en.wikipedia.org/wiki/Real-time_Transport_Protocol

在这里插入图片描述

图3-5 RTP报文格式
  • Version:(2 bits) 是目前协议的版本号码,当前版本号是 2
  • P(Padding):(1 bit) 是用于 RTP 报文(packet)结束点的预留空间,视报文是否需要多余的填塞空间
  • X(Extension):(1 bit) 是否在使用延伸空间于报文之中
  • CC(CSRC count):(4 bits) 包含了 CSRC 数目用于修正标头(fixed header)
  • M(Marker):(1 bit) 是用于应用等级以及其原型(profile)的定义。如果不为零表示目前的资料有特别的程序解译
  • PT(Payload type):(7 bits) 是指 payload 的格式并决定将如何去由应用程序加以解释

3.5 RTSP之RTSP(Real Time Streaming Protocol)

RTSP 实时流协议,约定了具体实施时,如何进行流数据交互。RTP 只关注具体包的封装形式,不关注具体业务逻辑(例如登录、不同地址、交互、多播、开始、暂停、结束)。而 RTSP 则是基于 RTP 为流数据封装的格式之上,明确了业务逻辑的定义,即如果你要登录先发送什么再发送什么,如果你要订阅某个地址需要发送什么

一个 RTSP 包是由一个 RTP 包和 NAL 组成的,如下图所示

在这里插入图片描述

图3-6 直播中的RTSP传输

RTP 中封装了时间戳等信息,NAL 中封装了具体的视频画面信息。使用 RTP 对 NAL 数据进行封装,借由 RTSP 协议进行流传输,客户机接收到数据后,先揭开 RTP 包得到 NAL,然后送到解码器解码并播放

NAL 只是对画面进行编码,没有时间戳等信息,而 MP4 等封装格式需要考虑播放时间戳

3.6 硬件解码

在基于 NVIDIA 显卡上的硬件解码方案,是使用 NVIDIA 提供的 CUVIDAPI,最新称呼为 NVENC 编码、NVDEC 解码

解封包,主要使用 FFMPEG 实现,FFMPEG 可以解开众多封包格式例如 MP4、RTSP、RTP、FLV 等,拿到 NALU 时,即可将其送给 NVDEC 进行解码得到图像

具体见代码部分

https://github.com/shouxieai/hard_decode_trt

https://shouxieai.com/solution/trt/integ-1.21-multi-camera-decoder

4. hard_decode_trt

hard_decode_trt 项目是在 tensorRT_Pro 项目上新增了硬件解码部分

  • 需要配置 tensorRT_Pro 一样的环境
  • 新增 FFmpeg 和 NVDEC 的配置
  • make yolo -j64
  • Yolo 和硬件解码直接对接
  • make demuxer -j64
  • 仅仅解封装得到 h264 的包,并分析是什么帧
  • make hard_decode -j64
  • 硬件解码测试
  • 核心思想是吧 NALU 交替放入解码器进行解码,最后通过 timestamp 进行区分
  • 软解码(FFmpeg)和硬解码(NVDEC),分别消耗 CPU 和 GPU 资源。在多路、大分辨率下体现明显
  • 硬件解码和推理可以允许跨显卡
  • 理解并善于利用的时候,它才可能发挥最大的效果

4.1 环境配置

首先你需要配置和 tensorRT_Pro 一样的环境,需要安装 CUDA、cuDNN、TensorRT、OpenCV、Protobuf 等软件的安装,而关于 CUDA、cuDNN、TensorRT、OpenCV、Protobuf 等软件的安装请参考Ubuntu20.04部署YOLOv5,这里不再赘述。

除此之外还需要新增 FFmpeg 和 NVDEC 的配置,可参考下面编译安装

4.2 编译FFmpeg-4.2

参考自FFMpeg学习笔记–Ubuntu20.04编译FFmpeg、FFplay和FFprobe

描述:最近在学习视频流媒体的编解码,在学习 hard_decode_trt 项目时需要使用到 FFMpeg 第三方库,因此需要编译该库方便后续使用。编译流程按照上文的操作即可,值得注意的是 hard_decode_trt 使用的 FFmpeg-4.2 版本,博主编译过 FFmpeg-5.1 版本,发现在 FFmpeg 中有一些 API 发生了改变,导致 hard_decode_trt 项目无法运行起来,所以为了让问题更加简单化,博主也是编译的 FFmpeg-4.2,其它版本的编译也可以按照上文的流程走,没有问题。

FFMpeg介绍:FFmpeg 是一款跨平台的、开源的音视频处理软件。它可以对音视频进行编解码、转码、剪辑、合并等处理,支持多种格式的音视频文件。在视频流媒体编解码中,FFmpeg 通常被用来进行解封装、编码、解码、转换等操作。它通过将原始的音视频数据流解封装成一帧一帧的数据,然后将这些数据传输给硬件编码器(例如 NVIDIA 的 NVDEC)。硬件编码器将编码后的数据传输回 FFmpeg,再由 FFmpeg 将编码后的数据封装称所需要的视频格式,例如 MP4、AVI 等。FFmpeg 提供了必要的解封装和编解码功能,使得视频编解码更加高效、简单和方便。

4.2.1 下载FFmpeg

下载 4.2 版本的 ffmpeg,指令如下:

wget http://www.ffmpeg.org/releases/ffmpeg-4.2.tar.gz

可以将链接复制到浏览器中下载,外网访问慢,建议开代理。也可以点击 here[pwd:yolo] 下载博主准备好的安装包

解压下载好的压缩包

tar -zxvf ffmpeg-4.2.tar.gz

4.2.2 编译FFmpeg

进入解压后的文件夹

cd ffmpeg-4.2

安装依赖

# 安装ffplay需要的依赖
sudo apt-get install libx11-dev xorg-dev libsdl2-2.0 libsdl2-devsudo apt install clang libfdk-aac-dev libspeex-dev libx264-dev libx265-dev libnuma-devsudo apt install yasm pkg-config libopencore-amrnb-dev libopencore-amrwb-dev

编译FFmpeg

# 查看帮助文档确定需要安装的相关参数
./configure --help

配置相关参数

./configure --disable-static --enable-shared --enable-gpl --enable-version3 --enable-nonfree --enable-ffplay --enable-ffprobe --enable-libx264 --enable-libx265 --enable-debug

注意这里博主与参考的博文设置不太一样,博主新增了 --disable-static --enable-shared 参数,让其编译成动态库且不需要静态库,方便后续 hard_decode_trt 项目的指定。

make编译

make -j24

:make 这步需要的时间一般比较长

安装

sudo make install

耐心等待安装完成即可。

安装完成后的 FFmpeg 头文件在 /usr/local/include 目录下,库文件在 /usr/local/lib 目录下,可执行文件在 /usr/local/bin 目录下

4.2.3 设置环境变量

博主并未设置,若有需求见参考博文

4.2.4 验证

查看 FFmpeg 的版本,由于博主未设置环境变量,所以先要 cd 到 /usr/local/bin 目录下

cd /usr/local/binffmpeg -version

查看 FFmpeg 帮助文档

ffmpeg -hffmpeg -h longffmpeg -h full

4.2.5 卸载FFmpeg

卸载非常简单,指令如下:

# 首先进入 ffmpeg 源码编译的路径
cd ffmpeg-4.2sudo make uninstall

执行完成后可以发现在 /usr/local/include 中 FFmpeg 的头文件没有了,在 /usr/local/lib 中 FFmpeg 的库文件没有了,在 /usr/local/bin 中 FFmpeg 的可执行文件没有了。

这个需求非常有必要,当你发现编译后的 FFmpeg 缺少某些库时,你可以通过 sudo make uninstall 卸载,然后通过 ./configure 重新配置参数编译。当你需要替换其它版本的 FFmepg 时,也可以先通过 sudo make uninstall 卸载,然后下载其它版本的 FFmpeg,通过上述操作重新编译。

4.3 NVIDIA VIDEO_CODEC_SDK

在之前我们提到过视频流硬解码需要用到视频解码的 GPU 硬件加速器引擎,而 NVIDIA 提供了一个用于视频编解码的 SDK 即 NVIDIA VIDEO_CODEC_SDK,它是一个 API 套件,包含高性能工具、样本和文档,适用于 Windows 和 Linux 的硬件加速型视频编码和解码。此 SDK 包含两个硬件加速接口:

  • 用于视频编码加速的 NVENCODE API
  • 用于视频解码加速的 NVDECODE API(旧称 NVCUVID API)

NVENC:硬件加速的视频编码

从 Kepler 这一代开始,NVIDIA GPU 包含基于硬件的编码器(简称为 NVENC),可提供基于硬件的全加速视频编码,且独立于图形性能。由于计算复杂的编码工作流完全卸载至 NVENC,图形引擎和 CPU 可以有更多的事件执行其它操作。

NVDEC:硬件加速的视频解码

NVIDIA GPU 包含基于硬件的解码器(简称为 NVDEC),可为几种热门的编解码器提供基于硬件的全加速视频解码。由于解码工作流完全卸载至 NVDEC,图形引擎和 CPU 可以有更多的事件执行其它操作。NVDEC 比实时解码速度更快,非常适合于转码应用以及视频播放应用。

Copy 自 NVIDIA 视频编解码器 SDK

由于是 SDK 不需要编译,直接下载解压拿来用就行,其下载地址为:

  • https://developer.nvidia.com/nvidia-video-codec-sdk/download

现在最新的 SDK 为 12.0 版本,其系统要求如下图,Linux 下显卡驱动要求为 520.56.06 或者更新,且只支持 NVIDIA Quadro,Tesla,GRID 以及 GeForce 系列产品

在这里插入图片描述

图4-1 Video Code SDK 12.0 系统要求

其历史版本可通过 https://developer.nvidia.com/video-codec-sdk-archive 下载,也可以点击 here[pwd:yolo] 下载博主准备好的安装包(提供 10.0、11.0、11.1、12.0 四个版本的 SDK,根据自己的需求下载即可) ,每个版本都有对应的显卡驱动要求,例如 11.0 要求 Linux 下显卡驱动大于等于 455.27。

下载完成后解压即可,后续可通过 makefile 文件进行其头文件和库文件的指定。

4.4 配置Makefile

主要修改

  1. 修改第 12 行,修改 protobuf 路径
lean_protobuf  := /home/jarvis/protobuf
  1. 修改第 13 行,修改 tensorRT 路径
lean_tensor_rt := /opt/TensorRT-8.4.1.5
  1. 修改第 15 行,修改 OpenCV 路径
lean_opencv    := /usr/local
  1. 修改第 16 行,修改 CUDA 路径
lean_cuda      := /usr/local/cuda-11.6
  1. 修改第 17 行,修改 FFmpeg 路径
lean_ffmpeg    := /usr/local
  1. 修改第 18 行,修改 NVDEC SDK 路径
lean_nvdec     := /home/jarvis/software/Video_Codec_SDK_11.0.10
  1. 修改第 44 行,修改链接库
avresample => swresample

博主编译完 FFmpeg 时,发现没有动态库文件 libavresample,于是修改为了 swresample

  1. 修改第 56 行,修改 -gencode=arch=compute_75,code=sm_75 显卡算力
cu_compile_flags  := -std=c++11 -m64 -Xcompiler -fPIC -g -w -gencode=arch=compute_86,code=sm_86 -O0

显卡对应的号码参考这里:https://developer.nvidia.com/zh-cn/cuda-gpus#compute

博主显卡是 GeForce RTX 3060,所以选择 GeForce 产品,查看到 GeForce RTX 3060 的计算能力为 86,故将上面显卡算力改为 86

在这里插入图片描述

图4-2 显卡算力查询

完整的 Makefile 的内容如下:


cpp_srcs := $(shell find src -name "*.cpp")
cpp_objs := $(cpp_srcs:.cpp=.o)
cpp_objs := $(cpp_objs:src/%=objs/%)
cpp_mk   := $(cpp_objs:.o=.mk)cu_srcs := $(shell find src -name "*.cu")
cu_objs := $(cu_srcs:.cu=.cuo)
cu_objs := $(cu_objs:src/%=objs/%)
cu_mk   := $(cu_objs:.cuo=.cumk)lean_protobuf  := /home/jarvis/protobuf
lean_tensor_rt := /opt/TensorRT-8.4.1.5
lean_cudnn     := /data/sxai/lean/cudnn8.2.2.26
lean_opencv    := /usr/local
lean_cuda      := /usr/local/cuda-11.6
lean_ffmpeg    := /usr/local
lean_nvdec     := /home/jarvis/software/Video_Codec_SDK_11.0.10include_paths := src        \src/application \src/tensorRT	\src/tensorRT/common  \$(lean_protobuf)/include \$(lean_opencv)/include/opencv4 \$(lean_tensor_rt)/include \$(lean_cuda)/include  \$(lean_cudnn)/include \$(lean_ffmpeg)/include \$(lean_nvdec)/Interfacelibrary_paths := $(lean_protobuf)/lib \$(lean_opencv)/lib    \$(lean_tensor_rt)/lib \$(lean_cuda)/lib64  \$(lean_cudnn)/lib \$(lean_ffmpeg)/lib \$(lean_nvdec)/Lib/linux/stubs/x86_64link_librarys := opencv_core opencv_imgproc opencv_videoio opencv_imgcodecs \nvinfer nvinfer_plugin \cuda cublas cudart cudnn \nvcuvid nvidia-encode \avcodec avformat swresample swscale avutil \stdc++ protobuf dlpaths     := $(foreach item,$(library_paths),-Wl,-rpath=$(item))
include_paths := $(foreach item,$(include_paths),-I$(item))
library_paths := $(foreach item,$(library_paths),-L$(item))
link_librarys := $(foreach item,$(link_librarys),-l$(item))# 如果是其他显卡,请修改-gencode=arch=compute_75,code=sm_75为对应显卡的能力
# 显卡对应的号码参考这里:https://developer.nvidia.com/zh-cn/cuda-gpus#compute
# 如果是 jetson nano,提示找不到-m64指令,请删掉 -m64选项。不影响结果
cpp_compile_flags := -std=c++11 -fPIC -m64 -g -fopenmp -w -O0
cu_compile_flags  := -std=c++11 -m64 -Xcompiler -fPIC -g -w -gencode=arch=compute_86,code=sm_86 -O0   # 修改 
link_flags        := -pthread -fopenmp -Wl,-rpath='$$ORIGIN'cpp_compile_flags += $(include_paths)
cu_compile_flags  += $(include_paths)
link_flags 		  += $(library_paths) $(link_librarys) $(paths)ifneq ($(MAKECMDGOALS), clean)
-include $(cpp_mk) $(cu_mk)
endifpro    : workspace/pro
trtpyc : python/trtpy/libtrtpyc.soworkspace/pro : $(cpp_objs) $(cu_objs)@echo Link $@@mkdir -p $(dir $@)@g++ $^ -o $@ $(link_flags)python/trtpy/libtrtpyc.so : $(cpp_objs) $(cu_objs)@echo Link $@@mkdir -p $(dir $@)@g++ -shared $^ -o $@ $(link_flags)objs/%.o : src/%.cpp@echo Compile CXX $<@mkdir -p $(dir $@)@g++ -c $< -o $@ $(cpp_compile_flags)objs/%.cuo : src/%.cu@echo Compile CUDA $<@mkdir -p $(dir $@)@nvcc -c $< -o $@ $(cu_compile_flags)objs/%.mk : src/%.cpp@echo Compile depends CXX $<@mkdir -p $(dir $@)@g++ -M $< -MF $@ -MT $(@:.mk=.o) $(cpp_compile_flags)objs/%.cumk : src/%.cu@echo Compile depends CUDA $<@mkdir -p $(dir $@)@nvcc -M $< -MF $@ -MT $(@:.cumk=.o) $(cu_compile_flags)demuxer : workspace/pro@cd workspace && ./pro demuxerhard_decode : workspace/pro@cd workspace && ./pro hard_decodeyolo : workspace/pro@cd workspace && ./pro yolodebug :@echo $(includes)clean :@rm -rf objs workspace/pro python/trtpy/libtrtpyc.so python/build python/dist python/trtpy.egg-info python/trtpy/__pycache__@rm -rf workspace/single_inference@rm -rf workspace/scrfd_result workspace/retinaface_result@rm -rf workspace/YoloV5_result workspace/YoloX_result@rm -rf workspace/face/library_draw workspace/face/result@rm -rf build@rm -rf python/trtpy/libplugin_list.so.PHONY : clean yolo alphapose fall debug

4.5 运行

Makefile 文件修改好后,执行在终端运行指令即可,主要有三个案例

  • make demuxer -j64
    • 解封装得到 h264 的包,并分析是什么帧

运行结果如下图所示:

在这里插入图片描述

图4-3 FFmpeg解封装

这个案例演示了如何使用 FFmpeg 库来对 mp4 格式视频文件进行解封装,并输出视频文件中的 NALU 数据。

在输出时,首先输出了 Extra Data(码流的第一个 NALU),其代表了 H.264 的序列参数集 (SPS) 和图像参数集 (PPS) 信息。然后对每个 NALU 数据包进行了循环处理,包括获取 NALU 的帧类型、NALU 数据的长度和时间戳。

  • make hard_decode -j64
    • 硬件解码测试

运行效果如下图所示:

在这里插入图片描述

图4-4 硬件解码测试

这个案例演示了如何利用 FFmpeg 和 NVIDIA 的 NVDEC 进行硬件解码测试,可以在输入视频文件中解析并解码出视频流数据,并将解码后的视频帧以图像的形式存储下来。具体实现过程包括以下几个步骤:

  1. 创建 FFmpeg 解封装器,打开输入视频文件
  2. 创建 NVIDIA 硬件解码器,获取解码器的配置信息
  3. 获取输入视频流数据,并将其送入解码器进行解码
  4. 解码后的数据为 YUV-NV12 格式,需要将其转换为 BGR 格式的图像,以便可以将其存储成图像文件
  5. 将解码后的视频帧以图像的形式存储下来

与第一个案例相比,该案例使用 NVDEC 进行硬件解码,其速度更快,而且可以充分利用 GPU 资源,提高解码效率。

  • make yolo -j64
    • Yolo和硬件解码直接对接

运行结果如下图所示:

在这里插入图片描述

图4-5 Yolo和硬解码对接

这个案例演示了如何利用 FFmpeg 和 NVDEC 对视频进行硬件解码,并将解码后的视频帧送入 YOLO 检测器进行检测,并输出检测结果。

首先,创建了一个 YOLO 检测器对象,并通过调用 create_ffmpeg_demuxercreate_cuvid_decoder 函数创建了 FFmpeg 解封包器和 NVIDIA 硬件解码器对象。接着,使用解封包器获取视频的头信息,并将其通过解码器进行解码。然后,在一个循环中,获取每一帧视频数据并解码,将解码后的视频帧作为输入,送入 YOLO 检测器进行检测,并输出检测结果。最后,将检测结果可视化后保存为图片。

从日志输出中,我们可以看到软件解码事件为 158.87ms,硬件解码事件为 310.50ms,为什么硬件解码还会比软件解码时间更长呢?

硬件解码通常比软件解码更快,但在这种情况下,可能是因为硬件解码加速器 NVDEC 不太适用于单路视频、小分辨率这样的情况。因此,在这种情况下,软件解码可能比硬件解码更快。当涉及到多路、高分辨率视频时,硬件加速器的优势更为明显。

我们可以做一个简单的试验,下面是对一个高分辨率的单路视频流进行检测,可以看到软件解码时间为 1056.77ms,硬件解码时间为 1096.46ms,二者差不多

在这里插入图片描述

图4-6 Yolo和硬解码对接(大分辨率下)

同时在 workspace/soft 和 workspace/hard 目录下分别储存着对应软硬件解码检测的结果,如下所示:

在这里插入图片描述

图4-7 检测效果图

结语

本篇博客主要和大家分享了一些视频流媒体相关的知识,并配置运行了 hard_decode_trt 硬件解码框架,对视频编码解码有了一个大概的认识,了解了编解码可以通过硬件来完成加速工作,当需要推理多路、大分辨率视频时效果非常显著。由于初学,博主在这里只做了最简单的实现,并没有对代码进行深究,需要各位看官自行去了解了😄。

感谢各位看到最后,创作不易,读后有收获的看官请帮忙点个👍

下载链接

  • Linux主机软件配置安装包[pwd:yolo]

参考

  • https://github.com/shouxieai/hard_decode_trt
  • https://github.com/shouxieai/tensorRT_Pro
  • 二维傅里叶变换是怎么进行的?
  • DCT变换、DCT反变换、分块DCT变换
  • JPEG图像压缩算法流程详解
  • 哈夫曼树原理,及构造方法
  • 视频编码与封装
  • 【H264】码流结构详解
  • H264 PDF
  • 维基百科之RTSP
  • FFMpeg学习笔记–Ubuntu20.04编译FFmpeg、FFplay和FFprobe
  • NVIDIA 视频编解码器 SDK

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

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

相关文章

《云计算好用工具清单(2023版)》.pdf

其实&#xff0c;自工信部在2018年印发《推动企业上云实施指南&#xff08;2018-2020年&#xff09;》以来&#xff0c;国家政策就已经显示出不断支持与引导着我国云计算行业的发展的态势。 根据美国软件公司Flexera《2021年云计算市场发展状态报告》数据显示&#xff0c;36%的…

基于TCP交互的安卓chatGLM客户端

基于TCP交互的安卓chatGLM客户端 基本原理 随着chatGPT的火爆发展&#xff0c;如何将其快速实现部署和访问成为了急需解决的问题&#xff0c;为此&#xff0c;开发了一个基于TCP交互的安卓chatGLM客户端系统。 其中 后端在开源的 chatGLM-6B 的基础上&#xff0c;使用socket…

aardio + Python 可视化快速开发桌面程序,一键生成独立 EXE

&#x1f680; 优质资源分享 &#x1f680; 学习路线指引&#xff08;点击解锁&#xff09;知识定位人群定位&#x1f9e1; Python实战微信订餐小程序 &#x1f9e1;进阶级本课程是python flask微信小程序的完美结合&#xff0c;从项目搭建到腾讯云部署上线&#xff0c;打造一…

chatgpt赋能python:Python手游:如何用Python技术构建一个优秀的手游?

Python手游&#xff1a;如何用Python技术构建一个优秀的手游&#xff1f; 在当今的数字时代&#xff0c;游戏已经成为一种最流行的娱乐方式之一。尤其是手游&#xff0c;在移动设备上已经占据了巨大市场份额。通过Python编程语言&#xff0c;我们可以使用其强大的功能来构建出…

chatgpt赋能python:Python拦截发包实现网络安全

Python拦截发包实现网络安全 网络安全是当前社会中一个非常重要的话题&#xff0c;无论是企业还是个人都需要采取有效措施保护自身隐私和数据安全。在网络安全中&#xff0c;拦截发包是一个非常有用的技术手段。本文将介绍如何利用Python实现拦截发包&#xff0c;从而实现网络…

AutoCV:Python基础总结

目录 Python基础总结前言Python基础课程总结第一课&#xff1a;开发环境和基本数据类型第二课&#xff1a;控制流程和函数第三课&#xff1a;闭包和根号2的求解第四课&#xff1a;类和三大神器第五课&#xff1a;文件IO和pickle、json第六课&#xff1a;作用域、模块和包、Data…

AutoCV第一课:Python基础

目录 Python基础注意事项一、2023/3/24更新一、2023/3/25更新前言1.开发环境1.1 作业 2.变量和基本数据类型2.1 python常见数据类型2.2 作业2.3 拓展-Python格式化输出 3.算数运算和变量解包3.1 zip函数3.2 拓展-后处理代码详解 4.字符串基本使用4.1 拼接4.2 重复4.3 提取单个字…

Wireshark的抓包和分析,看这篇就够了!

点击上方蓝字 关注【程序IT圈】 WireShark是一个网络封包分析软件。网络封包分析软件的功能是撷取网络封包&#xff0c;并尽可能显示出最为详细的网络封包资料。Wireshark使用WinPCAP作为接口&#xff0c;直接与网卡进行数据报文交换。在网络封包和流量分析领域有着十分强大功能…

全网超详细的Linux iptables命令详解以及详解iptables-save和iptables-restore命令

文章目录 1. 文章引言2. iptables的四表五链2.1 何为四表2.2 何为五链 3. iptables语法格式4. 规则的查看与清除5. 防火墙的备份与还原5.1 iptables-save命令5.2 iptables-restore命令 1. 文章引言 最近在学习Linux iptables&#xff0c;从而知道&#xff0c;它是Linux防火墙系…

chatgpt赋能python:Python拦截游戏封包:打造自己的游戏辅助工具

Python拦截游戏封包&#xff1a;打造自己的游戏辅助工具 随着游戏领域的不断发展&#xff0c;越来越多的玩家开始使用游戏辅助工具提高自己的游戏水平。而Python拦截游戏封包技术的出现&#xff0c;让玩家们能够更加自由地打造适合自己的游戏辅助工具&#xff0c;实现更高效的…

chatgpt赋能python:如何用Python炒股赚钱?

如何用Python 炒股赚钱&#xff1f; 在过去的几年里&#xff0c;随着互联网技术的飞速发展&#xff0c;许多人开始关注股票市场。许多股票交易者也利用数据分析和机器学习技术来辅助他们做出更好的交易决策。Python&#xff0c;作为一种快捷&#xff0c;高效&#xff0c;易于学…

chatgpt赋能python:如何用Python炒股

如何用Python炒股 Python是一种功能强大、易用且广泛使用的编程语言。它受到众多程序员的追捧&#xff0c;不仅因为它易于学习&#xff0c;而且因为它有许多优秀的库和工具可供使用。现在&#xff0c;Python不仅可以用于Web应用程序、数据科学和人工智能等领域&#xff0c;还可…

chatgpt赋能Python-python_ai炒股

介绍 随着人工智能技术的不断发展&#xff0c;越来越多的行业开始应用AI技术提升效率和准确性。其中&#xff0c;炒股领域是一个非常典型的案例。而Python则成为了广泛被应用于AI炒股的主流语言之一。本文将介绍Python在AI炒股方面的应用以及相关的技术和策略。 什么是AI炒股…

chatgpt赋能python:Python用于炒股

Python用于炒股 随着互联网和数据科学的发展&#xff0c;越来越多的投资者开始依靠计算机和数据科学来进行更高效的投资。Python是一种广泛使用的编程语言&#xff0c;可以帮助投资者更好地处理数据和自动化交易。本文将简要介绍Python如何用于炒股&#xff0c;并分享一些有用…

chatGPT代替了我的工作,却让他加薪了50%

前言 最近ChatGPT这个技术发展&#xff0c;着实有点让人眼花缭乱&#xff0c;所以&#xff0c;搞来了一份表格&#xff0c;帮你理清它的发展思路。 简单来说&#xff0c;ChatGPT是一种全新聊天机器人模型&#xff0c;也可以称之为“生成型AI”。 点击免费领取&#xff1a; CS…

ChatGPT代替了人类思考,学习还有什么用?

ChatGPT是由OpenAI在2022年11月30日发布的聊天机器人。如果你认为它只是另一个 Siri、小度&#xff0c;你就大大低估了这个产品的革命性威力。 这个产品的本质&#xff0c; 不是语音机器人&#xff0c;而是代替人类思考 。 过去你想知道什么&#xff0c;会用谷歌、百度搜索&…

Midjourney|文心一格prompt教程[进阶篇]:Midjourney Prompt 高级参数、各版本差异、官方提供常见问题

Midjourney|文心一格prompt教程[进阶篇]&#xff1a;Midjourney Prompt 高级参数、各版本差异、官方提供常见问题 1.Midjourney Prompt 高级参数 Quality 图片质量是另一个我比较常用的属性&#xff0c;首先需要注意这个参数并不影响分辨率&#xff0c;并不改变分辨率&#x…

我的同事把AI带进公司,让它写代码、写稿、画图……

字节跳动的同学&#xff0c;真的超级爱研究新技术。 一有什么新技术出现&#xff0c;大家就纷纷去探索、组团实践。 拿最近热门的 AIGC 来说&#xff0c;不管是业务需求&#xff0c;还是私下探索&#xff0c;不少人都把这些能写会画的 AI 引进了公司。 这下可是造福了字节同…

保姆级教程:手把手教你拿下雅思写作7分

在留学路上&#xff0c;雅思考试是绕不开的一道坎。然而&#xff0c;众所周知&#xff0c;雅思学习热度高&#xff0c;学习难度大&#xff0c;而且很多人找不到合适的学习方法。在这里&#xff0c;我们以雅思写作中的大作文为例&#xff0c;从大作文的结构拆解、学习的任务拆分…

Prompt learning 教学[最终篇]:Chatgpt使用场景推荐、优秀学习资料推荐、AI工具推荐

Prompt learning 教学[最终篇]&#xff1a;Chatgpt使用场景推荐、优秀学习资料推荐、AI工具推荐 1.chatgpt使用场景推荐 各位应该在各种平台看到不少可以尝试使用的场景&#xff0c;我这里仅收录&#xff1a; 有意思的场景&#xff1a;一般比较垂直或者小众&#xff0c;或者出…