FFmpeg 硬核指南:从底层架构到播放器全链路开发实战 基础
目录
- 1.ffmpeg的基本组成
- 2.播放器的API
- 2.1 复用器阶段
- 2.1.1 分配解复用上下文
- 2.1.2 文件信息操作
- 2.1.3 综合示例
- 2. 2 编解码部分
- 2.2.1 分配解码器上下文
- 2.2.2编解码操作
- 2.2.3 综合示例
- 3 ffmpeg 内存模型
- 3.1 基本概念
- 3.2API
1.ffmpeg的基本组成
| 模块名称 | 功能描述 | 主要用途 |
|---|---|---|
| AVFormat | 实现媒体封装格式的处理,支持多种音视频容器格式(如 MP4、AVI、MKV 等)。 | - 读取和解析音视频文件的容器格式 - 封装和复用音视频流 - 支持流媒体协议(如 RTMP、HTTP) |
| AVCodec | 提供音视频编解码器,支持多种编解码格式(如 H.264、AAC、MP3 等)。 | - 编码音视频数据 - 解码音视频数据 - 支持硬件加速编解码(如 NVENC、VAAPI) |
| AVFilter | 提供音视频滤镜处理框架,用于对音视频数据进行处理和转换。 | - 视频滤镜(如裁剪、旋转、添加水印) - 音频滤镜(如调整音量、混音) - 创建复杂的滤镜链 |
| AVDevice | 提供对音视频设备的访问接口,支持摄像头、麦克风、显示器等设备。 | - 捕获音视频数据(如从摄像头或麦克风) - 输出音视频数据(如到显示器或扬声器) - 列举和控制设备 |
| AVUtil | 提供通用工具函数,支持数学运算、内存管理、数据结构等。 | - 提供辅助函数(如哈希计算、时间戳处理) - 支持像素格式和音频样本格式的转换 - 提供错误处理和日志功能 |
| swscale | 用于视频图像的缩放和像素格式转换。 | - 将图像从一种分辨率缩放到另一种分辨率 - 将像素格式从一种转换为另一种(如 YUV 到 RGB) |
| swresample | 用于音频的重采样、声道转换和音频格式转换。 | - 将音频从一种采样率转换为另一种采样率 - 调整声道数(如单声道到立体声) - 转换音频格式 |
| 说明 |
- AVFormat:负责处理音视频文件的容器格式,支持多种格式的读取和写入。
- AVCodec:提供编解码器,支持多种音视频编解码格式。
- AVFilter:提供音视频滤镜框架,用于处理音视频数据。
- AVDevice:提供对音视频设备的访问接口,支持设备输入和输出。
- AVUtil:提供通用工具函数,支持多种辅助功能。
- swscale:专门用于视频图像的缩放和像素格式转换。
- swresample:专门用于音频的重采样和格式转换。
2.播放器的API
概要

