当前位置: 首页 > news >正文

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);AVPacketbuf 分配内存,引用计数初始化为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();
}

相关文章:

  • 字符串系列一>最长回文子串
  • ValueError: model.embed_tokens.weight doesn‘t have any device set
  • 6TOPS算力NPU加持!RK3588如何重塑8K显示的边缘计算新边界
  • 深入浅出 Multi-Head Attention:原理 + 例子 + PyTorch 实现
  • 研0大模型学习(第四、五天)
  • 武林秘籍之INSERT篇:一键插入,笑傲数据库
  • 数据分析处理库Pandas常用方法汇总
  • 极狐GitLab 项目和群组的导入导出速率限制如何设置?
  • 论文阅读--Orient Anything
  • spring注解@Transactional会回滚哪些异常
  • 供应链项目技术实现方案,供应链详细设计方案书,采购管理,财务管理(Word原件)
  • [Vue3]动态引入图片
  • L2-002 链表去重
  • MATLAB 控制系统设计与仿真 - 36
  • 使用 PySpark 批量清理 Hive 表历史分区
  • 在Qt中验证LDAP账户(Windows平台)
  • 【dataframe显示不全问题】打开一个行列超多的excel转成df之后行列显示不全
  • Android tinyalsa库函数剖析
  • 几款开源C#插件框架
  • 2025年山东燃气瓶装送气工考试真题练习
  • 河南社旗县委书记张荣印转任南阳市人大常委会农工委主任
  • 马上评丨“化学麻将”创新值得点赞,但要慎言推广
  • 擘画开放新篇 共筑合作之桥——中国银行上海市分行全力护航第八届进博会筹备工作
  • 特朗普:“百分之百”相信能与欧盟达成贸易协议
  • 特朗普叫停已许可的海上风电,机构将美国风电前景下调40%
  • 女子斥“老法师”涉嫌偷拍?街头摄影的边界应该怎么定?