【音视频】FFmpeg内存模型
FFmpeg内存模型
从现有的Packet拷贝一个新Packet的时候,有两种情况:
- 两个Packet的buf引用的是同一数据缓存空间,这时候要注意数据缓存空间的释放问题;
- 两个Packet的buf引用不同的数据缓存空间,每个Packet都有数据缓存空间的copy;
关系介绍
- AVBuffer:核心数据容器,存储实际数据(
data
),通过refcount
实现引用计数,free()
用于定义内存释放逻辑。 - AVBufferRef:作为 AVBuffer 的引用,允许不同组件(如 AVPacket、AVFrame)共享同一份数据。它内部的
buffer
指向 AVBuffer,data
和size
本质是对 AVBuffer 对应成员的“转发”。 - AVPacket/AVFrame:通过 AVBufferRef 管理数据。例如,AVPacket 的
buf
成员、AVFrame 的buf
数组,都依赖 AVBufferRef 实现数据共享与内存管理。
代码示例
以下代码演示 AVBuffer、AVBufferRef、AVPacket 的关联逻辑:
#include <libavutil/buffer.h>
#include <libavcodec/avcodec.h>int main() {// 1. 创建 AVBuffer(分配 1024 字节内存)AVBufferRef *buffer_ref = av_buffer_alloc(1024);if (!buffer_ref) {return -1;}AVBuffer *buffer = buffer_ref->buffer; // 获取关联的 AVBufferuint8_t *data = buffer->data; // AVBuffer 的数据指针// 2. 创建 AVPacket,并关联 AVBufferRefAVPacket *pkt = av_packet_alloc();if (!pkt) {av_buffer_unref(&buffer_ref);return -1;}pkt->buf = av_buffer_ref(buffer_ref); // AVPacket 引用 AVBufferRef// 此时,AVPacket 的数据指针 pkt->data 等同于 buffer->data(即同一份数据)// 3. 验证引用计数printf("Initial refcount: %u\n", buffer->refcount); // 输出 2(AVBufferRef 自身 + AVPacket 引用)// 4. 释放资源av_packet_free(&pkt); // 减少 AVPacket 对 AVBufferRef 的引用av_buffer_unref(&buffer_ref); // 若引用计数归 0,AVBuffer 内存自动释放return 0;
}
代码解析
-
创建 AVBuffer:通过
av_buffer_alloc
分配内存,返回 AVBufferRef。此时,AVBufferRef 的buffer
指向内部的 AVBuffer,data
指向实际内存。 -
关联 AVPacket:使用
av_buffer_ref
让 AVPacket 的buf
引用 AVBufferRef。此时,AVPacket 的data
与 AVBuffer 的data
指向同一块内存。 -
引用计数:每次
av_buffer_ref
会增加引用计数(refcount
),av_buffer_unref
减少计数。当计数为 0 时,AVBuffer 自动释放内存,避免手动管理内存的繁琐与风险。 -
AVFrame 同理:AVFrame 的
buf
数组使用类似逻辑,例如:AVFrame *frame = av_frame_alloc(); frame->buf[0] = av_buffer_ref(buffer_ref); // AVFrame 引用 AVBufferRef
通过这种设计,FFmpeg 实现了高效的内存共享与自动释放,减少内存泄漏风险。
一个 AVBuffer
结构体通常存储 一个分量 的数据,而非完整的 YUV 所有分量。以常见的平面格式(如 YUV420P)为例:
- Y 分量:单独由一个
AVBuffer
存储,对应AVFrame->buf[0]
关联的AVBufferRef
指向的AVBuffer
。 - U 分量:由另一个
AVBuffer
存储,对应AVFrame->buf[1]
关联的AVBufferRef
指向的AVBuffer
。 - V 分量:再由一个
AVBuffer
存储,对应AVFrame->buf[2]
关联的AVBufferRef
指向的AVBuffer
。
这种设计下,每个 AVBuffer
负责管理单一数据平面(分量)的内存,通过 AVFrame->buf
数组中的多个 AVBufferRef
,实现对 YUV 各分量的独立内存管理(如分配、引用计数、释放等)。若为打包格式(如 YUV420P 非平面形式),虽数据存储方式不同,但 AVBuffer
仍遵循“单一内存块管理”原则,不会同时存储多个独立分量的完整数据。
更为精确的模型
FFmpeg内存模型-引用计数
对于多个AVPacket共享同一个缓存空间,FFmpeg使用的引用计数的机制(reference-count):
- 初始化引用计数为0,只有真正分配AVBuffer的时候,引用计数初始化为1;
- 当有新的Packet引用共享的缓存空间时,就将引用计数+1;
- 当释放了引用共享空间的Packet,就将引用计数-1;引用计数为0时,就释放掉引用的缓存空间AVBuffer。
- AVFrame也是采用同样的机制。
AVPacket常用API
AVPacket *av_packet_alloc(void); | 分配AVPacket 这个时候和buffer没有关系 |
---|---|
void av_packet_free(AVPacket **pkt); | 释放AVPacket 和_alloc对应 |
void av_init_packet(AVPacket *pkt); | 初始化AVPacket 只是单纯初始化pkt字段 |
int av_new_packet(AVPacket *pkt, int size); | 给AVPacket的buf分配内存,引 用计数初始化为1 |
int av_packet_ref(AVPacket *dst, const AVPacket *src) | 增加引用计数 |
void av_packet_unref(AVPacket *pkt); | 减少引用计数 |
void av_packet_move_ref(AVPacket *dst, AVPacket *src); | 转移引用计数 |
AVPacket *av_packet_clone(const AVPacket *src); | 等于 av_packet_alloc()+av_packet_ref() |
AVFrame常用API
函数原型 | 功能描述 |
---|---|
AVFrame *av_frame_alloc(void); | 分配 AVFrame |
void av_frame_free(AVFrame **frame); | 释放 AVFrame |
int av_frame_ref(AVFrame *dst, const AVFrame *src); | 增加引用计数 |
void av_frame_unref(AVFrame *frame); | 减少引用计数 |
void av_frame_move_ref(AVFrame *dst, AVFrame *src); | 转移引用计数 |
int av_frame_get_buffer(AVFrame *frame, int align); | 根据 AVFrame 分配内存 |
AVFrame *av_frame_clone(const AVFrame *src); | 等同于 av_frame_alloc() + av_frame_ref() |
测试代码
宏定义
#define MEM_ITEM_SIZE (20*1024*102)
#define AVPACKET_LOOP_COUNT 1000
测试1
void av_packet_test1()
{AVPacket *pkt = NULL;int ret = 0;pkt = av_packet_alloc();ret = av_new_packet(pkt, MEM_ITEM_SIZE); // 引用计数初始化为1memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);av_packet_unref(pkt); // 要不要调用av_packet_free(&pkt); // 如果不free将会发生内存泄漏,内部调用了 av_packet_unref
}
下面将对 av_packet_test1
函数的代码进行详细剖析:
代码功能概述
av_packet_test1
函数的主要功能是创建一个 AVPacket
对象,为其分配数据缓冲区,尝试向该缓冲区复制数据,之后释放 AVPacket
及其关联的数据缓冲区,以此避免内存泄漏。
代码逐行分析
1. 变量声明
AVPacket *pkt = NULL;
int ret = 0;
pkt
:这是一个指向AVPacket
结构体的指针,初始化为NULL
。AVPacket
是 FFmpeg 里用于存储压缩媒体数据(像视频帧、音频帧等)的结构体。ret
:用于存储函数调用的返回值,初始化为 0。
2. 分配 AVPacket
结构体
pkt = av_packet_alloc();
- 调用
av_packet_alloc
函数,在堆上分配一个新的AVPacket
结构体实例,并且把结构体的成员初始化为默认值。若分配成功,pkt
会指向新分配的AVPacket
;若失败,pkt
为NULL
。
3. 为 AVPacket
分配数据缓冲区
ret = av_new_packet(pkt, MEM_ITEM_SIZE); // 引用计数初始化为1
av_new_packet
函数的作用是为AVPacket
分配一个大小为MEM_ITEM_SIZE
字节的数据缓冲区。- 若分配成功,返回值
ret
为 0,同时AVPacket
关联的数据缓冲区的引用计数会初始化为 1。 - 若分配失败,
ret
会是一个负的错误码。
4. 复制数据到 AVPacket
的数据缓冲区
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
memccpy
是 C 标准库中的函数,用于在内存之间复制数据。pkt->data
指向AVPacket
的数据缓冲区,也就是目标内存区域。(void *)&av_packet_test1
是源内存区域的地址,av_packet_test1
可能是一个自定义的变量或结构体。1
是要查找的字符,当在源数据中碰到这个字符时,复制操作会停止。MEM_ITEM_SIZE
是最多要复制的字节数。
5. 减少 AVPacket
引用计数
av_packet_unref(pkt); // 要不要调用
av_packet_unref
函数会减少AVPacket
关联的数据缓冲区的引用计数。当引用计数降为 0 时,会释放实际的数据缓冲区内存,同时把AVPacket
结构体的成员重置为默认值。- 在此处,调用
av_packet_unref
并非必需,因为av_packet_free
函数内部会自动调用av_packet_unref
。不过,调用它也不会有问题,只是会多执行一次减少引用计数的操作。
6. 释放 AVPacket
结构体
av_packet_free(&pkt); // 如果不free将会发生内存泄漏,内部调用了 av_packet_unref
av_packet_free
函数会释放AVPacket
结构体所占用的内存。在释放之前,它会自动调用av_packet_unref
处理AVPacket
关联的数据缓冲区,确保数据缓冲区的引用计数被正确处理,避免内存泄漏。- 该函数接收一个指向
AVPacket
指针的指针作为参数,释放后会把传入的指针置为NULL
,防止出现悬空指针。
测试2
void av_packet_test2()
{AVPacket *pkt = NULL;int ret = 0;pkt = av_packet_alloc();ret = av_new_packet(pkt, MEM_ITEM_SIZE);memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);printf("size1 = %p",pkt->buf);av_init_packet(pkt); // 这个时候init就会导致内存无法释放printf("size2 = %p",pkt->buf);av_packet_free(&pkt);
}
代码功能概述
该函数的主要目的是创建一个 AVPacket
对象,为其分配数据缓冲区,向缓冲区复制数据,然后释放 AVPacket
及其关联的数据缓冲区。
代码逐行分析
1. 变量声明与 AVPacket
分配
AVPacket *pkt = NULL;
int ret = 0;pkt = av_packet_alloc();
- 声明一个指向
AVPacket
的指针pkt
并初始化为NULL
,同时声明一个用于存储函数返回值的变量ret
。 - 调用
av_packet_alloc
函数在堆上分配一个新的AVPacket
结构体实例,并将其地址赋给pkt
。
2. 为 AVPacket
分配数据缓冲区
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
av_new_packet
函数为AVPacket
分配一个大小为MEM_ITEM_SIZE
字节的数据缓冲区。- 若分配成功,
pkt->data
指向新分配的缓冲区,同时数据缓冲区的引用计数初始化为 1。
3. 复制数据到 AVPacket
的数据缓冲区
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
- 使用
memccpy
函数将av_packet_test1
指向的数据复制到pkt->data
所指向的缓冲区中,最多复制MEM_ITEM_SIZE
个字节,当遇到值为 1 的字符时停止复制。
4. 调用 av_init_packet(pkt)
av_init_packet(pkt);
- 这一步是问题所在。
av_init_packet
是一个已被弃用的函数,其作用是将AVPacket
结构体的成员初始化为默认值。 - 调用
av_init_packet
会将pkt->buf
设置为NULL
,同时还会重置其他一些成员。 - 由于
pkt->buf
被设置为NULL
,后续调用av_packet_free
时,av_packet_unref
无法正确识别之前分配的数据缓冲区,从而导致数据缓冲区的引用计数无法正确处理,最终造成内存泄漏。
5. 释放 AVPacket
av_packet_free(&pkt);
av_packet_free
函数会先调用av_packet_unref
来处理AVPacket
关联的数据缓冲区,然后释放AVPacket
结构体本身的内存。- 但由于
av_init_packet
已经将pkt->buf
设置为NULL
,av_packet_unref
无法正确释放数据缓冲区,只释放了AVPacket
结构体本身的内存。
测试3
void av_packet_test3()
{AVPacket *pkt = NULL;AVPacket *pkt2 = NULL;int ret = 0;pkt = av_packet_alloc();ret = av_new_packet(pkt, MEM_ITEM_SIZE);memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);pkt2 = av_packet_alloc(); // 必须先allocav_packet_move_ref(pkt2, pkt);//内部其实也调用了av_init_packetav_init_packet(pkt);av_packet_free(&pkt);av_packet_free(&pkt2);
}
代码逐行分析
1. 变量声明
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;
- 声明了两个指向
AVPacket
结构体的指针pkt
和pkt2
,并初始化为NULL
。 - 声明一个整型变量
ret
,用于存储函数调用的返回值。
2. 分配并初始化第一个 AVPacket
pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
av_packet_alloc()
:分配一个新的AVPacket
结构体,并将其地址赋给pkt
。av_new_packet(pkt, MEM_ITEM_SIZE)
:为pkt
分配一个大小为MEM_ITEM_SIZE
字节的数据缓冲区。如果分配成功,pkt->data
指向新分配的缓冲区,同时数据缓冲区的引用计数初始化为 1。返回值存储在ret
中,若返回值小于 0 则表示分配失败。memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE)
:将av_packet_test1
指向的数据复制到pkt->data
所指向的缓冲区中,最多复制MEM_ITEM_SIZE
个字节,当遇到值为 1 的字符时停止复制。
3. 分配第二个 AVPacket
pkt2 = av_packet_alloc(); // 必须先alloc
使用 av_packet_alloc()
分配一个新的 AVPacket
结构体,并将其地址赋给 pkt2
。在进行数据引用转移之前,必须先为 pkt2
分配内存。
4. 转移数据引用
av_packet_move_ref(pkt2, pkt); // 内部其实也调用了av_init_packet
av_packet_move_ref(pkt2, pkt)
函数将 pkt
的数据引用转移到 pkt2
上。调用该函数后,pkt
不再拥有数据缓冲区的引用,其成员会被重置为默认值,而 pkt2
接管数据缓冲区的引用。
5. 再次调用 av_init_packet
av_init_packet(pkt);
这一步是多余且可能会带来问题的操作。因为 av_packet_move_ref
已经将 pkt
的成员重置为默认值,再次调用 av_init_packet(pkt)
并不会有额外的作用。并且如果 av_packet_move_ref
出现异常,这一步操作可能会进一步破坏 pkt
的状态。
6. 释放 AVPacket
av_packet_free(&pkt);
av_packet_free(&pkt2);
av_packet_free(&pkt)
:释放pkt
所指向的AVPacket
结构体。由于之前av_packet_move_ref
已经将pkt
的数据引用转移走,此时pkt
不持有数据缓冲区的引用,所以只会释放AVPacket
结构体本身的内存。av_packet_free(&pkt2)
:释放pkt2
所指向的AVPacket
结构体及其关联的数据缓冲区。因为pkt2
持有数据缓冲区的引用,调用av_packet_free
会先调用av_packet_unref
减少数据缓冲区的引用计数,当引用计数降为 0 时释放数据缓冲区的内存,然后释放AVPacket
结构体本身的内存。
测试4
av_packet_test4
函数是一个用于测试 AVPacket
内存管理和引用计数机制的函数,其目的是展示由于不当使用 av_init_packet
而导致的内存泄漏问题。下面我们对该函数进行详细的逐行分析。
1. 变量声明
AVPacket *pkt = NULL;
// av_packet_alloc()没有必要,因为av_packet_clone内部有调用 av_packet_alloc
AVPacket *pkt2 = NULL;
int ret = 0;
pkt
和pkt2
是指向AVPacket
结构体的指针,初始化为NULL
。pkt
用于后续创建和操作一个AVPacket
对象,pkt2
则用于克隆pkt
。ret
是一个整型变量,用于存储函数调用的返回值,方便后续判断操作是否成功。
2. 创建并初始化第一个 AVPacket
pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
av_packet_alloc()
:动态分配一个新的AVPacket
结构体,并将其地址赋给pkt
。av_new_packet(pkt, MEM_ITEM_SIZE)
:为pkt
分配一个大小为MEM_ITEM_SIZE
字节的数据缓冲区。如果分配成功,pkt->data
指向该缓冲区,同时数据缓冲区的引用计数初始化为 1。ret
存储该函数的返回值,若小于 0 则表示分配失败。memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE)
:将av_packet_test1
指向的数据复制到pkt->data
所指向的缓冲区中,最多复制MEM_ITEM_SIZE
个字节,当遇到值为 1 的字符时停止复制。
3. 克隆 AVPacket
pkt2 = av_packet_clone(pkt); // av_packet_alloc()+av_packet_ref(), 调用该函数后,pkt和pkt2对应的buf引用计数变成2
av_packet_clone(pkt)
:该函数相当于先调用av_packet_alloc
分配一个新的AVPacket
结构体,再调用av_packet_ref
将pkt
的数据引用复制到新的AVPacket
上。因此,调用该函数后,pkt
和pkt2
共享同一份数据缓冲区,数据缓冲区的引用计数变为 2。
4. 故意破坏 pkt
的引用管理
av_init_packet(pkt); // 这里是故意去做init的操作,让这个函数出现内存泄漏
av_init_packet(pkt)
:该函数会将pkt
的成员重置为默认值,其中包括将pkt->buf
置为NULL
。pkt->buf
是一个指向AVBufferRef
的指针,用于管理数据缓冲区的引用计数。将其置为NULL
会破坏pkt
与数据缓冲区的引用关系,导致后续无法正确处理引用计数。
5. 释放 pkt
av_packet_free(&pkt); // pkt在调用av_init_packet后,对应的buf被置为NULL,在调用av_packet_free没法做引用计数-1的操作
av_packet_free(&pkt)
:该函数会先调用av_packet_unref
来减少pkt
关联的数据缓冲区的引用计数,然后释放pkt
结构体本身的内存。但由于之前av_init_packet(pkt)
已经将pkt->buf
置为NULL
,av_packet_unref
无法找到对应的AVBufferRef
,也就无法减少数据缓冲区的引用计数。因此,数据缓冲区的引用计数仍然为 2。
6. 释放 pkt2
av_packet_free(&pkt2); // 触发引用计数变为1,但因为不是0,所以buf不会被释放,导致内存泄漏
av_packet_free(&pkt2)
:同样,该函数会先调用av_packet_unref
来减少pkt2
关联的数据缓冲区的引用计数,然后释放pkt2
结构体本身的内存。由于pkt
之前没有正确减少引用计数,此时调用av_packet_unref
只会将数据缓冲区的引用计数减为 1,而不是 0。因此,数据缓冲区的内存不会被释放,从而导致内存泄漏。
测试5
void av_packet_test5()
{AVPacket *pkt = NULL;AVPacket *pkt2 = NULL;int ret = 0;pkt = av_packet_alloc(); //if(pkt->buf) // 打印referenc-counted,必须保证传入的是有效指针{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));}ret = av_new_packet(pkt, MEM_ITEM_SIZE);if(pkt->buf) // 打印referenc-counted,必须保证传入的是有效指针{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));}memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);pkt2 = av_packet_alloc(); // 必须先allocav_packet_move_ref(pkt2, pkt); // av_packet_move_ref
// av_init_packet(pkt); //av_packet_move_refav_packet_ref(pkt, pkt2);av_packet_ref(pkt, pkt2); // 多次ref如果没有对应多次unref将会内存泄漏if(pkt->buf) // 打印referenc-counted,必须保证传入的是有效指针{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));}if(pkt2->buf) // 打印referenc-counted,必须保证传入的是有效指针{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));}av_packet_unref(pkt); // 将为2av_packet_unref(pkt); // 做第二次是没有用的if(pkt->buf)printf("pkt->buf没有被置NULL\n");elseprintf("pkt->buf已经被置NULL\n");if(pkt2->buf) // 打印referenc-counted,必须保证传入的是有效指针{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));}av_packet_unref(pkt2);av_packet_free(&pkt);av_packet_free(&pkt2);
}
代码功能概述
该函数主要用于测试 FFmpeg 中 AVPacket
的内存分配、数据操作、引用转移、引用计数管理以及释放等操作,同时通过打印引用计数来观察这些操作对引用计数的影响,以此展示引用计数管理不当可能导致的问题。
代码逐行分析
1. 变量声明
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;
声明两个 AVPacket
指针 pkt
和 pkt2
并初始化为 NULL
,同时声明一个整型变量 ret
用于存储函数调用的返回值。
2. 分配 pkt
并检查引用计数
pkt = av_packet_alloc();
if(pkt->buf)
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));
}
av_packet_alloc()
分配一个新的AVPacket
结构体给pkt
。- 由于此时
pkt
还未分配数据缓冲区,pkt->buf
为NULL
,所以不会打印引用计数。
3. 为 pkt
分配数据缓冲区并检查引用计数
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
if(pkt->buf)
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));
}
av_new_packet(pkt, MEM_ITEM_SIZE)
为pkt
分配一个大小为MEM_ITEM_SIZE
的数据缓冲区,分配成功后pkt->buf
指向该缓冲区,引用计数初始化为 1。- 若
pkt->buf
不为NULL
,则打印当前pkt
的引用计数。
4. 复制数据到 pkt
的数据缓冲区
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
将 av_packet_test1
指向的数据复制到 pkt->data
所指向的缓冲区中,最多复制 MEM_ITEM_SIZE
个字节,遇到值为 1 的字符时停止复制。
5. 分配 pkt2
并转移引用
pkt2 = av_packet_alloc();
av_packet_move_ref(pkt2, pkt);
av_packet_alloc()
分配一个新的AVPacket
结构体给pkt2
。av_packet_move_ref(pkt2, pkt)
将pkt
的数据引用转移到pkt2
上,之后pkt
不再持有数据引用,其成员被重置,pkt->buf
变为NULL
。
6. 多次引用 pkt
到 pkt2
并检查引用计数
av_packet_ref(pkt, pkt2);
av_packet_ref(pkt, pkt2);
if(pkt->buf)
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));
}
if(pkt2->buf)
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));
}
- 两次调用
av_packet_ref(pkt, pkt2)
使pkt
和pkt2
共享数据缓冲区,数据缓冲区的引用计数变为 3(初始 1 次 + 两次引用各加 1)。 - 若
pkt->buf
和pkt2->buf
不为NULL
,则分别打印它们的引用计数。
7. 两次调用 av_packet_unref(pkt)
并检查 pkt->buf
av_packet_unref(pkt);
av_packet_unref(pkt);
if(pkt->buf)printf("pkt->buf没有被置NULL\n");
elseprintf("pkt->buf已经被置NULL\n");
- 第一次调用
av_packet_unref(pkt)
时,引用计数减 1 变为 2。 - 第二次调用
av_packet_unref(pkt)
时,由于第一次调用后pkt->buf
已经被置为NULL
(当引用计数降为 0 时会发生),所以这次调用不会有实际效果。 - 根据
pkt->buf
是否为NULL
打印相应信息。
8. 检查 pkt2
的引用计数并调用 av_packet_unref(pkt2)
if(pkt2->buf)
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));
}
av_packet_unref(pkt2);
- 若
pkt2->buf
不为NULL
,则打印pkt2
的引用计数,此时为 2。 - 调用
av_packet_unref(pkt2)
使引用计数减 1 变为 1。
9. 释放 pkt
和 pkt2
av_packet_free(&pkt);
av_packet_free(&pkt2);
使用 av_packet_free
分别释放 pkt
和 pkt2
所指向的 AVPacket
结构体。
存在的问题
代码存在内存泄漏问题。由于第二次调用 av_packet_unref(pkt)
无效,且只调用了一次 av_packet_unref(pkt2)
,数据缓冲区的引用计数最终仍为 1,没有降为 0,导致数据缓冲区的内存无法被释放。
测试6
void av_packet_test6()
{AVPacket *pkt = NULL;AVPacket *pkt2 = NULL;int ret = 0;pkt = av_packet_alloc();ret = av_new_packet(pkt, MEM_ITEM_SIZE);memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);pkt2 = av_packet_alloc(); // 必须先alloc*pkt2 = *pkt; // 有点类似 pkt可以重新分配内存if(pkt->buf) // 打印referenc-counted,必须保证传入的是有效指针{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));}av_init_packet(pkt);if(pkt2->buf) // 打印referenc-counted,必须保证传入的是有效指针{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));}av_packet_free(&pkt);av_packet_free(&pkt2);
}
代码功能概述
av_packet_test6
函数主要演示了 FFmpeg 中 AVPacket
的内存分配、数据复制、引用计数管理以及释放等操作,同时通过打印引用计数来观察不同操作对引用计数的影响
代码逐行分析
1. 变量声明
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;
声明了两个指向 AVPacket
结构体的指针 pkt
和 pkt2
,并初始化为 NULL
。同时声明了一个整型变量 ret
,用于存储函数调用的返回值。
2. 分配并初始化 pkt
pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
av_packet_alloc()
:分配一个新的AVPacket
结构体,并将其地址赋给pkt
。av_new_packet(pkt, MEM_ITEM_SIZE)
:为pkt
分配一个大小为MEM_ITEM_SIZE
字节的数据缓冲区。如果分配成功,pkt->data
指向新分配的缓冲区,同时数据缓冲区的引用计数初始化为 1。返回值存储在ret
中,若返回值小于 0 则表示分配失败。memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE)
:将av_packet_test1
指向的数据复制到pkt->data
所指向的缓冲区中,最多复制MEM_ITEM_SIZE
个字节,当遇到值为 1 的字符时停止复制。
3. 分配 pkt2
并复制 pkt
的内容
pkt2 = av_packet_alloc(); // 必须先alloc
*pkt2 = *pkt; // 有点类似 pkt可以重新分配内存
av_packet_alloc()
:分配一个新的AVPacket
结构体,并将其地址赋给pkt2
。*pkt2 = *pkt;
:这行代码直接将pkt
的内容复制到pkt2
中,包括pkt
的数据指针data
和AVBufferRef
指针buf
。此时pkt
和pkt2
指向同一个数据缓冲区,且数据缓冲区的引用计数没有增加。
4. 打印 pkt
的引用计数
if(pkt->buf) // 打印referenc-counted,必须保证传入的是有效指针
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));
}
如果 pkt->buf
不为 NULL
,则打印 pkt
关联的数据缓冲区的引用计数,此时引用计数仍为 1。
5. 调用 av_init_packet(pkt)
av_init_packet(pkt);
av_init_packet(pkt)
会将 pkt
的成员重置为默认值,其中包括将 pkt->buf
置为 NULL
,这一步不影响引用计数
6. 打印 pkt2
的引用计数
if(pkt2->buf) // 打印referenc-counted,必须保证传入的是有效指针
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));
}
如果 pkt2->buf
不为 NULL
,则打印 pkt2
关联的数据缓冲区的引用计数,此时引用计数仍为 1。
7. 释放 pkt
和 pkt2
av_packet_free(&pkt);
av_packet_free(&pkt2);
av_packet_free(&pkt)
:由于之前av_init_packet(pkt)
已经将pkt->buf
置为NULL
,av_packet_free
只会释放pkt
结构体本身的内存,不会对数据缓冲区的引用计数产生影响。av_packet_free(&pkt2)
:释放pkt2
所指向的AVPacket
结构体及其关联的数据缓冲区。因为pkt2
持有数据缓冲区的引用,调用av_packet_free
会先调用av_packet_unref
减少数据缓冲区的引用计数,因此这里的引用计数变成了0
,会释放数据缓冲区的内存,然后释放AVPacket
结构体本身的内存。
测试7
void av_frame_test1()
{AVFrame *frame = NULL;int ret = 0;frame = av_frame_alloc();// 没有类似的AVPacket的av_new_packet的API// 1024 *2 * (16/8) =frame->nb_samples = 1024;frame->format = AV_SAMPLE_FMT_S16;//AV_SAMPLE_FMT_S16P AV_SAMPLE_FMT_S16frame->channel_layout = AV_CH_LAYOUT_STEREO; //AV_CH_LAYOUT_MONO AV_CH_LAYOUT_STEREOret = av_frame_get_buffer(frame, 0); // 根据格式分配内存if(frame->buf && frame->buf[0])printf("%s(%d) 1 frame->buf[0]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[0]->size); //受frame->format等参数影响if(frame->buf && frame->buf[1])printf("%s(%d) 1 frame->buf[1]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[1]->size); //受frame->format等参数影响if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针printf("%s(%d) ref_count1(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));ret = av_frame_make_writable(frame); // 当frame本身为空时不能make writableprintf("av_frame_make_writable ret = %d\n", ret);if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针printf("%s(%d) ref_count2(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));av_frame_unref(frame);if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针printf("%s(%d) ref_count3(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));av_frame_free(&frame);
}
1. 变量声明
AVFrame *frame = NULL;
int ret = 0;
声明一个指向 AVFrame
结构体的指针 frame
并初始化为 NULL
,同时声明一个整型变量 ret
用于存储函数调用的返回值。
2. 分配 AVFrame
结构体
frame = av_frame_alloc();// 没有类似的AVPacket的av_new_packet的API
调用 av_frame_alloc
函数在堆上分配一个新的 AVFrame
结构体,并将其地址赋给 frame
。与 AVPacket
的 av_new_packet
不同,av_frame_alloc
仅分配 AVFrame
结构体本身,不分配实际的数据缓冲区。
3. 设置 AVFrame
的参数
// 1024 *2 * (16/8) =
frame->nb_samples = 1024;
frame->format = AV_SAMPLE_FMT_S16;//AV_SAMPLE_FMT_S16P AV_SAMPLE_FMT_S16
frame->channel_layout = AV_CH_LAYOUT_STEREO; //AV_CH_LAYOUT_MONO AV_CH_LAYOUT_STEREO
frame->nb_samples
:设置音频帧中的样本数量为 1024。frame->format
:设置音频样本的格式为AV_SAMPLE_FMT_S16
,即 16 位有符号整数。frame->channel_layout
:设置音频的声道布局为立体声(AV_CH_LAYOUT_STEREO
)。
4. 为 AVFrame
分配数据缓冲区
ret = av_frame_get_buffer(frame, 0); // 根据格式分配内存
调用 av_frame_get_buffer
函数根据 frame
中设置的参数(如 format
、channel_layout
和 nb_samples
)为 AVFrame
分配数据缓冲区。如果分配成功,frame->buf
数组将指向分配的缓冲区。
5. 打印缓冲区大小
if(frame->buf && frame->buf[0])printf("%s(%d) 1 frame->buf[0]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[0]->size); //受frame->format等参数影响
if(frame->buf && frame->buf[1])printf("%s(%d) 1 frame->buf[1]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[1]->size); //受frame->format等参数影响
如果 frame->buf[0]
或 frame->buf[1]
不为 NULL
,则打印它们的大小。缓冲区大小受 frame->format
等参数的影响。
因为AV_SAMPLE_FMT_S16
格式只有一个通道,因此只有buf[0]
有数据;如果是AV_SAMPLE_FMT_S16P
有2个通道,即buf[0]
和buf[1]
都有效
6. 打印初始引用计数
if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针printf("%s(%d) ref_count1(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));
如果 frame->buf[0]
不为 NULL
,则打印其引用计数。初始时,引用计数通常为 1。
7. 使 AVFrame
可写
ret = av_frame_make_writable(frame); // 当frame本身为空时不能make writable
printf("av_frame_make_writable ret = %d\n", ret);
调用 av_frame_make_writable
函数确保 frame
是可写的。如果 frame
已经是可写的,则直接返回;否则,会复制一份数据并使 frame
指向新的可写缓冲区,并且使引用计数减一。打印该函数的返回值,返回值为 0 表示成功。
8. 打印可写操作后的引用计数
if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针printf("%s(%d) ref_count2(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));
如果 frame->buf[0]
不为 NULL
,则打印可写操作后的引用计数。如果发生了数据复制,引用计数可能会改变。
9. 解除 AVFrame
的引用
av_frame_unref(frame);
调用 av_frame_unref
函数解除 frame
对其数据缓冲区的引用,减少数据缓冲区的引用计数。如果引用计数降为 0,则释放数据缓冲区。
10. 打印解除引用后的引用计数
if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针printf("%s(%d) ref_count3(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));
如果 frame->buf[0]
不为 NULL
,则打印解除引用后的引用计数。通常情况下,引用计数应该为 0。
11. 释放 AVFrame
结构体
av_frame_free(&frame);
调用 av_frame_free
函数释放 frame
所指向的 AVFrame
结构体。
更多资料:https://github.com/0voice
相关文章:
【音视频】FFmpeg内存模型
FFmpeg内存模型 从现有的Packet拷贝一个新Packet的时候,有两种情况: 两个Packet的buf引用的是同一数据缓存空间,这时候要注意数据缓存空间的释放问题;两个Packet的buf引用不同的数据缓存空间,每个Packet都有数据缓存…...
基于nlohmann/json 实现 从C++对象转换成JSON数据格式
C对象的JSON序列化与反序列化 基于JsonCpp库实现C对象序列化与反序列化 JSON 介绍 JSON作为一种轻量级的数据交换格式,在Web服务和应用程序中广泛使用。 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读…...
在线视频转 AVI 的便捷之选,便捷操作,无需下载软件,在线使用
在视频处理的众多需求中,将视频转换为 AVI 格式是不少用户的刚需。小白工具网(https://www.xiaobaitool.net/videos/convert-to-avi/ )的在线视频转 AVI 功能,以其显著优势,多格式支持、便捷操作、数据安全保障以及广泛…...
【MCP Node.js SDK 全栈进阶指南】初级篇(3):MCP资源开发基础
引言 在前两篇文章中,我们已经详细介绍了MCP开发环境的搭建以及基础服务器开发。本文作为MCP TypeScript-SDK系列的第三篇,将聚焦于MCP资源开发基础,包括静态资源与动态资源的开发、资源模板设计与参数提取、资源列表与发现机制,以及常见资源类型与最佳实践。通过本文的学…...
L2-1、打造稳定可控的 AI 输出 —— Prompt 模板与格式控制
一、为什么需要 Prompt 模板? 在与 AI 模型交互时,我们经常会遇到输出不稳定、格式混乱的问题。Prompt 模板帮助我们解决这些问题,通过结构化的输入指令来获得可预测且一致的输出结果。 模板的作用主要体现在: 固定输出格式&am…...
Java集成Zxing和OpenCV实现二维码生成与识别工具类
Java集成Zxing和OpenCV实现二维码生成与识别工具类 本文将介绍如何使用Java集成Zxing和OpenCV库,实现二维码的生成和识别功能。识别方法支持多种输入形式,包括File对象、文件路径和Base64编码。 一、环境准备 添加Maven依赖 <dependencies><…...
jenkins pipeline ssh协议报错处理
一、jenkins版本 jenkins:2.492.3 openssh:OpenSSH_9.8p1, OpenSSL 3.3.1 # grep jenkins /etc/passwd jenkins:x:996:994:Jenkins Automation Server:/var/lib/jenkins:/bin/false 二、报错 三、处理 步骤1:手动添加目标主机密钥到Jenk…...
当OCR遇上“幻觉”:如何让AI更靠谱地“看懂”文字?
在数字化的世界里,OCR(光学字符识别)技术就像给机器装上了“电子眼”。但当这项技术遇上大语言模型,一个意想不到的问题出现了——AI竟然会像人类一样产生“幻觉”。想象一下,当你拿着模糊的财务报表扫描件时ÿ…...
vue watch监听路由,第一次进入不触发解决办法
“第一次进入的时候没触发,第二次就触发了”非常典型,它印证了路由监听(无论是 watch $route 还是 beforeRouteUpdate)主要是为了监听变化,而不是处理首次加载时的初始状态。 当你通过 this.$router.push 导航到一个新…...
JVM考古现场(二十四):逆熵者·时间晶体的永恒之战
"警告!时间晶体正在吞噬GC日志!" 我腰间的太极八卦镜突然迸发出刺目的量子辉光,终南山之巅的星宿大阵浮现出诡异的四维克莱因瓶拓扑——这是逆熵者文明穿越时空的拜帖! 楔子:时间晶体的觉醒 🕯️…...
spring中使用netty-socketio部署到服务器(SSL、nginx转发)
spring中使用netty-socketio部署到服务器(SSL、nginx转发) 本文实现前端socket.io-client连接后端netty-socketio,并且部署到服务器上的示例,以及说明一些实现过程中可能遇到的错误。 socketio默认基于的路径是/socket.io 传输…...
qt.tlsbackend.ossl: Failed to load libssl/libcrypto.
我的环境是windows,QT6.3.2(msvc2019_64/mingw_64) 出错原因 QT没有正确加载OpenSSL。 解决过程 1、确保安装的有openssl。 文章结尾有个注意,是其他方式安装过openssl,环境变量有,但是QT找不到的问题。…...
【Python爬虫基础篇】--3.cookie和session
目录 1.cookie 1.1.定义 1.2.参数 1.3.分类 2.session 3.使用cookie登录微博 4.使用session登录 1.cookie 由于http是一个无状态的协议,请求与请求之间无法相互传递或者记录一些信息,cookie和session正是为了解决这个问题而产生。 例子࿱…...
uView的u-modal不显示问题
问题分析:在项目中,其他页面显示正常,在这个页面显示不正常。 问题解决: 一般的原因,诸如层级遮挡控制器true后,被其他逻辑又改为了false最可恨的一个原因 :showshow被编辑器的提示功能误写成了v-modal&qu…...
联易融科技:以科技赋能驱动经营反转与价值重估
行业去重周期下,轻量化发展成破局关键。当前,供应链金融行业正经历从"规模扩张"到"价值深耕"的转型期,降本增效、轻资产运营成为行业共识。联易融公告表示,截至2024年末,公司现金储备高达51亿元,显示出财务状况健康良好,流动资金持续充裕。 董…...
Office文档图片批量提取工具
Office.Files.Images 是一款专注于从 Word、Excel、PPT 等 Office 文档中批量提取图片的轻量级工具,支持 .docx、.xlsx、.pptx 格式文件。该软件体积仅 343KB,无需安装即可运行,通过拖拽操作实现快速解析与导出,尤其适合需批量…...
Python 设计模式:回调模式
1. 什么是回调函数? 回调函数是指作为参数传递给另一个函数的函数。当这个函数执行到某个特定的点时,它会调用这个回调函数。回调函数通常用于处理异步操作、事件处理或在某些条件下执行特定的操作。 回调函数的特点: 作为参数传递&#x…...
DCDC芯片,boost升压电路设计,MT3608 芯片深度解析:从架构到设计的全维度技术手册
一、硬件架构解析:电流模式升压 converter 的核心设计 (一)电路拓扑与核心组件 MT3608 采用恒定频率峰值电流模式升压(Boost)转换器架构,核心由以下模块构成: 集成功率 MOSFET 内置 80mΩ 导通电阻的 N 沟道 MOSFET,漏极(Drain)对应引脚 SW,源极(Source)内部接…...
大数据学习(112)-HIVE中的窗口函数
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一…...
Hive学习
一、Hive 核心原理 1. Hive 架构与执行流程 Hive 是基于 Hadoop 的数据仓库工具,将 SQL 转化为分布式计算任务(MapReduce/Tez/Spark),核心组件如下: 元数据存储(Metastore):存储表…...
前端开发核心知识详解:Vue2、JavaScript 与 CSS
一、Vue2 核心知识点 1. Vue2 的双向绑定原理 Vue2 实现双向绑定主要依赖数据劫持与发布 - 订阅者模式。 利用Object.defineProperty方法对数据对象的属性进行劫持,为每个属性定义getter和setter。getter用于收集依赖,当视图中使用到该属性时…...
仅追加KV数据库
仅追加KV数据库 6.1 我们将要做什么 在本章中,我们将创建一个基于文件的键值存储(KV Store),其核心是一个写时复制(Copy-on-Write, CoW)B 树。这种设计的目标是实现数据的持久性和原子性。 1. 设计概述 …...
【Java面试笔记:基础】8.对比Vector、ArrayList、LinkedList有何区别?
在Java中,Vector、ArrayList和LinkedList均实现了List接口,但它们在线程安全、数据结构、性能特性及应用场景上存在显著差异。 1. Vector、ArrayList 和 LinkedList 的区别 Vector: 线程安全:Vector 是线程安全的动态数组&#…...
Git分支管理方案
成都众望智慧有限公司Git分支管理方案 采用 轻量级Git Flow 敏捷版本控制策略,在保证稳定性的同时提升开发效率。以下是优化后的方案: 1. 精简分支模型(相比6-8人团队减少分支层级) 分支类型作用生命周期devops生产环境代码&am…...
SQL Tuning Advisor
什么是SQL Tuning Advisor STA可以用来优化那些已经被发现的高负载SQL. 默认情况下, Oracle数据库在自动维护窗口中自动认证那些有问题的SQL并且执行优化建议,找寻提升高负载SQL执行计划性能的方法. ** 如何查看自动优化维护窗口产生的报告? ** SQL> set ser…...
联易融出席深圳链主企业供应链金融座谈会,加速对接票交所系统
近日,深圳市委金融办组织召开全市链主企业供应链金融高质量发展座谈会。联易融作为供应链金融企业代表,与虾皮信息科技、电子元器件和集成电路国际交易中心等代表性机构以及行业协会、金融机构参加了会议。 发展供应链金融是破解中小微企业融资难、融资…...
【前端记事】关于electron的入门使用
electron入门使用 背景how to start第一步 创建一个vite-vue3项目第二步 装各种依赖第三步 配置vite.config.jspackage.jsonelectron入口 启动重写关闭、隐藏、最大化最小化 背景 最近对electron比较感兴趣,折腾一段时间后有了点眉目,记录一下 how to …...
Qt绘制可选择范围的日历
【日历控件设计】 #include <QApplication> #include <QWidget> #include <QVBoxLayout> #include <QCalendarWidget> #include <QHBoxLayout> #include <QSpinBox> #include <QPushButton> #include <QLabel> #include <Q…...
Pycharm(十五)面向对象程序设计基础
目录 一、定义类及使用类的成员 二、self关键字介绍 三、在类内部调用类中的函数 class 类名: 属性(类似于定义变量) 行为(类似于定义函数,只不过第一个形参要写self) 一、面向对象基本概述 属性&…...
【C++游戏引擎开发】第21篇:基于物理渲染(PBR)——统计学解构材质与光影
引言 宏观现象:人眼观察到的材质表面特性(如金属的高光锐利、石膏的漫反射柔和),本质上是微观结构对光线的统计平均结果。 微观真相:任何看似平整的表面在放大后都呈现崎岖的微观几何。每个微表面(Microfacet)均为完美镜面,但大量微表面以不同朝向分布时,宏观上会表…...
flutter_slidable 插件使用
简介 flutter_slidable 是一个用于创建可滑动列表项的 Flutter 插件,它允许用户通过滑动来显示隐藏的操作按钮,比如删除、分享等功能。 安装 在 pubspec.yaml 中添加依赖(并运行 flutter pub get): dependencies:fl…...
[论文阅读]ConfusedPilot: Confused Deputy Risks in RAG-based LLMs
ConfusedPilot: Confused Deputy Risks in RAG-based LLMs [2408.04870] ConfusedPilot: Confused Deputy Risks in RAG-based LLMs DEFCON AI Village 2024 文章是针对Copilot这样一个RAG服务提供平台的攻击 在企业环境中整合人工智能工具(如 RAG)会…...
诠视科技MR眼镜如何使用头瞄点和UGUI交互
诠视科技MR眼镜如何使用头瞄点和UGUI交互 要实现头瞄点计算单元确认键操作UGUI,最快捷的方式,右键直接添加XvHeadGazeInputController。 添加以后会自动生成XvHeadGazeInputController到Head节点下面去。 重要的几个参数讲解: scaleFactor:…...
数据赋能(204)——原则与原理——原理方法
原理更多地关注事物本身的客观规律,而原则侧重于指导人们的行为和决策。原则与原理是两个常常被提及,但有所区别的概念。原则和原理在各个领域中都发挥着重要的作用。 原理概念 原理,则通常指的是自然科学和社会科学中具有普遍意义的基本规…...
代码随想录算法训练营第五十六天 | 108.冗余连接 109.冗余连接II
108.冗余连接 题目链接:108. 冗余的边 文章讲解:代码随想录 思路: 题目说是无向图,返回一条可以删去的边,使得结果图是一个有着N个节点的树,如果有多个答案,则返回二维数组中最后出现的边。 …...
Git入门
一、Git 基础概念 1. 版本控制系统分类 本地版本控制:如RCS,仅在本机保存历史版本集中式版本控制:如SVN,单一中央服务器管理代码分布式版本控制:如Git,每个开发者都有完整的仓库副本 2. Git 核心概念 概…...
5G + 物联网:智能世界的催化剂,如何用Python打造下一代IoT应用?
5G 物联网:智能世界的催化剂,如何用Python打造下一代IoT应用? 在数字化时代,物联网(IoT) 已成为智能产业的关键技术。从智能家居到智慧城市,再到工业4.0,我们的世界正在变得越来越…...
从单点突破到链式攻击:XSS 的渗透全路径解析
在网络安全领域,跨站脚本攻击(Cross-Site Scripting,简称 XSS)早已不是新鲜话题。然而,随着网络技术的迭代与应用场景的复杂化,攻击者不再满足于单一的 XSS 漏洞利用,而是将 XSS 与其他安全漏洞…...
spark和hadoop的对比和联系
一、Apache Hadoop 简介 Hadoop是一个由Apache基金会开发的开源分布式计算平台。它主要由Hadoop分布式文件系统(HDFS)和MapReduce计算框架组成。HDFS是为大规模数据存储而设计的,它将文件分割成多个数据块(block)&…...
【Vue3 / TypeScript】 项目兼容低版本浏览器的全面指南
在当今前端开发领域,Vue3 和 TypeScript 已成为主流技术栈。然而,随着 JavaScript 语言的快速演进,许多现代特性在低版本浏览器中无法运行。本文将详细介绍如何使 Vue3 TypeScript 项目完美兼容 IE11 等低版本浏览器。 一、理解兼容性挑战 …...
从零开始搭建你的个人博客:使用 GitHub Pages 免费部署静态网站
🌐 从零开始搭建你的个人博客:使用 GitHub Pages 免费部署静态网站 在互联网时代,拥有一个属于自己的网站不仅是一种展示方式,更是一种技术能力的体现。今天我们将一步步学习如何通过 GitHub Pages 搭建一个免费的个人博客或简历…...
java 设计模式 原型模式
简介 原型模式(Prototype Pattern) 是一种创建型设计模式,它通过复制现有对象来生成新对象,而不是通过 new 关键字创建。核心思想是减少对象创建的开销,尤其是当对象初始化过程复杂或代价较高时。 原型模式的核心实现…...
分别配置Github,Gitee的SSH链接
文章目录 前言一、为第二个账号生成新的密钥对二、 配置 SSH config 文件1.引入库使用 Host 别名进行 clone/push/pull注意扩展 前言 之前已经在电脑配置过Github一个仓库ssh链接,今天想配一个Gitee仓库的ssh链接。运行 ssh-keygen -t rsa提示已经存在,…...
从零开始搭建Django博客②--Django的服务器内容搭建
本文主要在Ubuntu环境上搭建,为便于研究理解,采用SSH连接在虚拟机里的ubuntu-24.04.2-desktop系统搭建,当涉及一些文件操作部分便于通过桌面化进行理解,通过Nginx代理绑定域名,对外发布。 此为从零开始搭建Django博客…...
如何用python脚本读取本地excel表格Workbook.xlsx将里面B2:B8内容,发给本地ollama大模型改写内容后写入对应C2:C8?
环境: python3.10 Win10专业版 ollama 火山引擎 影刀 问题描述: 如何用python脚本读取本地excel表格Workbook.xlsx将里面B2:B8内容,发给本地ollama大模型改写内容后写入对应C2:C8? 解决方案: 1.制作一个python脚本如下: import openpyxl import requests import…...
webpack详细打包配置,包含性能优化、资源处理...
以下是一个详细的 Webpack 5 配置示例,包含常见资源处理和性能优化方案: const path require(path); const webpack require(webpack); const { BundleAnalyzerPlugin } require(webpack-bundle-analyzer); const TerserPlugin require(terser-webp…...
MYSQL的binlog
用于备份恢复和主从复制 binlog 有 3 种格式类型,分别是 STATEMENT(默认格式)、ROW、 MIXED: STATEMENT:每一条修改数据的 SQL 都会被记录到 binlog 中(相当于记录了逻辑操作,所以针对这种格式…...
Saliency Driven Perceptual Image Compression阅读
2021 WACV 创新点 常用的评估指标如MS-SSIM和PSNR不足以判断压缩技术的性能,它们与人类对相似性的感知不一致(2和3的MS-SSIM更高,但文字反而没那么清晰)。 考虑显著区域的压缩(a)将更多的比特分配给显著区域(b&#…...
【C++ 类和数据抽象】构造函数
目录 一、构造函数的基本概念 1.1 构造函数核心特性 1.2 构造函数的作用 1.3 构造函数类型体系 二、构造函数的类型 2.1 默认构造函数 2.2 带参数的构造函数 2.3 拷贝构造函数 2.4 移动构造函数(C11 及以后) 三、初始化关键技术 3.1 成员初始…...
kotlin的kmp编程中遇到Unresolved reference ‘java‘问题
解决办法 打开 File → Project Structure → Project 确保 Project SDK 是 与你的 jvmToolchain 保持一致 如果没有,点击右上角 Add SDK 添加 JDK 路径 同步Sync 然后就正常了。 package org.example.projectimport androidx.compose.animation.AnimatedVi…...