FFmpeg 硬核指南:从底层架构到播放器全链路开发实战 基础

news2025/4/20 7:21:56

目录

  • 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. 将码流中的编解码器信息拷贝到AVCodecContext
    ret = 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. 打开编解码器并关联到AVCodecContext
    ret = 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;
    }

    // 分配存储解码后帧的AVFrame
    frame = 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 writable
    printf("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();
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UE5有些场景的导航生成失败解决方法

如果导航丢失&#xff0c;就在项目设置下将&#xff1a; 即可解决问题&#xff1a; 看了半个小时的导航生成代码发现&#xff0c;NavDataSet这个数组为空&#xff0c;导致异步构建导航失败。 解决 NavDataSet 空 无法生成如下&#xff1a; 当 NavDataSet 为空的化 如果 bAut…

MCP(Model Context Protocol 模型上下文协议)科普

MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;是由人工智能公司 Anthropic 于 2024年11月 推出的开放标准协议&#xff0c;旨在为大型语言模型&#xff08;LLM&#xff09;与外部数据源、工具及服务提供标准化连接&#xff0c;从而提升AI在实际…

健康养生指南

在快节奏的现代生活中&#xff0c;健康养生成为人们关注的焦点。它不仅关乎身体的强健&#xff0c;更是提升生活质量、预防疾病的关键。掌握科学的养生方法&#xff0c;能让我们在岁月流转中始终保持活力。 饮食是健康养生的基础。遵循 “均衡膳食” 原则&#xff0c;每日饮食需…

Linux系统:进程终止的概念与相关接口函数(_exit,exit,atexit)

本节目标 理解进程终止的概念理解退出状态码的概念以及使用方法掌握_exit与exit函数的用法以及区别atexit函数注册终止时执行的函数相关宏 一、进程终止 进程终止&#xff08;Process Termination&#xff09;是指操作系统结束一个进程的执行&#xff0c;回收其占用的资源&a…

Linux下 文件的查找、复制、移动和解压缩

1、在/var/log目录下创建一个hehe.log的文件&#xff0c;其文件内容是&#xff1a; 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串口通信

主要参考学习资料&#xff1a; B站江协科技 STM32入门教程-2023版 细致讲解 中文字幕 开发资料下载链接&#xff1a;https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwddspb 单片机套装&#xff1a;STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协 实验&…

fastlio用mid360录制的bag包离线建图,提示消息类型错误

我用mid360录制的bag包&#xff0c;激光雷达的数据类型是sensor_msgs::PointCloud2&#xff0c;但是运行fast_lio中的mid360 launch文件&#xff0c;会报错&#xff08;没截图&#xff09;&#xff0c;显示无法从livox_ros_driver2::CustomMsg转换到sensor_msgs::PointCloud2。…

二级评论列表-Java实现

二级评论列表是很常见的功能&#xff0c;文章记录了新手用Java实现的具体逻辑。 整体实现逻辑是先用2个sql&#xff0c;分别查出两层数据。然后用java在service中实现数据组装&#xff0c;返给前端。这种实现思路好处是SQL简洁&#xff0c;逻辑分明&#xff0c;便于维护。 一…

IP检测工具“ipjiance”

目录 IP质量检测 应用场景 对网络安全的贡献 对网络管理的帮助 对用户决策的辅助作用 IP质量检测 检测IP的网络提供商&#xff1a;通过ASN&#xff08;自治系统编号&#xff09;识别IP地址所属的网络运营商&#xff0c;例如电信、移动、联通等。 识别网络类型&#xff1…

Replicate Python client

本文翻译整理自&#xff1a;https://github.com/replicate/replicate-python 文章目录 一、关于 Replicate Python 客户端相关链接资源关键功能特性 二、1.0.0 版本的重大变更三、安装与配置1、系统要求2、安装3、认证配置 四、核心功能1、运行模型2、异步IO支持3、流式输出模型…

deekseak 本地windows 10 部署步骤

有些场景需要本地部署&#xff0c;例如金融、医疗&#xff08;HIPAA&#xff09;、政府&#xff08;GDPR&#xff09;、军工等&#xff0c;需完全控制数据存储和访问权限&#xff0c;避免云端合规风险或者偏远地区、船舶、矿井等无法依赖云服务&#xff0c;关键设施&#xff08…

<sql>、<resultMap>、<where>、<foreach>、<trim>、<set>等标签的作用和用法

目录 一. sql 代码片段标签 二. resultMap 映射结果集标签 三. where 条件标签 四. set 修改标签 五. trim 标签 六. foreach 循环标签 一. sql 代码片段标签 sql 标签是 mybatis 框架中一个非常常用的标签页&#xff0c;特别是当一张表很有多个字段多&#xff0c;或者要…

【项目】CherrySudio配置MCP服务器

CherrySudio配置MCP服务器 &#xff08;一&#xff09;Cherry Studio介绍&#xff08;二&#xff09;MCP服务环境搭建&#xff08;1&#xff09;环境准备&#xff08;2&#xff09;依赖组件安装<1> Bun和UV安装 &#xff08;3&#xff09;MCP服务器使用<1> 搜索MCP…

【技术派后端篇】 Redis 实现用户活跃度排行榜

在各类互联网应用中&#xff0c;排行榜是一个常见的功能需求&#xff0c;它能够直观地展示用户的表现或贡献情况&#xff0c;提升用户的参与感和竞争意识。在技术派项目中&#xff0c;也引入了用户活跃度排行榜&#xff0c;该排行榜主要基于 Redis 的 ZSET 数据结构来实现。接下…

模拟算法(一)作业分析及答案

目录 作业1&#xff1a;角谷猜想 解题思路 &#xff1a; 代码实现&#xff1a; 作业2&#xff1a;校门外的树 解题思路 注意事项 代码实现 作业3&#xff1a;乒乓球 ​编辑 问题重述 解题思路&#xff1a; 作业1&#xff1a;角谷猜想 【描述】 所谓角谷猜想&#xf…

西红柿番茄检测数据集VOC+YOLO格式2320张1类别可用于计数

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2320 标注数量(xml文件个数)&#xff1a;2320 标注数量(txt文件个数)&#xff1a;2320 …

专题十六:虚拟路由冗余协议——VRRP

一、VRRP简介 VRRP&#xff08;Virtual Router Redundancy Protocol&#xff09;虚拟路由冗余协议通过把几台设备联合组成一台虚拟的设备&#xff0c;使用一定的机制保证当主机的下一跳设备出现故障时&#xff0c;及时将业务切换到备份设备&#xff0c;从而保持通讯的连续性和…

DDPM(diffusion)原理

DDPM&#xff08;diffusion&#xff09;原理 1、DDPM&#xff08;原理&#xff09;2、DDPM和 Conditional DDPM&#xff08;原理解释&#xff09;2.1. Diffusion Models 原理详解核心思想前向扩散过程&#xff08;Forward Diffusion&#xff09;反向去噪过程&#xff08;Revers…

《软件设计师》复习笔记(2.2)——效验码、体系结构、指令、流水线

目录 一、校验码 码距 奇偶校验码 循环冗余校验码&#xff08;CRC&#xff09; 海明码 真题示例&#xff1a; 二、体系结构 Flynn分类法 三、指令系统 指令组成 指令执行过程 指令的寻址方式 操作数的寻址方式 CISC vs RISC 真题示例&#xff1a; 四、流水线技…