2.1 复用器阶段
2.1.1 分配解复用上下文
avformat_alloc_context()
功能作用
- 内存分配:在堆上为 AVFormatContext 结构体分配内存空间。AVFormatContext 是 FFmpeg 里极为关键的结构体,存储着音视频文件的格式信息(如封装格式是 MP4、FLV 等 )、输入输出相关参数、音视频流的相关信息(如流的数量、每个流的编码参数等 )。
- 内部初始化:完成 AVFormatContext 内部使用对象 AVFormatInternal 结构体的空间分配及其部分成员字段的赋值。
具体操作
-
函数原型:
AVFormatContext*avformat_alloc_context(void); -
示例
#include <libavformat/avformat.h>
// 通常还需包含其他相关头文件,如内存管理等
#include <libavutil/mem.h> AVFormatContext *formatContext = avformat_alloc_context();
if (formatContext == NULL) {// 分配失败处理,比如打印错误日志、返回错误码等// 可使用av_log等函数打印FFmpeg相关错误信息av_log(NULL, AV_LOG_ERROR, "Failed to allocate AVFormatContext\n"); // 后续可根据实际情况进行更详细处理,如返回错误码给调用方return -1;
}
avformat_free_context(formatContext);
avformat_open_input()
功能作用
- 探测文件格式
自动探测:若 fmt 参数为 NULL,函数会通过文件扩展名、文件头部的特征字节以及其他一些启发式方法来自动识别文件的封装格式,如 MP4、AVI、FLV、MKV 等。例如,对于扩展名为 .mp4 的文件,它会尝试使用 MP4 对应的输入格式解析器来处理。
指定格式:若已知文件格式,可通过 fmt 参数指定具体的 AVInputFormat,强制使用该格式进行解析,避免自动探测可能出现的错误。
- 初始化 AVFormatContext
该函数会初始化传入的 AVFormatContext 结构体,将输入文件或流的相关信息填充到其中。
分配内存:若传入的 AVFormatContext 指针为空,函数会在内部为其分配内存空间。
填充基本信息:包括文件的元数据(如标题、作者、时长等)、流的数量、每个流的基本信息(如流类型、编码格式等)。例如,对于一个包含视频流和音频流的 MP4 文件,会在 AVFormatContext 中记录这两个流的相关信息。
- 打开输入流
本地文件:打开本地磁盘上的音视频文件,为后续读取文件内容做准备。
网络流:建立网络连接,从远程服务器获取音视频数据。例如,当 url 为 RTSP 地址时,会与 RTSP 服务器建立连接并开始接收数据。
设备输入:如果支持设备输入,会打开相应的设备并开始采集音视频数据。
具体操作
- 函数原型:
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
- AVFormatContext **ps :指向 AVFormatContext 指针的指针。AVFormatContext 用于存储打开的媒体文件的上下文信息,如文件格式、流信息等。
若传入的指针为空(即 ps == NULL ),函数会内部自动分配一个 AVFormatContext 结构体,并将指针存入 ps 指向的位置。
若传入的是已分配好的 AVFormatContext 指针(即 ps 指向一个有效的 AVFormatContext 指针 ),若函数执行失败,会释放这个传入的 AVFormatContext 结构体。- const char *url :要打开的媒体文件的 URL。既可以是本地文件路径(如 “C:/videos/test.mp4” ),也可以是网络流地址(如 “http://example.com/stream.ts” 、“rtsp://example.com/live” )等。
- AVInputFormat *fmt :AVInputFormat 结构体的指针,用于指定媒体文件的格式。
如果该参数为 NULL ,FFmpeg 会根据文件扩展名或通过探测文件内容自动选择合适的输入格式。
若明确知道文件格式,可传入对应的 AVInputFormat 指针,强制指定格式。比如要打开基于 avfoundation 输入格式的 macOS 音视频设备,可先通过 av_find_input_format(“avfoundation”) 获取指针后传入。- AVDictionary **options :AVDictionary 结构体的指针,用于传递打开媒体文件时的选项。
可以设置诸如分辨率、帧率、采样率、缓冲大小等参数。例如,想设置视频流的最大缓冲大小,可创建一个 AVDictionary 并添加相应键值对后传入。
函数返回时,该参数会被销毁并替换为一个新的 AVDictionary,其中包含未找到或未处理的选项。若不需要设置选项,可传入 NULL 。
2.1.2 文件信息操作
avformat_find_stream_info()
功能
avformat_find_stream_info() 函数的主要功能是读取输入流的数据包,以获取每个流的详细信息。当使用 avformat_open_input() 打开输入文件后,虽然已经获取了文件的基本格式信息,但对于每个流的详细参数(如视频的帧率、分辨率,音频的采样率、声道数等)可能还不完整。该函数会通过读取一定数量的数据包,对这些流信息进行分析和填充,从而为后续的解码和处理提供准确的参数。
原型:int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
AVFormatContext *ic:指向 AVFormatContext 结构体的指针,该结构体包含了输入文件的格式上下文信息。
AVDictionary **options:指向 AVDictionary 指针的指针,用于传递一些额外的选项。如果不需要设置额外选项,可以传入 NULL。
av_read_frame()
- 功能
av_read_frame() 函数用于从输入流中读取一个数据包(AVPacket)。在音视频文件中,数据是以数据包的形式存储和传输的,每个数据包包含了一段连续的编码数据(可能是视频帧、音频帧或其他数据)。该函数会按照文件的顺序依次读取数据包,直到文件结束。
-int av_read_frame(AVFormatContext *s, AVPacket *pkt);
参数:
AVFormatContext *s:指向 AVFormatContext 结构体的指针,该结构体包含了输入文件的格式上下文信息。
AVPacket *pkt:指向 AVPacket 结构体的指针,用于存储读取到的数据包。
avformat_seek_file()
- 功能
avformat_seek_file() 函数用于在输入文件中定位到指定的时间点或位置。在音视频播放、编辑等应用中,经常需要实现快进、快退等功能,该函数可以帮助我们快速定位到指定的位置,然后继续读取数据包。 int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
int stream_index:指定要定位的流的索引。如果为 -1,则表示对所有流进行定位。
int64_t min_ts:允许的最小时间戳。
int64_t ts:要定位到的目标时间戳。
int64_t max_ts:允许的最大时间戳。
int flags:定位的标志,用于指定定位的方式,例如 AVSEEK_FLAG_BACKWARD 表示向后定位,AVSEEK_FLAG_ANY 表示允许定位到非关键帧等。
2.1.3 综合示例
#include <libavformat/avformat.h>
#include <stdio.h>
#include <libavutil/error.h>// 打印错误信息
static void log_error(int err_num) {char errbuf[1024];av_strerror(err_num, errbuf, sizeof(errbuf));fprintf(stderr, "Error: %s\n", errbuf);
}int main(int argc, char* argv[]) {if (argc != 2) {fprintf(stderr, "Usage: %s <media_file_path>\n", argv[0]);return -1;}AVFormatContext* fmt_ctx = NULL;AVPacket pkt;int ret;// 1. 分配解复用器上下文fmt_ctx = avformat_alloc_context();if (!fmt_ctx) {fprintf(stderr, "Failed to allocate AVFormatContext\n");return -1;}// 2. 根据url打开本地文件或网络流ret = avformat_open_input(&fmt_ctx, argv[1], NULL, NULL);if (ret < 0) {log_error(ret);avformat_free_context(fmt_ctx);return -1;}// 3. 读取媒体的部分数据包以获取码流信息ret = avformat_find_stream_info(fmt_ctx, NULL);if (ret < 0) {log_error(ret);avformat_close_input(&fmt_ctx);avformat_free_context(fmt_ctx);return -1;}// 打印媒体文件信息av_dump_format(fmt_ctx, 0, argv[1], 0);// 4. 循环操作(读取数据包或定位文件)int seek_operation = 0; // 标记是否进行定位操作,0表示不进行定位,非0表示进行定位while (1) {if (!seek_operation) {// 4.1 从文件中读取数据包ret = av_read_frame(fmt_ctx, &pkt);if (ret == AVERROR_EOF) {printf("End of file reached.\n");break;} else if (ret < 0) {log_error(ret);break;}// 这里可以添加对数据包的处理逻辑,如判断是音频还是视频包等av_packet_unref(&pkt);} else {// 4.2 定位文件(这里简单示例定位到起始位置,实际应用可按需调整时间戳等参数)int64_t target_ts = 0;ret = avformat_seek_file(fmt_ctx, -1, INT64_MIN, target_ts, INT64_MAX, 0);if (ret < 0) {log_error(ret);break;}seek_operation = 0; // 定位后可继续读取数据包}}// 5. 关闭解复用器avformat_close_input(&fmt_ctx);avformat_free_context(fmt_ctx);return 0;
}
2. 2 编解码部分
2.2.1 分配解码器上下文
avcodec_alloc_context3()
- 功能
avcodec_alloc_context3() 函数用于分配一个编解码器上下文(AVCodecContext)结构体,并根据传入的编解码器(AVCodec)进行初始化。编解码器上下文包含了编解码器所需的所有参数和状态信息,是进行编解码操作的重要基础。 AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
const AVCodec *codec:指向 AVCodec 结构体的指针,表示要分配上下文的编解码器。如果传入 NULL,则分配一个通用的编解码器上下文,后续需要手动设置相关参数。
avcodec_parameters_to_context
- 功能
在使用 FFmpeg 进行音视频处理时,通常在解复用阶段会从输入文件中获取到每个流的编解码器参数(存储在 AVCodecParameters 结构体中),而在后续的解码阶段需要使用编解码器上下文(AVCodecContext)来进行实际的解码操作。avcodec_parameters_to_context 函数的作用就是将解复用阶段得到的编解码器参数复制到编解码器上下文中,从而为后续的解码操作做好准备。
int avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *par);
AVCodecContext *codec:指向目标编解码器上下文的指针。这个上下文将用于后续的编解码操作,函数会将 par 中的参数复制到该上下文中。
const AVCodecParameters *par:指向源编解码器参数的指针。这些参数通常是从解复用器(如 AVFormatContext 中的流信息)中获取的。
avcodec_find_decoder()
- 功能**:函数用于根据给定的编解码器 ID 查找对应的解码器**。FFmpeg 支持多种编解码器,每个编解码器都有一个唯一的 ID,通过这个函数可以找到相应的解码器结构体(AVCodec)。
- AVCodec *avcodec_find_decoder(enum AVCodecID id)
enum AVCodecID id:编解码器的 ID,例如 AV_CODEC_ID_H264 表示 H.264 视频编解码器,AV_CODEC_ID_AAC 表示 AAC 音频编解码器等。
avcodec_find_decoder_by_name()
- 功能
avcodec_find_decoder_by_name() 函数用于根据给定的解码器名称查找对应的解码器。有时候我们可能只知道解码器的名称,而不知道其对应的 ID,使用这个函数可以方便地根据名称找到相应的解码器。 AVCodec *avcodec_find_decoder_by_name(const char *name);
const char *name:解码器的名称,例如 “h264”、“aac” 等。
ID 唯一原因
精准识别:ID 是固定数值,像 AV_CODEC_ID_H264 ,FFmpeg 能靠它在众多编解码器里快速精准定位到特定的编解码器,处理复杂媒体文件时很关键。
兼容对接:在 FFmpeg 底层及和其他多媒体工具交互时,ID 是标准标识,确保不同系统、平台间数据处理的兼容性。
名字不唯一原因
实现多样:同一编码标准会有不同公司或组织开发的编解码器,如 H.264 有 x264 等不同实现,名字各异。
历史与特性:FFmpeg 发展久且生态丰富,新的编解码器加入时,可能因历史原因或为突出功能特性,导致名字重复或类似。
2.2.2编解码操作
avcodec_open2()
- 功能
avcodec_open2() 函数用于打开编解码器,它会初始化编解码器上下文(AVCodecContext)并分配必要的资源,为后续的编解码操作做好准备。 int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
AVCodecContext *avctx:指向编解码器上下文的指针,该上下文包含了编解码器所需的参数和状态信息。
const AVCodec *codec:指向要打开的编解码器的指针,通常通过 avcodec_find_decoder() 或 avcodec_find_encoder() 函数获取。
avcodec_send_packet()
- 功能
avcodec_send_packet() 函数用于将编码后的数据包(AVPacket)发送给解码器进行解码。该函数将数据包送入解码器的输入队列,解码器会在后续的处理中对其进行解码。 int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
AVCodecContext *avctx:指向编解码器上下文的指针,该上下文已经通过 avcodec_open2() 函数打开。
const AVPacket *avpkt:指向要发送的编码数据包的指针。如果传入 NULL,表示刷新解码器,即告诉解码器已经没有更多的输入数据,需要处理剩余的缓冲区数据。
avcodec_receive_frame()
- 功能
avcodec_receive_frame() 函数用于从解码器中接收解码后的帧(AVFrame)。在调用 avcodec_send_packet() 函数发送数据包后,解码器会对其进行解码,并将解码后的帧存储在内部缓冲区中,通过该函数可以从缓冲区中取出解码后的帧进行处理。 int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
AVCodecContext *avctx:指向编解码器上下文的指针,该上下文已经通过 avcodec_open2() 函数打开。
AVFrame *frame:指向用于存储解码后帧的 AVFrame 结构体的指针。
2.2.3 综合示例
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/frame.h>
#include <stdio.h>// 打印错误信息
static void log_error(int err_num) {char errbuf[1024];av_strerror(err_num, errbuf, sizeof(errbuf));fprintf(stderr, "Error: %s\n", errbuf);
}int main(int argc, char* argv[]) {if (argc != 2) {fprintf(stderr, "Usage: %s <input_video_file>\n", argv[0]);return -1;}AVFormatContext* fmt_ctx = NULL;AVCodecContext* codec_ctx = NULL;AVCodec* codec = NULL;AVFrame* frame = NULL;AVPacket pkt;int video_stream_index = -1;int ret;// 初始化网络组件(如果处理网络流需要)avformat_network_init();// 1. 分配解复用器上下文fmt_ctx = avformat_alloc_context();if (!fmt_ctx) {fprintf(stderr, "Failed to allocate AVFormatContext\n");return -1;}// 2. 根据url打开本地文件或网络流ret = avformat_open_input(&fmt_ctx, argv[1], NULL, NULL);if (ret < 0) {log_error(ret);avformat_free_context(fmt_ctx);return -1;}// 3. 读取媒体的部分数据包以获取码流信息ret = avformat_find_stream_info(fmt_ctx, NULL);if (ret < 0) {log_error(ret);avformat_close_input(&fmt_ctx);avformat_free_context(fmt_ctx);return -1;}// 查找视频流for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {video_stream_index = i;break;}}if (video_stream_index == -1) {fprintf(stderr, "Could not find video stream\n");avformat_close_input(&fmt_ctx);avformat_free_context(fmt_ctx);return -1;}// 4. 分配编解码器上下文codec_ctx = avcodec_alloc_context3(NULL);if (!codec_ctx) {fprintf(stderr, "Could not allocate codec context\n");avformat_close_input(&fmt_ctx);avformat_free_context(fmt_ctx);return -1;}// 5. 将码流中的编解码器信息拷贝到AVCodecContextret = avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_stream_index]->codecpar);if (ret < 0) {log_error(ret);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);avformat_free_context(fmt_ctx);return -1;}// 6. 根据编解码器信息查找相应的解码器codec = avcodec_find_decoder(codec_ctx->codec_id);if (!codec) {fprintf(stderr, "Could not find codec\n");avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);avformat_free_context(fmt_ctx);return -1;}// 7. 打开编解码器并关联到AVCodecContextret = avcodec_open2(codec_ctx, codec, NULL);if (ret < 0) {log_error(ret);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);avformat_free_context(fmt_ctx);return -1;}// 分配存储解码后帧的AVFrameframe = av_frame_alloc();if (!frame) {fprintf(stderr, "Could not allocate frame\n");avcodec_close(codec_ctx);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);avformat_free_context(fmt_ctx);return -1;}// 循环进行解码操作while (1) {// 读取数据包ret = av_read_frame(fmt_ctx, &pkt);if (ret == AVERROR(EOF)) {// 发送一个空包,让解码器处理完剩余数据pkt.data = NULL;pkt.size = 0;ret = avcodec_send_packet(codec_ctx, &pkt);if (ret < 0) {log_error(ret);break;}// 接收解码后的帧,直到没有帧输出while (1) {ret = avcodec_receive_frame(codec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;} else if (ret < 0) {log_error(ret);break;}// 这里可以添加对解码后帧的处理逻辑,如保存为图像等printf("Decoded frame: pts = %ld\n", frame->pts);}break;} else if (ret < 0) {log_error(ret);break;}if (pkt.stream_index == video_stream_index) {// 5.1 向解码器发送数据包ret = avcodec_send_packet(codec_ctx, &pkt);if (ret < 0) {log_error(ret);av_packet_unref(&pkt);continue;}// 5.2 接收解码后的帧while (1) {ret = avcodec_receive_frame(codec_ctx, frame);if (ret == AVERROR(EAGAIN)) {break;} else if (ret == AVERROR_EOF) {break;} else if (ret < 0) {log_error(ret);break;}// 这里可以添加对解码后帧的处理逻辑,如保存为图像等printf("Decoded frame: pts = %ld\n", frame->pts);}}av_packet_unref(&pkt);}// 6. 关闭解码器和释放上下文avcodec_close(codec_ctx);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);avformat_free_context(fmt_ctx);av_frame_free(&frame);return 0;
}
3 ffmpeg 内存模型
前言有两个问题
(1 )从av_read_frame读取到一个AVPacket后怎么放入队列?
- 选择队列数据结构:可以使用编程语言自带的队列数据结构,像 C++ 的 std::queue 、Python 的 queue.Queue 等 ;也可以自己基于链表、数组实现一个队列。
- 克隆数据包(可选):因为 AVPacket 可能在后续操作中被修改或释放,为避免影响原数据,通常会先克隆一份。在 FFmpeg 里,使用 av_packet_clone 函数来克隆 AVPacket ,得到一个内容相同但独立的新数据包。
- 入队操作:利用所选队列数据结构对应的入队方法。比如用 C++ 的 std::queue 时,使用 push 方法把克隆后的 AVPacket 指针放入队列;用 Python 的 queue.Queue 时,通过 put 方法将 AVPacket 对象放入队列 。
将 AVFrame 放入队列 同理
3.1 基本概念
有两种克隆模式
①两个Packet的buf引用的是同一数据缓存空间,这候要注意数据缓存空间的释放问题;
②两个Packet的buf引用不同的数据缓存空间,每个Packet都有数据缓存空间的copy;
一般ffmpeg选择的是第一种。
Fmpeg 引用计数原理:AVPacket 和 AVFrame 围绕 AVBuffer 构建引用计数体系。以 AVPacket 为例,分配 AVBuffer 时引用计数初始化为 1 ;新的 AVPacket 引用共享缓存空间,通过 av_packet_ref 函数使引用计数加 1 ;释放引用共享空间的 AVPacket ,调用 av_packet_unref 函数让引用计数减 1 ,引用计数为 0 ,释放 AVBuffer 。AVFrame 原理类似 。

AVBuffer
概念:AVBuffer 是 FFmpeg 中用于存储数据的缓冲区,采用引用计数机制管理内存 。它代表数据缓冲区本身,是内存管理的基石,但一般不直接被调用者操作 。
AVBufferRef
概念:AVBufferRef 是对 AVBuffer 的一层封装,代表指向 AVBuffer 的单个引用 。它直接面向调用者,通过它来操作 AVBuffer,实现对 AVBuffer 的引用计数管理和数据访问控制 。
3.2API
AVPacket常用API
| 函数原型 | 说明 |
|---|---|
AVPacket *av_packet_alloc(void); | 分配 AVPacket,此时和 buffer 无关联 |
void av_packet_free(AVPacket **pkt); | 释放 AVPacket,与 av_packet_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() |
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_S16 一个采样点多少位frame->channel_layout = AV_CH_LAYOUT_STEREO; //AV_CH_LAYOUT_MONO AV_CH_LAYOUT_STEREO//声道ret = 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);
}void av_frame_test()
{av_frame_test1();
}相关文章:
FFmpeg 硬核指南:从底层架构到播放器全链路开发实战 基础
目录 1.ffmpeg的基本组成2.播放器的API2.1 复用器阶段2.1.1 分配解复用上下文2.1.2 文件信息操作2.1.3 综合示例 2. 2 编解码部分2.2.1 分配解码器上下文2.2.2编解码操作2.2.3 综合示例 3 ffmpeg 内存模型3.1 基本概念3.2API 1.ffmpeg的基本组成 模块名称功能描述主要用途AVFo…...
大数据建模与评估
文章目录 实战案例:电商用户分群与价值预测核心工具与库总结一、常见数据挖掘模型原理及应用(一)决策树模型(二)随机森林模型(三)支持向量机(SVM)模型(四)K - Means聚类模型(五)K - Nearest Neighbors(KNN)模型二、运用Python机器学习知识实现数据建模与评估(一…...
UE5有些场景的导航生成失败解决方法
如果导航丢失,就在项目设置下将: 即可解决问题: 看了半个小时的导航生成代码发现,NavDataSet这个数组为空,导致异步构建导航失败。 解决 NavDataSet 空 无法生成如下: 当 NavDataSet 为空的化 如果 bAut…...
MCP(Model Context Protocol 模型上下文协议)科普
MCP(Model Context Protocol,模型上下文协议)是由人工智能公司 Anthropic 于 2024年11月 推出的开放标准协议,旨在为大型语言模型(LLM)与外部数据源、工具及服务提供标准化连接,从而提升AI在实际…...
健康养生指南
在快节奏的现代生活中,健康养生成为人们关注的焦点。它不仅关乎身体的强健,更是提升生活质量、预防疾病的关键。掌握科学的养生方法,能让我们在岁月流转中始终保持活力。 饮食是健康养生的基础。遵循 “均衡膳食” 原则,每日饮食需…...
Linux系统:进程终止的概念与相关接口函数(_exit,exit,atexit)
本节目标 理解进程终止的概念理解退出状态码的概念以及使用方法掌握_exit与exit函数的用法以及区别atexit函数注册终止时执行的函数相关宏 一、进程终止 进程终止(Process Termination)是指操作系统结束一个进程的执行,回收其占用的资源&a…...
Linux下 文件的查找、复制、移动和解压缩
1、在/var/log目录下创建一个hehe.log的文件,其文件内容是: myhostname ghl mydomain localdomain relayhost [smtp.qq.com]:587 smtp_use_tls yes smtp_sasl_auth_enable yes smtp_sasl_security_options noanonymous smtp_sasl_tls_security_opt…...
C语言学习之预处理指令
目录 预定义符号 #define的应用 #define定义常量 #define定义宏 带有副作用的宏参数 宏替换的规则 函数和宏定义的区别 #和## #运算符 ##运算符 命名约定 #undef 编辑 命令行定义 条件编译 头文件包含 头文件被包含的方式 1.本地头文件包含 2.库文件包含 …...
【STM32单片机】#10 USART串口通信
主要参考学习资料: B站江协科技 STM32入门教程-2023版 细致讲解 中文字幕 开发资料下载链接:https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwddspb 单片机套装:STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协 实验&…...
fastlio用mid360录制的bag包离线建图,提示消息类型错误
我用mid360录制的bag包,激光雷达的数据类型是sensor_msgs::PointCloud2,但是运行fast_lio中的mid360 launch文件,会报错(没截图),显示无法从livox_ros_driver2::CustomMsg转换到sensor_msgs::PointCloud2。…...
二级评论列表-Java实现
二级评论列表是很常见的功能,文章记录了新手用Java实现的具体逻辑。 整体实现逻辑是先用2个sql,分别查出两层数据。然后用java在service中实现数据组装,返给前端。这种实现思路好处是SQL简洁,逻辑分明,便于维护。 一…...
IP检测工具“ipjiance”
目录 IP质量检测 应用场景 对网络安全的贡献 对网络管理的帮助 对用户决策的辅助作用 IP质量检测 检测IP的网络提供商:通过ASN(自治系统编号)识别IP地址所属的网络运营商,例如电信、移动、联通等。 识别网络类型࿱…...
mysql的函数(第二期)
九、窗口函数(MySQL 8.0) 适用于对结果集的子集(窗口)进行计算,常用于数据分析场景。 ROW_NUMBER() 作用:为每一行生成唯一的序号。示例:按分数降序排名 SELECT n…...
Replicate Python client
本文翻译整理自:https://github.com/replicate/replicate-python 文章目录 一、关于 Replicate Python 客户端相关链接资源关键功能特性 二、1.0.0 版本的重大变更三、安装与配置1、系统要求2、安装3、认证配置 四、核心功能1、运行模型2、异步IO支持3、流式输出模型…...
halcon模板匹配(八)alignment_for_ocr_in_semiconductor
目录 一、alignment_for_ocr_in_semiconductor例程目的二、创建训练和查找用于图像对齐三、图像对齐四、在指定区域内查找文本一、alignment_for_ocr_in_semiconductor例程目的 在一个图像中定义两个区域,一个用于图像对齐,在另一个区域内使用文本模板进行匹配。 二、创建训…...
Java读取JSON文件并将其中元素转为JSON对象输出
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Java读取JSON文件并将其中元素转为JSON对象输…...
华为openEuler操作系统全解析:起源、特性与生态对比
华为openEuler操作系统全解析:起源、特性与生态对比 一、起源与发展历程 openEuler(欧拉操作系统)是华为于2019年开源的Linux发行版,其前身为华为内部研发的服务器操作系统EulerOS。EulerOS自2010年起逐步发展,支持华…...
Elasticsearch使用及常见的问题
Elasticsearch作为一款分布式搜索与分析引擎,其核心优势在于高性能搜索能力,依托倒排索引和分布式架构,可快速处理海量数据及复杂查询,支持实时索引与动态扩容,兼具高可用性和扩展性。其丰富的RESTful API与查询语言降…...
Python基础总结(七)之条件语句
文章目录 条件语句if一、Python中的真假二、条件语句格式2.1 if语句格式2.2 if-else语句2.3 if-elif-else语句 三、if语句嵌套 条件语句if 条件语句其实就是if语句,在讲解if语句之前需要知道Python中对于真假的判断。 一、Python中的真假 在Python中非0的都为真&…...
命令update-alternatives
❯ which pip /home/ying/anaconda3/bin/pipying192 ~ [2]> which pip /usr/bin/pip使用update-alternatives对他们进行管理和切换 快捷方式 和 实际路径不可以相同 所以我这边选择了/usr/local/bin目录作为介质存储快捷方式,另外该快捷方式会自己创建我们只需选…...
deekseak 本地windows 10 部署步骤
有些场景需要本地部署,例如金融、医疗(HIPAA)、政府(GDPR)、军工等,需完全控制数据存储和访问权限,避免云端合规风险或者偏远地区、船舶、矿井等无法依赖云服务,关键设施(…...
MySQL中常用函数的分类及示例
概述 以下是 MySQL 中常用函数的分类及示例,涵盖字符串处理、数值计算、日期操作、条件判断等常见场景: 一、字符串函数 1. CONCAT(str1, str2, ...) 拼接字符串。 SELECT CONCAT(Hello, , World); -- 输出: Hello World2. SUBSTRING(str, start,…...
<sql>、<resultMap>、<where>、<foreach>、<trim>、<set>等标签的作用和用法
目录 一. sql 代码片段标签 二. resultMap 映射结果集标签 三. where 条件标签 四. set 修改标签 五. trim 标签 六. foreach 循环标签 一. sql 代码片段标签 sql 标签是 mybatis 框架中一个非常常用的标签页,特别是当一张表很有多个字段多,或者要…...
企业级HAProxy高可用离线部署实战(附Kubernetes APIServer负载均衡配置)
企业级HAProxy高可用离线部署实战(附Kubernetes APIServer负载均衡配置) 摘要:本文深入讲解在离线环境下部署HAProxy 3.1.1的全流程,涵盖源码编译、系统服务封装、K8S APIServer四层负载配置等核心环节,并提供生产级高…...
实现Azure Databricks安全地请求企业内部API返回数据
需要编写一个Databricks在Azure云上运行,它需要访问企业内部的API获取JSON格式的数据,企业有网关和防火墙,API有公司的okta身份认证,通过公司的域账号来授权访问,现在需要创建一个专用的域账号,让Databrick…...
kafka认证部署
首先启动 zookeeper /home/kafka/bin/zookeeper-server-start.sh /home/kafka/config/zookeeper.properties 创建SCRAM证书 /home/kafka/bin/kafka-configs.sh --zookeeper localhost:2181 --alter --add-config SCRAM-SHA-256[iterations8192,passwordliebe],SCRAM-SHA-512[p…...
【项目】CherrySudio配置MCP服务器
CherrySudio配置MCP服务器 (一)Cherry Studio介绍(二)MCP服务环境搭建(1)环境准备(2)依赖组件安装<1> Bun和UV安装 (3)MCP服务器使用<1> 搜索MCP…...
【LeetCode 热题 100】双指针 系列
📁283. 移动零 对于该题目,需要注意的是两个地方,一是保持非零元素的相对顺序,以及O(1)的空间复杂度。 采用双指针的思路,将数组划分成3个区间,。 [0 , left]:该区间内元素全是非零元素。 [left1 , right…...
【技术派后端篇】 Redis 实现用户活跃度排行榜
在各类互联网应用中,排行榜是一个常见的功能需求,它能够直观地展示用户的表现或贡献情况,提升用户的参与感和竞争意识。在技术派项目中,也引入了用户活跃度排行榜,该排行榜主要基于 Redis 的 ZSET 数据结构来实现。接下…...
模拟算法(一)作业分析及答案
目录 作业1:角谷猜想 解题思路 : 代码实现: 作业2:校门外的树 解题思路 注意事项 代码实现 作业3:乒乓球 编辑 问题重述 解题思路: 作业1:角谷猜想 【描述】 所谓角谷猜想…...
