轻松掌握FFmpeg编程:从架构到实践

轻松掌握FFmpeg编程:从架构到实践 (Master FFmpeg Programming with Ease: From Architecture to Practice

  • 引言 (Introduction)
    • FFmpeg简介与应用场景 (Brief Introduction and Application Scenarios of FFmpeg)
    • 为什么选择FFmpeg进行音视频处理 (Why Choose FFmpeg for Audio and Video Processing)
  • FFmpeg架构概览 (Overview of FFmpeg Architecture)
    • 核心组件介绍 (Introduction to Core Components)
      • i. libavcodec (编解码库) (Codec Library)
      • ii. libavformat (封装与解封装库) (Muxing and Demuxing Library)
      • iii. libavutil (实用工具库) (Utility Library)
      • iv. libavfilter (音视频滤镜库) (Audio and Video Filter Library)
      • v. libswscale (视频缩放库) (Video Scaling Library)
      • vi. libswresample (音频重采样库) (Audio Resampling Library)
  • 快速入门 (Quick Start)
    • a. 安装与环境配置 (Installation and Environment Configuration)
      • 1. Windows 系统安装与配置
        • a. 下载 FFmpeg
        • b. 解压与配置环境变量
      • 2. macOS 系统安装与配置
        • a. 使用 Homebrew 安装
      • 3. Linux 系统安装与配置
        • a. 使用包管理器安装
        • Ubuntu/Debian
        • Fedora
        • CentOS/RHEL
      • 4. 从源码编译安装
    • b. FFmpeg命令行基本使用 (Basic Usage of FFmpeg Command Line)
      • 1. 查询信息 (Query Information)
        • a. 查看 FFmpeg 版本
        • b. 查看支持的编码器/解码器
        • c. 查看支持的封装格式
        • d. 查看支持的滤镜
      • 2. 文件转换 (File Conversion)
        • a. 视频格式转换
        • b. 音频格式转换
      • 3. 提取音视频 (Extract Audio and Video)
        • a. 提取视频
        • b. 提取音频
      • 4. 编解码 (Encoding and Decoding)
        • a. 使用指定编码器转换格式
        • b. 转换视频分辨率
        • c. 改变视频帧率
        • d. 转换音频比特率
      • 5. 滤镜与特效 (Filters and Effects)
        • a. 视频裁剪
        • b. 视频旋转
        • c. 添加水印
        • d. 音频静音处理
  • FFmpeg编程实践 (FFmpeg Programming in Practice)
    • a. libavcodec编解码操作 (Codec Operations with libavcodec)
      • 1. 初始化解码器
      • 2. 初始化编码器
      • 3. 解码音视频帧
      • 4. 编码音视频帧
    • b. libavformat封装与解封装操作 (Muxing and Demuxing Operations with libavformat)
      • 1. 解封装操作
      • 2. 封装操作
    • c. libavfilter滤镜应用 (Filter Applications with libavfilter)
      • 1. 初始化滤镜图
      • 2. 添加滤镜
      • 3. 配置滤镜图
      • 4. 使用滤镜处理音视频帧
      • 5. 释放资源
    • d. libswscale与libswresample的使用 (Usage of libswscale and libswresample)
      • 1. libswscale:视频缩放
      • 2. libswresample:音频重采样
    • e. 编程实例:音频转换与视频转码 (Programming Examples: Audio Conversion and Video Transcoding)
      • 1. 音频转换:从 MP3 转换为 WAV
      • 2. 视频转码:从 MP4 转换为 MKV
      • 3. FFmpeg拼接图片为视频
      • 4.ffmpeg调整视频速度
  • 高级技巧与优化 (Advanced Techniques and Optimization)
    • a. 多线程编解码 (Multithreading Codec)
      • 1. 启用多线程编解码
      • 2. 注意事项
    • b. 硬件加速 (Hardware Acceleration)
      • 1. 初始化硬件加速设备
      • 2. 设置编解码器上下文
      • 3. 获取硬件加速帧
      • 4. 释放资源
    • c. 自定义滤镜开发 (Custom Filter Development)
      • 1. 定义滤镜结构体
      • 2. 实现回调函数
      • 3. 注册滤镜
      • d. 零拷贝解码和编码 (Zero-Copy Decoding and Encoding)
        • 1. 解码过程
        • 2. 编码过程
      • e. 利用矩阵变换优化视频旋转和翻转 (Optimizing Video Rotation and Flip with Matrix Transformations)
      • f. 利用FFmpeg实现实时音视频处理 (Real-time Audio and Video Processing with FFmpeg)
        • 1. 减少延迟
        • 2. 数据流处理
        • 3. 实时处理音频和视频
        • 4. 资源管理与错误处理
  • Fmpeg各个库的底层原理
  • flash文件与ffmpeg
    • flv和swf文件的区别
    • 其他不支持的格式
  • 优化ffmpeg的方法
    • 优化解码
    • 优化编码
  • Qt与ffmpeg
  • 调试FFmpeg的方法
  • FFmpeg与其他框架的对比
  • FFmpeg的优点和局限性
  • 基于FFmpeg 的个人项目
  • FFmpeg 搭配SDL
  • 结语

引言 (Introduction)

FFmpeg简介与应用场景 (Brief Introduction and Application Scenarios of FFmpeg)

FFmpeg是一个开源的音视频处理工具集,为用户提供了丰富的音视频编解码、转换、流媒体等功能。它包含了一个命令行程序以及多个库,开发者可以利用这些库进行定制化的音视频处理。FFmpeg具有广泛的应用场景,如格式转换、实时音视频传输、文件切割与合并、滤镜效果处理、编解码开发等。

FFmpeg is an open-source audio and video processing toolkit that provides users with a wealth of audio and video codec, conversion, streaming, and other functions. It includes a command-line program and multiple libraries, which developers can use for customized audio and video processing. FFmpeg has a wide range of application scenarios, such as format conversion, real-time audio and video transmission, file slicing and merging, filter effect processing, and codec development.

为什么选择FFmpeg进行音视频处理 (Why Choose FFmpeg for Audio and Video Processing)

选择FFmpeg进行音视频处理有以下几个主要原因:

  1. 功能强大:FFmpeg支持多种音视频格式、封装格式、滤镜效果等,覆盖了大部分音视频处理需求。
  2. 跨平台性:FFmpeg支持多种操作系统,包括Windows、macOS、Linux等,便于统一的应用开发。
  3. 高性能:FFmpeg优化了音视频处理性能,支持多线程处理与硬件加速,满足高性能需求。
  4. 灵活定制:FFmpeg的库组件可供开发者灵活使用,满足定制化的音视频处理需求。

The main reasons for choosing FFmpeg for audio and video processing are as follows:

  1. Powerful functions: FFmpeg supports various audio and video formats, container formats, filter effects, etc., covering most audio and video processing needs.
  2. Cross-platform: FFmpeg supports multiple operating systems, including Windows, macOS, Linux, etc., facilitating unified application development.
  3. High performance: FFmpeg optimizes audio and video processing performance, supports multithreading and hardware acceleration, and meets high-performance requirements.
  4. Flexible customization: FFmpeg’s library components can be flexibly used by developers to meet customized audio and video processing needs.

FFmpeg架构概览 (Overview of FFmpeg Architecture)

核心组件介绍 (Introduction to Core Components)

i. libavcodec (编解码库) (Codec Library)

libavcodec是FFmpeg中的核心组件之一,它提供了大量的音视频编解码器,支持多种流行的音视频编码格式。通过这个库,开发者可以实现对音视频文件的编码和解码操作。

编解码器(codec)是用于对音视频数据进行编码(压缩)和解码(解压)的工具。编码器(encoder)将未压缩的音视频数据转换为压缩格式,从而减少数据的存储空间和传输带宽;解码器(decoder)将压缩数据恢复为原始数据,以便进行播放和编辑。libavcodec支持诸如H.264、HEVC、VP8、VP9、AV1、AAC、MP3等众多编解码器。

除了常见的编解码器,libavcodec还提供了一些实用功能,如比特率控制、帧率转换、颜色空间转换等。这些功能可以帮助开发者实现更加复杂的音视频处理需求。

libavcodec is one of the core components of FFmpeg, providing a large number of audio and video codecs that support various popular audio and video encoding formats. With this library, developers can perform encoding and decoding operations on audio and video files.

A codec is a tool used to encode (compress) and decode (decompress) audio and video data. Encoders convert uncompressed audio and video data into compressed formats, thereby reducing storage space and transmission bandwidth. Decoders restore compressed data to the original data for playback and editing. libavcodec supports numerous codecs, such as H.264, HEVC, VP8, VP9, AV1, AAC, MP3, etc.

In addition to common codecs, libavcodec also provides some utility functions, such as bitrate control, frame rate conversion, color space conversion, etc. These features can help developers achieve more complex audio and video processing needs.

接下来,我们将介绍一些常用的libavcodec相关接口:

  1. AVCodec: AVCodec结构体用于表示一个编解码器,包含编解码器的基本信息,如名称、类型(音频或视频)、编解码器ID等。
  2. AVCodecContext: AVCodecContext结构体是编解码过程中的主要数据结构,用于存储编解码相关的参数和状态。它包含了编解码器的配置(如码率、分辨率、采样率等)、输入/输出数据格式等信息。
  3. avcodec_find_encoderavcodec_find_decoder: 这两个函数用于根据编解码器ID或名称查找对应的编解码器。
  4. avcodec_alloc_context3: 该函数用于为AVCodecContext结构体分配内存并进行初始化。
  5. avcodec_open2: 该函数用于打开编解码器并将其与AVCodecContext关联。
  6. avcodec_send_packetavcodec_receive_frame: 这两个函数用于向解码器发送压缩数据包(AVPacket)并接收解码后的数据帧(AVFrame)。对于编码器,使用 avcodec_send_frameavcodec_receive_packet 函数。
  7. avcodec_close: 该函数用于关闭编解码器并释放相关资源。
  8. avcodec_free_context: 该函数用于释放AVCodecContext结构体的内存。

以下是一个简单的音频解码示例:

AVCodec *codec;
AVCodecContext *codec_ctx;
AVPacket *pkt;
AVFrame *frame;
int ret;

// 查找解码器
codec = avcodec_find_decoder(AV_CODEC_ID_MP3);
if (!codec) {
    printf("解码器未找到\n");
    return -1;
}

// 初始化AVCodecContext
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
    printf("无法分配AVCodecContext\n");
    return -1;
}

// 打开解码器
ret = avcodec_open2(codec_ctx, codec, NULL);
if (ret < 0) {
    printf("无法打开解码器\n");
    return -1;
}

// 初始化AVPacket和AVFrame
pkt = av_packet_alloc();
frame = av_frame_alloc();

// 循环读取音频数据包并解码
while (read_packet(pkt)) {
    ret = avcodec_send_packet(codec_ctx, pkt);
    if (ret < 0) {
        printf("发送数据包失败\n");
        break;
    }

    while (ret >= 0) {
        ret = avcodec_receive_frame(codec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            break;
        if (ret < 0) {
            printf("解码失败\n");
            break;
        }
        
        // 处理解码后的数据帧
        process_decoded_frame(frame);
    }
}

// 关闭解码器并释放资源
avcodec_close(codec_ctx);
avcodec_free_context(&codec_ctx);
av_packet_free(&pkt);
av_frame_free(&frame);

ii. libavformat (封装与解封装库) (Muxing and Demuxing Library)

libavformat 是 FFmpeg 中的另一个核心组件,主要用于处理音视频数据的封装和解封装。封装(muxing)是将多个音视频轨道混合到一个容器文件中,例如 MP4、MKV、FLV 等。解封装(demuxing)则是将容器文件中的音视频轨道分离出来。通过 libavformat,开发者可以读取和创建各种音视频容器格式的文件。

libavformat 提供了以下几个主要的接口:

  1. AVFormatContext: AVFormatContext 结构体用于存储封装和解封装过程中的状态信息。对于输入文件,它包含了文件的元数据、音视频轨道信息等;对于输出文件,它包含了待写入的封装格式、音视频流配置等。
  2. avformat_open_inputavformat_find_stream_info: 这两个函数用于打开输入文件并获取音视频轨道的信息。
  3. avformat_alloc_output_context2: 该函数用于为输出文件的 AVFormatContext 分配内存并进行初始化。
  4. avformat_new_stream: 该函数用于在输出文件中创建新的音视频流。
  5. av_read_frame: 该函数用于从输入文件中读取一个封装数据包(AVPacket)。
  6. av_write_frameav_interleaved_write_frame: 这两个函数用于将封装数据包(AVPacket)写入输出文件。av_interleaved_write_frame 会对数据包进行交错处理以获得更好的播放性能。
  7. avformat_close_input: 该函数用于关闭输入文件并释放相关资源。
  8. avformat_free_context: 该函数用于释放 AVFormatContext 结构体的内存。

以下是一个简单的音视频复制示例,从输入文件读取音视频数据包并写入到输出文件中:

AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVPacket pkt;
int ret;

// 打开输入文件
ret = avformat_open_input(&ifmt_ctx, input_file, NULL, NULL);
if (ret < 0) {
    printf("无法打开输入文件\n");
    return -1;
}

// 获取音视频轨道信息
ret = avformat_find_stream_info(ifmt_ctx, NULL);
if (ret < 0) {
    printf("无法获取音视频轨道信息\n");
    return -1;
}

// 为输出文件分配 AVFormatContext
ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, output_file);
if (ret < 0) {
    printf("无法分配输出文件的 AVFormatContext\n");
    return -1;
}

// 复制输入文件的音视频轨道到输出文件
for (int i = 0; i < ifmt_ctx->nb_streams; i++) {
    AVStream *in_stream = ifmt_ctx->streams[i];
    AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL);
    if (!out_stream) {
    printf("无法创建输出音视频流\n");
    return -1;
}

// 复制编解码器参数
ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
if (ret < 0) {
    printf("复制编解码器参数失败\n");
    return -1;
}
out_stream->codecpar->codec_tag = 0; // 避免使用不兼容的编码标签

}

// 打开输出文件
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, output_file, AVIO_FLAG_WRITE);
if (ret < 0) {
printf("无法打开输出文件\n");
return -1;
}
}

// 写入文件头
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
printf("写入文件头失败\n");
return -1;
}

// 读取输入文件的数据包并写入到输出文件中
while (1) {
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0)
break;
AVStream *in_stream = ifmt_ctx->streams[pkt.stream_index];
AVStream *out_stream = ofmt_ctx->streams[pkt.stream_index];

// 转换时间戳
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;

// 写入数据包
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0) {
    printf("写入数据包失败\n");
    break;
}
av_packet_unref(&pkt);
}

// 写入文件尾
av_write_trailer(ofmt_ctx);

// 关闭文件并释放资源
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
avio_closep(&ofmt_ctx->pb);
avformat_close_input(&ifmt_ctx);
avformat_free_context(ofmt_ctx);


以上示例展示了如何使用 libavformat 从输入文件读取音视频数据包并将其复制到输出文件中。使用 libavformat 进行封装和解封装操作可以方便地处理各种音视频容器格式。

iii. libavutil (实用工具库) (Utility Library)

libavutil 是 FFmpeg 中的一个辅助工具库,为其他 FFmpeg 组件提供公共函数和数据结构。它包括了一些基本的数学运算、字符串操作、内存管理、时间计算等功能。此外,libavutil 还提供了一些特定的工具,如图像处理、音频处理、像素格式转换等。

以下是 libavutil 中一些主要功能的简介:

  1. 内存管理: libavutil 提供了一些内存管理函数,如 av_mallocav_reallocav_free 等。这些函数类似于标准 C 库中的 mallocreallocfree,但经过优化以提供更高的性能和安全性。
  2. 字典: AVDictionary 结构体用于存储键值对,常用于表示元数据信息。av_dict_setav_dict_get 函数用于设置和获取字典中的值。
  3. 时间处理: libavutil 提供了一些用于处理时间的函数,如 av_gettimeav_rescale_q 等。这些函数可以帮助开发者在不同的时间基准之间进行转换和计算。
  4. 像素格式和颜色空间转换: libavutil 包含了一些用于像素格式和颜色空间转换的函数。例如,sws_getContextsws_scale 函数可以用于对图像进行缩放和像素格式转换。
  5. 音频处理: libavutil 提供了一些音频处理功能,如重采样、格式转换等。例如,swr_alloc_set_optsswr_convert 函数可以用于进行音频重采样操作。

以下是一个使用 libavutil 进行图像缩放的示例:

#include 
#include 

// 初始化源图像和目标图像的 AVFrame
AVFrame *src_frame = ...; // 已分配并填充数据的源图像帧
AVFrame *dst_frame = av_frame_alloc();
dst_frame->width = new_width;
dst_frame->height = new_height;
dst_frame->format = src_frame->format;
av_frame_get_buffer(dst_frame, 32);

// 创建缩放上下文
struct SwsContext *sws_ctx = sws_getContext(src_frame->width, src_frame->height, src_frame->format,
                                            dst_frame->width, dst_frame->height, dst_frame->format,
                                            SWS_BILINEAR, NULL, NULL, NULL);

// 执行缩放操作
sws_scale(sws_ctx, (const uint8_t *const *)src_frame->data, src_frame->linesize,
          0, src_frame->height, dst_frame->data, dst_frame->linesize);

// 释放资源
sws_freeContext(sws_ctx);
av_frame_unref(src_frame);
av_frame_unref(dst_frame);

以上示例展示了如何使用 libavutil 中的 libswscale 组件进行图像缩放操作。

iv. libavfilter (音视频滤镜库) (Audio and Video Filter Library)

libavfilter 是 FFmpeg 中用于处理音视频滤镜的库,提供了丰富的音视频滤镜效果。例如,视频滤镜包括裁剪、缩放、翻转、旋转、水印添加等;音频滤镜包括均衡器、混响、降噪等。libavfilter 提供了一个灵活的滤镜图表设计,允许将多个滤镜连接在一起,创建复杂的处理流程。

以下是使用 libavfilter 的一些主要接口:

  1. AVFilterGraph: AVFilterGraph 结构体用于表示滤镜图。滤镜图是由滤镜节点(AVFilterContext)组成的有向无环图,表示音视频处理流程。
  2. avfilter_register_all: 该函数用于注册所有内置滤镜。在使用 libavfilter 之前,需要调用此函数进行初始化。
  3. avfilter_get_by_name: 该函数根据滤镜名称查找对应的 AVFilter 结构体。AVFilter 结构体表示一个滤镜的类型和属性。
  4. avfilter_graph_create_filter: 该函数用于在滤镜图中创建滤镜节点(AVFilterContext)。每个滤镜节点表示一个特定的滤镜实例,可以设置参数和配置输入输出。
  5. avfilter_link: 该函数用于将滤镜节点的输出端口连接到另一个滤镜节点的输入端口。这样可以将多个滤镜连接在一起,形成处理流程。
  6. av_buffersrc_add_frame_flagsav_buffersink_get_frame: 这两个函数用于将音视频帧添加到滤镜图的源滤镜,并从目标滤镜获取处理后的音视频帧。
  7. avfilter_graph_free: 该函数用于释放滤镜图及其资源。

以下是一个使用 libavfilter 对视频帧进行缩放和水印添加的示例:

AVFilterGraph *filter_graph;
AVFilterContext *buffersrc_ctx, *buffersink_ctx;
AVFilter *buffersrc, *buffersink, *scale, *overlay;
int ret;

// 注册所有内置滤镜并查找所需的滤镜
avfilter_register_all();
buffersrc = avfilter_get_by_name("buffer");
buffersink = avfilter_get_by_name("buffersink");
scale = avfilter_get_by_name("scale");
overlay = avfilter_get_by_name("overlay");

// 创建滤镜图
filter_graph = avfilter_graph_alloc();

// 创建并配置源滤镜节点
char args[512];
snprintf(args, sizeof(args),
         "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
         src_width, src_height, src_pix_fmt,
         time_base.num, time_base.den,
         sample_aspect_ratio.num, sample_aspect_ratio.den);
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "  in", args, NULL, filter_graph);
if (ret < 0) {
    printf("创建源滤镜节点失败\n");
    return -1;
}

// 创建并配置缩放滤镜节点
AVFilterContext *scale_ctx;
ret = avfilter_graph_create_filter(&scale_ctx, scale, "scale", "640x480", NULL, filter_graph);
if (ret < 0) {
    printf("创建缩放滤镜节点失败\n");
    return -1;
}

// 创建并配置水印滤镜节点
AVFilterContext *overlay_ctx;
ret = avfilter_graph_create_filter(&overlay_ctx, overlay, "overlay", "10:10", NULL, filter_graph);
if (ret < 0) {
    printf("创建水印滤镜节点失败\n");
    return -1;
}

// 创建并配置目标滤镜节点
AVBufferSinkParams *buffersink_params = av_buffersink_params_alloc();
enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
buffersink_params->pixel_fmts = pix_fmts;
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, buffersink_params, filter_graph);
av_free(buffersink_params);
if (ret < 0) {
    printf("创建目标滤镜节点失败\n");
    return -1;
}

// 连接滤镜节点
ret = avfilter_link(buffersrc_ctx, 0, scale_ctx, 0);
if (ret < 0) {
    printf("连接源滤镜和缩放滤镜失败\n");
    return -1;
}
ret = avfilter_link(scale_ctx, 0, overlay_ctx, 0);
if (ret < 0) {
    printf("连接缩放滤镜和水印滤镜失败\n");
    return -1;
}
ret = avfilter_link(overlay_ctx, 0, buffersink_ctx, 0);
if (ret < 0) {
    printf("连接水印滤镜和目标滤镜失败\n");
    return -1;
}

// 配置滤镜图
ret = avfilter_graph_config(filter_graph, NULL);
if (ret < 0) {
    printf("配置滤镜图失败\n");
    return -1;
}

// 添加视频帧到源滤镜并从目标滤镜获取处理后的帧
AVFrame *src_frame = ...; // 已分配并填充数据的源视频帧
AVFrame *dst_frame = av_frame_alloc();
ret = av_buffersrc_add_frame_flags(buffersrc_ctx, src_frame, AV_BUFFERSRC_FLAG_KEEP_REF);
if (ret < 0) {
    printf("添加视频帧到源滤镜失败\n");
    return -1;
}
ret = av_buffersink_get_frame(buffersink_ctx, dst_frame);
if (ret < 0) {
    printf("从目标滤镜获取处理后的视频帧失败\n");
    return -1;
}

// 释放资源
av_frame_unref(src_frame);
av_frame_unref(dst_frame);
avfilter_graph_free(&filter_graph);
   

以上示例展示了如何使用 libavfilter 对视频帧进行缩放和水印添加操作。通过将多个滤镜节点连接在一起,可以创建复杂的音视频处理流.

v. libswscale (视频缩放库) (Video Scaling Library)

libswscale 是 FFmpeg 中用于进行视频缩放和像素格式转换的库。它支持多种缩放算法,如双线性、双三次、Lanczos 等,并可处理各种像素格式的视频。libswscale 提供了高性能的 CPU 和 GPU 加速功能,能满足各种性能要求。

以下是使用 libswscale 的一些主要接口:

  1. sws_getContext: 该函数用于创建 SwsContext 结构体,用于存储视频缩放和像素格式转换的参数和状态。调用时需指定源视频的尺寸、像素格式和目标视频的尺寸、像素格式。
  2. sws_scale: 该函数用于执行视频缩放和像素格式转换操作。需要提供源视频帧和目标视频帧,以及 SwsContext 结构体。
  3. sws_freeContext: 该函数用于释放 SwsContext 结构体及其资源。

以下是一个使用 libswscale 对视频帧进行缩放和像素格式转换的示例:

#include 
#include 

// 初始化源视频帧和目标视频帧的 AVFrame
AVFrame *src_frame = ...; // 已分配并填充数据的源视频帧
AVFrame *dst_frame = av_frame_alloc();
dst_frame->width = new_width;
dst_frame->height = new_height;
dst_frame->format = new_pix_fmt;
av_frame_get_buffer(dst_frame, 32);

// 创建缩放上下文
struct SwsContext *sws_ctx = sws_getContext(src_frame->width, src_frame->height, (enum AVPixelFormat)src_frame->format,
                                            dst_frame->width, dst_frame->height, (enum AVPixelFormat)dst_frame->format,
                                            SWS_BILINEAR, NULL, NULL, NULL);

// 执行缩放操作
sws_scale(sws_ctx, (const uint8_t *const *)src_frame->data, src_frame->linesize,
          0, src_frame->height, dst_frame->data, dst_frame->linesize);

// 释放资源
sws_freeContext(sws_ctx);
av_frame_unref(src_frame);
av_frame_unref(dst_frame);

以上示例展示了如何使用 libswscale 对视频帧进行缩放和像素格式转换操作。在实际应用中,你可以根据需要选择不同的缩放算法和像素格式。

vi. libswresample (音频重采样库) (Audio Resampling Library)

libswresample 是 FFmpeg 中用于进行音频重采样、格式转换和通道重新排列的库。它支持多种采样率、采样格式和通道布局的音频处理。libswresample 提供了高性能的 CPU 和 GPU 加速功能,满足各种性能要求。

以下是使用 libswresample 的一些主要接口:

  1. swr_alloc_set_opts: 该函数用于创建并配置 SwrContext 结构体,用于存储音频重采样、格式转换和通道重新排列的参数和状态。调用时需指定源音频的采样率、采样格式、通道布局和目标音频的采样率、采样格式、通道布局。
  2. swr_init: 该函数用于初始化 SwrContext 结构体,完成参数设置后需调用此函数。
  3. swr_convert: 该函数用于执行音频重采样、格式转换和通道重新排列操作。需要提供源音频帧和目标音频帧,以及 SwrContext 结构体。
  4. swr_free: 该函数用于释放 SwrContext 结构体及其资源。

以下是一个使用 libswresample 对音频帧进行重采样、格式转换和通道重新排列的示例:

#include 
#include 

// 初始化源音频帧和目标音频帧的 AVFrame
AVFrame *src_frame = ...; // 已分配并填充数据的源音频帧
AVFrame *dst_frame = av_frame_alloc();
dst_frame->nb_samples = src_frame->nb_samples;
dst_frame->channel_layout = new_channel_layout;
dst_frame->format = new_sample_fmt;
dst_frame->sample_rate = new_sample_rate;
av_frame_get_buffer(dst_frame, 0);

// 创建重采样上下文
SwrContext *swr_ctx = swr_alloc_set_opts(NULL,
                                         dst_frame->channel_layout, (enum AVSampleFormat)dst_frame->format, dst_frame->sample_rate,
                                         src_frame->channel_layout, (enum AVSampleFormat)src_frame->format, src_frame->sample_rate,
                                         0, NULL);
swr_init(swr_ctx);

// 执行重采样操作
int ret = swr_convert(swr_ctx, dst_frame->data, dst_frame->nb_samples,
                      (const uint8_t **)src_frame->data, src_frame->nb_samples);

// 释放资源
swr_free(&swr_ctx);
av_frame_unref(src_frame);
av_frame_unref(dst_frame);

以上示例展示了如何使用 libswresample 对音频帧进行重采样、格式转换和通道重新排列操作。在实际应用中,你可以根据需要选择不同的采样率、采样格式和通道布局。

快速入门 (Quick Start)

a. 安装与环境配置 (Installation and Environment Configuration)

在开始使用 FFmpeg 进行音视频处理之前,需要先在你的系统上安装 FFmpeg 库并进行相关环境配置。以下是针对不同操作系统的安装和配置指南:

1. Windows 系统安装与配置

a. 下载 FFmpeg

  • 访问 FFmpeg 官方网站下载页面:https://ffmpeg.org/download.html
  • 选择 Windows 系统的对应版本,下载预编译的二进制包。

b. 解压与配置环境变量

  • 解压下载到的压缩包至合适目录,例如 C:\ffmpeg
  • 将 FFmpeg 可执行文件路径添加至环境变量。在系统环境变量 Path 中,添加 C:\ffmpeg\bin

2. macOS 系统安装与配置

a. 使用 Homebrew 安装

  • 如果没有安装 Homebrew,请访问 https://brew.sh 并按照指南安装。
  • 安装 FFmpeg:在终端中运行以下命令:
    brew install ffmpeg
    

3. Linux 系统安装与配置

a. 使用包管理器安装

Ubuntu/Debian

  • 打开终端,运行以下命令更新软件包列表:
    sudo apt-get update
    
  • 运行以下命令安装 FFmpeg:
    sudo apt-get install ffmpeg
    

Fedora

  • 打开终端,运行以下命令安装 FFmpeg:
    sudo dnf install ffmpeg
    

CentOS/RHEL

  • 启用 EPEL 仓库:
    sudo yum install epel-release
    
  • 安装 Nux Dextop 仓库:
    sudo rpm -v --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
    sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
    
  • 安装 FFmpeg:
    sudo yum install ffmpeg ffmpeg-devel
    

4. 从源码编译安装

如果你需要从源码编译 FFmpeg,以便定制安装和启用特定功能,请参考官方编译指南:

  • Windows 编译指南
  • macOS 编译指南
  • Linux 编译指南

完成以上安装步骤后,FFmpeg 就可以在你的系统中使用了。在终端中输入 ffmpeg -version 命令,可以查看当前安装的 FFmpeg 版本。

b. FFmpeg命令行基本使用 (Basic Usage of FFmpeg Command Line)

FFmpeg 提供了强大的命令行工具,通过简单的命令,即可实现音视频处理的各种操作。以下是按功能分类的 FFmpeg 命令行基本使用指南:

1. 查询信息 (Query Information)

a. 查看 FFmpeg 版本

ffmpeg -version

b. 查看支持的编码器/解码器

ffmpeg -encoders
ffmpeg -decoders

c. 查看支持的封装格式

ffmpeg -formats

d. 查看支持的滤镜

ffmpeg -filters

2. 文件转换 (File Conversion)

a. 视频格式转换

ffmpeg -i input.mp4 output.avi

b. 音频格式转换

ffmpeg -i input.mp3 output.wav

3. 提取音视频 (Extract Audio and Video)

a. 提取视频

ffmpeg -i input.mp4 -vn -c:a copy output_audio.mp4

b. 提取音频

ffmpeg -i input.mp4 -an -c:v copy output_video.mp4

4. 编解码 (Encoding and Decoding)

a. 使用指定编码器转换格式

ffmpeg -i input.mp4 -c:v libx264 output.mkv

b. 转换视频分辨率

ffmpeg -i input.mp4 -vf scale=1280:720 output_720p.mp4

c. 改变视频帧率

ffmpeg -i input.mp4 -r 30 output_30fps.mp4

d. 转换音频比特率

ffmpeg -i input.mp3 -ab 128k output_128k.mp3

5. 滤镜与特效 (Filters and Effects)

a. 视频裁剪

ffmpeg -i input.mp4 -vf "crop=640:480:0:0" output_cropped.mp4

b. 视频旋转

ffmpeg -i input.mp4 -vf "transpose=1" output_rotated.mp4

c. 添加水印

ffmpeg -i input.mp4 -i watermark.png -filter_complex "overlay=10:10" output_watermarked.mp4

d. 音频静音处理

ffmpeg -i input.mp3 -af "volume=0" output_mute.mp3

以上仅为 FFmpeg 命令行基本用法的部分示例,FFmpeg 提供了丰富的选项和功能。要了解更多关于 FFmpeg 命令行工具的使用方法,请参考官方文档:https://ffmpeg.org/documentation.html。

FFmpeg编程实践 (FFmpeg Programming in Practice)

a. libavcodec编解码操作 (Codec Operations with libavcodec)

在这部分中,我们将详细介绍使用libavcodec库进行音视频编解码操作的基本步骤。

1. 初始化解码器

在使用libavcodec库解码音频或视频流之前,需要先初始化解码器。这里列出了初始化解码器的基本步骤:

  1. 通过AVCodecID查找解码器。
    AVCodec *decoder = avcodec_find_decoder(codec_id);
    
  2. 为解码器分配AVCodecContext。
    AVCodecContext *decoder_ctx = avcodec_alloc_context3(decoder);
    
  3. 根据解码器的参数设置解码器上下文。
    avcodec_parameters_to_context(decoder_ctx, codec_parameters);
    
  4. 打开解码器。
    avcodec_open2(decoder_ctx, decoder, NULL);
    

2. 初始化编码器

与初始化解码器类似,使用libavcodec库进行音频或视频编码操作前,需要先初始化编码器。初始化编码器的基本步骤如下:

  1. 通过AVCodecID查找编码器。
    AVCodec *encoder = avcodec_find_encoder(codec_id);
    
  2. 为编码器分配AVCodecContext。
    AVCodecContext *encoder_ctx = avcodec_alloc_context3(encoder);
    
  3. 根据需要设置编码器上下文的参数。
    encoder_ctx->bit_rate = ...;
    encoder_ctx->width = ...;
    encoder_ctx->height = ...;
    encoder_ctx->pix_fmt = ...;
    encoder_ctx->time_base = ...;
    
  4. 打开编码器。
    avcodec_open2(encoder_ctx, encoder, NULL);
    

3. 解码音视频帧

完成解码器初始化后,可以使用libavcodec库解码音视频帧。以下是解码音视频帧的基本步骤:

  1. 将输入数据包发送到解码器上下文。
    avcodec_send_packet(decoder_ctx, &input_packet);
    
  2. 从解码器上下文接收解码后的音视频帧。
    AVFrame *decoded_frame = av_frame_alloc();
    avcodec_receive_frame(decoder_ctx, decoded_frame);
    

4. 编码音视频帧

编码音视频帧的基本步骤如下:

  1. 将待编码的音视频帧发送到编码器上下文。
    avcodec_send_frame(encoder_ctx, &input_frame);
    
  2. 从编码器上下文接收编码后的数据包。
    AVPacket output_packet;
    av_init_packet(&output_packet);
    output_packet.data = NULL;
    output_packet.size = 0;
    avcodec_receive_packet(encoder_ctx, &output_packet);
    

通过以上基本步骤,您已经可以实现对音视频帧的编解码操作。在实际编程中,您可能需要处理更复杂的场景,例如多路音视频流、不同编码格式的转换等。

b. libavformat封装与解封装操作 (Muxing and Demuxing Operations with libavformat)

使用libavformat库,我们可以轻松地处理多媒体文件的封装与解封装操作。在本节中,我们将讨论如何使用libavformat库进行封装与解封装操作。

1. 解封装操作

解封装操作包括从多媒体文件中提取音视频流的基本信息和数据。以下是解封装操作的基本步骤:

  1. 注册所有复用器和解复用器。
    av_register_all();
    
  2. 打开输入文件并分析文件格式。
    AVFormatContext *input_format_ctx = NULL;
    avformat_open_input(&input_format_ctx, input_file, NULL, NULL);
    avformat_find_stream_info(input_format_ctx, NULL);
    
  3. 查找音视频流。
    int video_stream_index = -1;
    int audio_stream_index = -1;
    
    for (int i = 0; i < input_format_ctx->nb_streams; i++) {
        if (input_format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i;
        } else if (input_format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audio_stream_index = i;
        }
    }
    
  4. 逐个读取数据包。
    AVPacket input_packet;
    av_init_packet(&input_packet);
    input_packet.data = NULL;
    input_packet.size = 0;
    
    while (av_read_frame(input_format_ctx, &input_packet) >= 0) {
        // 处理数据包...
        av_packet_unref(&input_packet);
    }
    

2. 封装操作

封装操作包括将音视频流的数据和基本信息写入多媒体文件。以下是封装操作的基本步骤:

  1. 创建输出文件并设置文件格式。
    AVFormatContext *output_format_ctx = NULL;
    avformat_alloc_output_context2(&output_format_ctx, NULL, NULL, output_file);
    if (!output_format_ctx) {
        avformat_alloc_output_context2(&output_format_ctx, NULL, "mpeg", output_file);
    }
    
  2. 添加音视频流。
    AVStream *video_stream = avformat_new_stream(output_format_ctx, video_codec);
    avcodec_parameters_from_context(video_stream->codecpar, video_codec_ctx);
    video_stream->time_base = video_codec_ctx->time_base;
    
    AVStream *audio_stream = avformat_new_stream(output_format_ctx, audio_codec);
    avcodec_parameters_from_context(audio_stream->codecpar, audio_codec_ctx);
    audio_stream->time_base = audio_codec_ctx->time_base;
    
  3. 打开输出文件。
    if (!(output_format_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_open(&output_format_ctx->pb, output_file, AVIO_FLAG_WRITE);
    }
    
  4. 写入文件头。
    avformat_write_header(output_format_ctx, NULL);
    
  5. 写入音视频数据包。
    av_interleaved_write_frame(output_format_ctx, &output_packet);
    
  6. 写入文件尾部。
av_write_trailer(output_format_ctx);
  1. 关闭输出文件。
    if (!(output_format_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&output_format_ctx->pb);
    }
    
  2. 释放资源。
    avformat_free_context(output_format_ctx);
    avformat_close_input(&input_format_ctx);
    

通过以上步骤,您已经可以实现音视频流的解封装与封装操作。这为处理多种复杂场景,如多路音视频流、不同封装格式的转换等提供了基础。在实际编程中,根据需求调整处理逻辑,以满足具体应用场景的要求。

c. libavfilter滤镜应用 (Filter Applications with libavfilter)

libavfilter库提供了丰富的音视频滤镜功能,包括缩放、剪辑、旋转、水印等。本节将介绍如何使用libavfilter库应用滤镜效果。

1. 初始化滤镜图

滤镜图是一系列滤镜节点的有向无环图。初始化滤镜图的基本步骤如下:

  1. 创建滤镜图上下文。
    AVFilterGraph *filter_graph = avfilter_graph_alloc();
    
  2. 创建滤镜输入节点(buffer)。
    const AVFilter *input_filter = avfilter_get_by_name("buffer");
    AVFilterContext *input_filter_ctx;
    char args[512];
    snprintf(args, sizeof(args),
             "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
             codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
             codec_ctx->time_base.num, codec_ctx->time_base.den,
             codec_ctx->sample_aspect_ratio.num,
             codec_ctx->sample_aspect_ratio.den);
    
    avfilter_graph_create_filter(&input_filter_ctx, input_filter, "in", args, NULL, filter_graph);
    
  3. 创建滤镜输出节点(buffersink)。
    const AVFilter *output_filter = avfilter_get_by_name("buffersink");
    AVFilterContext *output_filter_ctx;
    avfilter_graph_create_filter(&output_filter_ctx, output_filter, "out", NULL, NULL, filter_graph);
    

2. 添加滤镜

在滤镜图中,可以添加各种音视频滤镜,然后将它们连接在一起。这里以视频缩放滤镜为例,介绍如何添加滤镜:

  1. 创建缩放滤镜。
    const AVFilter *scale_filter = avfilter_get_by_name("scale");
    AVFilterContext *scale_filter_ctx;
    char scale_args[128];
    snprintf(scale_args, sizeof(scale_args), "width=%d:height=%d", target_width, target_height);
    avfilter_graph_create_filter(&scale_filter_ctx, scale_filter, "scale", scale_args, NULL, filter_graph);
    
  2. 将输入节点、缩放滤镜和输出节点连接起来。
    avfilter_link(input_filter_ctx, 0, scale_filter_ctx, 0);
    avfilter_link(scale_filter_ctx, 0, output_filter_ctx, 0);
    

3. 配置滤镜图

配置滤镜图,检查滤镜图的有效性并分配必要的资源。

avfilter_graph_config(filter_graph, NULL);

4. 使用滤镜处理音视频帧

使用滤镜图处理音视频帧,从输入节点发送待处理帧,从输出节点接收处理后的帧。

  1. 向输入节点发送帧。
    av_buffersrc_add_frame(input_filter_ctx, input_frame);
    
  2. 从输出节点接收处理后的帧。
    AVFrame *filtered_frame = av_frame_alloc();
    av_buffersink_get_frame(output_filter_ctx, filtered_frame);
    
    

5. 释放资源

在处理完所有音视频帧后,需要释放滤镜图相关的资源。

  1. 释放滤镜图上下文。
    avfilter_graph_free(&filter_graph);
    
  2. 释放帧。
    av_frame_free(&input_frame);
    av_frame_free(&filtered_frame);
    

通过上述步骤,您已经可以在音视频处理中应用libavfilter库提供的滤镜效果。根据需求,可以添加更多滤镜或调整滤镜参数,以满足不同应用场景的要求。

d. libswscale与libswresample的使用 (Usage of libswscale and libswresample)

libswscale和libswresample分别用于视频缩放和音频重采样,它们是音视频处理过程中常用的功能库。本节将介绍如何使用这两个库进行视频缩放和音频重采样操作。

1. libswscale:视频缩放

libswscale库提供了强大的视频缩放功能,支持多种缩放算法。以下是使用libswscale进行视频缩放的基本步骤:

  1. 初始化缩放上下文。
    SwsContext *sws_ctx = sws_getContext(src_width, src_height, src_pix_fmt,
                                         dst_width, dst_height, dst_pix_fmt,
                                         SWS_BILINEAR, NULL, NULL, NULL);
    
  2. 分配源视频帧和目标视频帧。
    AVFrame *src_frame = av_frame_alloc();
    AVFrame *dst_frame = av_frame_alloc();
    
    src_frame->width = src_width;
    src_frame->height = src_height;
    src_frame->format = src_pix_fmt;
    dst_frame->width = dst_width;
    dst_frame->height = dst_height;
    dst_frame->format = dst_pix_fmt;
    
    av_frame_get_buffer(src_frame, 0);
    av_frame_get_buffer(dst_frame, 0);
    
  3. 进行视频缩放。
    sws_scale(sws_ctx, src_frame->data, src_frame->linesize, 0, src_height,
              dst_frame->data, dst_frame->linesize);
    
  4. 释放资源。
    sws_freeContext(sws_ctx);
    av_frame_free(&src_frame);
    av_frame_free(&dst_frame);
    

2. libswresample:音频重采样

libswresample库提供了音频重采样功能,支持不同采样率、声道布局和采样格式之间的转换。以下是使用libswresample进行音频重采样的基本步骤:

  1. 初始化重采样上下文。
    SwrContext *swr_ctx = swr_alloc_set_opts(NULL,
                                             dst_channel_layout, dst_sample_fmt, dst_sample_rate,
                                             src_channel_layout, src_sample_fmt, src_sample_rate,
                                             0, NULL);
    swr_init(swr_ctx);
    
  2. 分配源音频帧和目标音频帧。
    AVFrame *src_frame = av_frame_alloc();
    AVFrame *dst_frame = av_frame_alloc();
    
    src_frame->nb_samples = src_nb_samples;
    src_frame->channel_layout = src_channel_layout;
    src_frame->format = src_sample_fmt;
    dst_frame->nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, src_sample_rate) + src_nb_samples,
                                           dst_sample_rate, src_sample_rate, AV_ROUND_UP);
    dst_frame->channel_layout = dst_channel_layout;
    dst_frame->format = dst_sample_fmt;
    
    av_frame_get_buffer(src_frame, 0);
    av_frame_get_buffer(dst_frame, 0);
    
  3. 进行音频重采样。
    swr_convert(swr_ctx, dst_frame->data, dst_frame->nb_samples,
    (const uint8_t **)src_frame->data,src_frame->nb_samples);
    
    

4. 更新目标音频帧的参数。


   dst_frame->pts = av_rescale_q(src_frame->pts, (AVRational){1, src_sample_rate}, (AVRational){1, dst_sample_rate});
  1. 释放资源。
    swr_free(&swr_ctx);
    av_frame_free(&src_frame);
    av_frame_free(&dst_frame);
    

通过上述步骤,您已经可以使用libswscale和libswresample库进行视频缩放和音频重采样操作。在实际应用中,可以根据需求调整缩放算法、采样率、声道布局等参数,以满足音视频处理的具体要求。

e. 编程实例:音频转换与视频转码 (Programming Examples: Audio Conversion and Video Transcoding)

本节将介绍使用 FFmpeg 库进行音频转换和视频转码的 C 语言编程示例。请确保你已正确安装并配置了 FFmpeg 开发环境。我们将逐步实现以下功能:

  1. 音频转换:将输入音频文件从 MP3 格式转换为 WAV 格式
  2. 视频转码:将输入视频文件从 MP4 格式转换为 MKV 格式,并更改编码器和分辨率

1. 音频转换:从 MP3 转换为 WAV

以下是一个简单的音频转换程序示例,从 MP3 格式转换为 WAV 格式。

#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[]) {
    // 验证参数
    if (argc != 3) {
        printf("Usage: %s input.mp3 output.wav\n", argv[0]);
        return 1;
    }

    // 初始化 FFmpeg
    av_register_all();

    // 打开输入文件并解析格式
    AVFormatContext *input_format_ctx = NULL;
    if (avformat_open_input(&input_format_ctx, argv[1], NULL, NULL) < 0) {
        printf("Error opening input file\n");
        return 1;
    }

    if (avformat_find_stream_info(input_format_ctx, NULL) < 0) {
        printf("Error finding stream info\n");
        return 1;
    }

    // 查找音频流
    int audio_stream_index = -1;
    for (int i = 0; i < input_format_ctx->nb_streams; i++) {
        if (input_format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            audio_stream_index = i;
            break;
        }
    }

    if (audio_stream_index == -1) {
        printf("Error finding audio stream\n");
        return 1;
    }

    // 打开音频解码器
    AVCodecParameters *input_codecpar = input_format_ctx->streams[audio_stream_index]->codecpar;
    AVCodec *input_codec = avcodec_find_decoder(input_codecpar->codec_id);
    AVCodecContext *input_codec_ctx = avcodec_alloc_context3(input_codec);
    avcodec_parameters_to_context(input_codec_ctx, input_codecpar);

    if (avcodec_open2(input_codec_ctx, input_codec, NULL) < 0) {
        printf("Error opening audio decoder\n");
        return 1;
    }

    // 创建输出文件并设置格式
    AVFormatContext *output_format_ctx = NULL;
    avformat_alloc_output_context2(&output_format_ctx, NULL, NULL, argv[2]);
    if (!output_format_ctx) {
        avformat_alloc_output_context2(&output_format_ctx, NULL, "wav", argv[2]);
    }

    if (!output_format_ctx) {
        printf("Error creating output context\n");
        return 1;
    }

    // 创建音频编码器
    AVCodec *output_codec = avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE);
    AVStream *output_stream = avformat_new_stream(output_format_ctx, output_codec);
    AVCodecContext *output_codec_ctx = avcodec_alloc_context3(output_codec);

    // 配置编码器参数
    output_codec_ctx->channels = input_codec_ctx->channels;
    output_codec_ctx->channel_layout = input_codec_ctx->channel_layout;
    output_codec_ctx->sample_rate = input_codec_ctx->sample_rate;
    output_codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16;
    output_codec_ctx->bit_rate = 16 * output_codec_ctx->channels * output_codec_ctx->sample_rate;
    output_codec_ctx->time_base = (AVRational){1, output_codec_ctx->sample_rate};

    // 打开音频编码器
    if (avcodec_open2(output_codec_ctx, output_codec, NULL) < 0) {
        printf("Error opening output codec\n");
        return 1;
    }

    avcodec_parameters_from_context(output_stream->codecpar, output_codec_ctx);

    // 打开输出文件
    if (!(output_format_ctx->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open(&output_format_ctx->pb, argv[2], AVIO_FLAG_WRITE) < 0) {
            printf("Error opening output file\n");
            return 1;
        }
    }

    // 写输出文件头
    if (avformat_write_header(output_format_ctx, NULL) < 0) {
        printf("Error writing output header\n");
        return 1;
    }

    // 读取输入音频帧
    AVPacket input_packet;
    av_init_packet(&input_packet);
    input_packet.data = NULL;
    input_packet.size = 0;

    AVFrame *decoded_frame = av_frame_alloc();

    // 设置重采样上下文
    SwrContext *swr_ctx = swr_alloc_set_opts(NULL,
                                             output_codec_ctx->channel_layout,
                                             output_codec_ctx->sample_fmt,
                                             output_codec_ctx->sample_rate,
                                             input_codec_ctx->channel_layout,
                                             input_codec_ctx->sample_fmt,
                                             input_codec_ctx->sample_rate,
                                             0, NULL);
    swr_init(swr_ctx);

    // 转换音频数据并写入输出文件
    while (av_read_frame(input_format_ctx, &input_packet) >= 0) {
        if (input_packet.stream_index == audio_stream_index) {
            if (avcodec_send_packet(input_codec_ctx, &input_packet) >= 0) {
                while (avcodec_receive_frame(input_codec_ctx, decoded_frame) >= 0) {
                    AVFrame *resampled_frame = av_frame_alloc();
                    resampled_frame->channel_layout = output_codec_ctx->channel_layout;
                    resampled_frame->format = output_codec_ctx->sample_fmt;
                    resampled_frame->sample_rate = output_codec_ctx->sample_rate;
                    resampled_frame->nb_samples = decoded_frame->nb_samples;

                    av_frame_get_buffer(resampled_frame, 0);
                    swr_convert(swr_ctx,
                                resampled_frame->data, resampled_frame->nb_samples,
                                (const uint8_t **)decoded_frame->data, decoded_frame->nb_samples);

                    AVPacket output_packet;
                    av_init_packet(&output_packet);
                    output_packet.data = NULL;
                    output_packet.size = 0;

                    if (avcodec_send_frame(output_codec_ctx, resampled_frame) >= 0) {
                        while (avcodec_receive_packet(output_codec_ctx, &output_packet) >= 0) {
                            av_packet_rescale_ts(&output_packet, input_codec_ctx->time_base, output_stream->time_base);
                            output_packet.stream_index = output_stream->index;
                            av_interleaved_write_frame(output_format_ctx, &output_packet);
                            av_packet_unref(&output_packet);
                        }
                    }

                    av_frame_free(&resampled_frame);
                }
            }
        }

        av_packet_unref(&input_packet);
    }

    // 写输出文件尾部
    av_write_trailer(output_format_ctx);

    // 关闭输出文件
    if (!(output_format_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&output_format_ctx->pb);
    }

    // 释放资源
    avformat_free_context(output_format_ctx);
    avformat_close_input(&input_format_ctx);
    av_frame_free(&decoded_frame);
    avcodec_free_context(&input_codec_ctx);
    avcodec_free_context(&output_codec_ctx);
    swr_free(&swr_ctx);

    return 0;
}


以上示例演示了如何使用 FFmpeg 库将 MP3 文件转换为 WAV 文件。请注意,为了简洁,示例省略了错误处理和详细注释。实际应用程序应添加适当的错误处理。

2. 视频转码:从 MP4 转换为 MKV

以下是一个简单的视频转码程序示例,从 MP4 格式转换为 MKV 格式,更改编码器和分辨率。

#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Usage: %s input.mp4 output.mkv\n", argv[0]);
        return 1;
    }

    // 初始化 FFmpeg
    av_register_all();

    // 打开输入文件并解析格式
    AVFormatContext *input_format_ctx = NULL;
    if (avformat_open_input(&input_format_ctx, argv[1], NULL, NULL) < 0) {
        printf("Error opening input file\n");
        return 1;
    }

    if (avformat_find_stream_info(input_format_ctx, NULL) < 0) {
        printf("Error finding stream info\n");
        return 1;
    }

    // 查找视频流
    int video_stream_index = -1;
    for (int i = 0; i < input_format_ctx->nb_streams; i++) {
        if (input_format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i;
            break;
        }
    }

    if (video_stream_index == -1) {
        printf("Error finding video stream\n");
        return 1;
    }

    // 打开视频解码器
    AVCodecParameters *input_codecpar = input_format_ctx->streams[video_stream_index]->codecpar;
    AVCodec *input_codec = avcodec_find_decoder(input_codecpar->codec_id);
    AVCodecContext *input_codec_ctx = avcodec_alloc_context3(input_codec);
    avcodec_parameters_to_context(input_codec_ctx, input_codecpar);

    if (avcodec_open2(input_codec_ctx, input_codec, NULL) < 0) {
        printf("Error opening video decoder\n");
        return 1;
    }

    // 创建输出文件并设置格式
    AVFormatContext *output_format_ctx = NULL;
    avformat_alloc_output_context2(&output_format_ctx, NULL, NULL, argv[2]);
    if (!output_format_ctx) {
        avformat_alloc_output_context2(&output_format_ctx, NULL, "matroska", argv[2]);
    }

    if (!output_format_ctx) {
        printf("Error creating output context\n");
        return 1;
    }

    // 创建视频编码器
    AVCodec *output_codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    AVStream *output_stream = avformat_new_stream(output_format_ctx, output_codec);
    AVCodecContext *output_codec_ctx = avcodec_alloc_context3(output_codec);

    // 配置编码器参数
    output_codec_ctx->width = 1280;
    output_codec_ctx->height = 720;
    output_codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
    output_codec_ctx->time_base = (AVRational){1, 25};
    output_codec_ctx->framerate = (AVRational){25, 1};
    output_codec_ctx->gop_size = 12;
    output_codec_ctx->max_b_frames = 2;
    output_codec_ctx->bit_rate = 4000000;
    output_codec_ctx->codec_id = AV_CODEC_ID_H264;
    output_codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;

    av_opt_set(output_codec_ctx->priv_data, "preset", "slow", 0);

    // 打开视频编码器
        if (avcodec_open2(output_codec_ctx, output_codec, NULL) < 0) {
        printf("Error opening video encoder\n");
        return 1;
    }

    avcodec_parameters_from_context(output_stream->codecpar, output_codec_ctx);

    // 打开输出文件
    if (!(output_format_ctx->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open(&output_format_ctx->pb, argv[2], AVIO_FLAG_WRITE) < 0) {
            printf("Error opening output file\n");
            return 1;
        }
    }

    // 写输出文件头
    if (avformat_write_header(output_format_ctx, NULL) < 0) {
        printf("Error writing output header\n");
        return 1;
    }

    // 设置图像转换上下文
    SwsContext *sws_ctx = sws_getContext(input_codec_ctx->width,
                                         input_codec_ctx->height,
                                         input_codec_ctx->pix_fmt,
                                         output_codec_ctx->width,
                                         output_codec_ctx->height,
                                         output_codec_ctx->pix_fmt,
                                         SWS_BILINEAR,
                                         NULL, NULL, NULL);

    // 读取输入视频帧
    AVPacket input_packet;
    av_init_packet(&input_packet);
    input_packet.data = NULL;
    input_packet.size = 0;

    AVFrame *decoded_frame = av_frame_alloc();

    // 转换视频帧并写入输出文件
    while (av_read_frame(input_format_ctx, &input_packet) >= 0) {
        if (input_packet.stream_index == video_stream_index) {
            if (avcodec_send_packet(input_codec_ctx, &input_packet) >= 0) {
                while (avcodec_receive_frame(input_codec_ctx, decoded_frame) >= 0) {
                    AVFrame *resampled_frame = av_frame_alloc();
                    resampled_frame->width = output_codec_ctx->width;
                    resampled_frame->height = output_codec_ctx->height;
                    resampled_frame->format = output_codec_ctx->pix_fmt;
                    av_image_alloc(resampled_frame->data, resampled_frame->linesize,
                                   output_codec_ctx->width, output_codec_ctx->height, output_codec_ctx->pix_fmt, 32);

                    sws_scale(sws_ctx,
                              (const uint8_t *const *)decoded_frame->data, decoded_frame->linesize,
                              0, input_codec_ctx->height,
                              resampled_frame->data, resampled_frame->linesize);

                    resampled_frame->pts = av_rescale_q(decoded_frame->pts,
                                                        input_format_ctx->streams[video_stream_index]->time_base,
                                                        output_stream->time_base);

                    AVPacket output_packet;
                    av_init_packet(&output_packet);
                    output_packet.data = NULL;
                    output_packet.size = 0;

                    if (avcodec_send_frame(output_codec_ctx, resampled_frame) >= 0) {
                        while (avcodec_receive_packet(output_codec_ctx, &output_packet) >= 0) {
                            output_packet.stream_index = output_stream->index;
                            av_interleaved_write_frame(output_format_ctx, &output_packet);
                            av_packet_unref(&output_packet);
                        }
                    }

                    av_frame_unref(resampled_frame);
                }
            }
        }

        av_packet_unref(&input_packet);
    }

    // 写输出文件尾部
    av_write_trailer(output_format_ctx);

    // 关闭输出文件
    if (!(output_format_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&output_format_ctx->pb);
    }

    // 释放资源
    avformat_free_context(output_format_ctx);
    avformat_close_input(&input_format_ctx);
    av_frame_free(&decoded_frame);
    avcodec_free_context(&input_codec_ctx);
    avcodec_free_context(&output_codec_ctx);
    sws_freeContext(sws_ctx);

    return 0;
}



以上示例演示了如何使用FFmpeg库将MP4文件转换为MKV文件,同时改变编码器和分辨率。请注意,为了简洁,示例省略了错误处理和详细注释。实际应用程序应添加适当的错误处理。

3. FFmpeg拼接图片为视频

要使用 FFmpeg 将一组图片拼接成视频,您可以使用 image2 选项。假设您的图片命名格式为 image-%03d.jpg,即从 image-001.jpg 开始,按顺序编号。以下是一个基本的 FFmpeg 命令,将这些图片合并为一个 MP4 视频:

ffmpeg -framerate 30 -i image-%03d.jpg -c:v libx264 -pix_fmt yuv420p output.mp4

在这个命令中:

  • -framerate 30:设置每秒显示 30 帧。您可以根据需要更改此值。
  • -i image-%03d.jpg:指定输入文件模式。%03d 表示一个三位数的整数。如果您的图片名称有不同的格式,请相应地修改。
  • -c:v libx264:指定输出视频编解码器为 H.264。
  • -pix_fmt yuv420p:设置像素格式为 yuv420p。这是大多数播放器和设备支持的常用格式。
  • output.mp4:指定输出文件名。

关于时间戳,FFmpeg 会自动处理每个帧的时间戳,您无需手动设置。它会根据您指定的帧率将图片按顺序组合成视频。如果您希望在图片之间添加延迟,可以通过调整 -framerate 参数来实现。

例如,如果您想让每张图片显示 2 秒,可以将帧率设置为 0.5(即 1/2):

ffmpeg -framerate 0.5 -i image-%03d.jpg -c:v libx264 -pix_fmt yuv420p output.mp4

在拼接图片时,FFmpeg 主要处理以下元素:

  1. 输入图片的顺序和命名格式。
  2. 帧率(即每秒显示的帧数)。
  3. 输出视频编解码器和格式设置(如 H.264 编解码器和 yuv420p 像素格式)。
  4. 输出视频文件名和格式。

使用这些设置,您可以方便地将一组图片拼接成视频。

/*
这段代码的目标是从指定文件夹中读取所有图片文件,然后将它们拼接成一个MP4视频。代码的工作原理可以分为以下几个部分:

获取文件夹中的所有图片文件名。
初始化FFmpeg库,并创建输出MP4文件。
设置视频编码器并初始化其参数。
创建并设置视频流。
初始化用于存储图像帧的AVFrame结构。
初始化用于重采样图像数据的SwsContext结构。
逐帧读取图片文件。
a. 打开图片文件并获取图像帧。
b. 将图像数据从RGB24转换为YUV420P。
c. 编码帧并写入到输出MP4文件中。
写入输出文件的尾部信息并关闭文件。
释放所有资源。
关于图片格式不同的问题,这个代码示例处理的是以连续数字命名的图片(如:image_0001.jpg,image_0002.jpg,...)。在读取图片时,FFmpeg会自动识别并处理不同的图像文件格式。然而,不同尺寸的图片可能会导致问题,因为FFmpeg可能无法正确处理不同尺寸的图像。如果图片具有不同的尺寸,你需要在将它们拼接成视频之前将它们缩放到相同的尺寸。

此外,如果图片文件名没有按照连续数字的顺序命名,这个代码示例可能无法正确识别和处理它们。你可以根据实际需求修改 get_image_files() 函数,以便正确处理你的图片文件名。

在实际应用中,可能需要根据实际需求和约束调整此代码示例,以获得最佳结果。
*/
extern "C" {
#include 
#include 
#include 
#include 
#include 
}

#include 
#include 
#include 
#include 
#include 
#include 
#include 
static audio_init(){

}
// 获取图片文件名列表
std::vector<std::string> get_image_files(const std::string& directory) {
    std::vector<std::string> files;
    for (const auto& entry : std::filesystem::directory_iterator(directory)) {
        if (entry.is_regular_file()) {
            files.push_back(entry.path().string());
        }
    }
    std::sort(files.begin(), files.end());
    return files;
}

// 为FFmpeg的错误代码生成详细的错误信息
std::string ffmpeg_error_string(int err) {
    char buf[1024];
    av_strerror(err, buf, sizeof(buf));
    return std::string(buf);
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cout << "Usage: " << argv[0] << "  " << std::endl;
        return 1;
    }

    std::string input_directory = argv[1];
    std::string output_file = argv[2];
    int width = 1280;
    int height = 720;
    int fps = 25;

    av_register_all();
    // 音频输入文件路径
const char* audio_input_path = "audio_input.mp3";

// 打开音频文件
AVFormatContext* audio_input_format_ctx = nullptr;
if (avformat_open_input(&audio_input_format_ctx, audio_input_path, nullptr, nullptr) < 0) {
    throw std::runtime_error("Failed to open audio input file");
}

// 查找音频文件的音频流
int audio_stream_index = -1;
for (unsigned int i = 0; i < audio_input_format_ctx->nb_streams; ++i) {
    if (audio_input_format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
        audio_stream_index = i;
        break;
    }
}

if (audio_stream_index == -1) {
    throw std::runtime_error("No audio stream found in audio input file");
}

// 获取音频解码器并创建音频解码器上下文
AVCodec* audio_input_codec = avcodec_find_decoder(audio_input_format_ctx->streams[audio_stream_index]->codecpar->codec_id);
AVCodecContext* audio_input_codec_ctx = avcodec_alloc_context3(audio_input_codec);
avcodec_parameters_to_context(audio_input_codec_ctx, audio_input_format_ctx->streams[audio_stream_index]->codecpar);

if (avcodec_open2(audio_input_codec_ctx, audio_input_codec, nullptr) < 0) {
    throw std::runtime_error("Failed to open audio input codec");
}

// 将音频流添加到输出文件中
AVStream* audio_output_stream = avformat_new_stream(format_ctx, audio_input_codec);
if (!audio_output_stream) {
    throw std::runtime_error("Failed to allocate audio output stream");
}

avcodec_parameters_from_context(audio_output_stream->codecpar, audio_input_codec_ctx);
audio_output_stream->codecpar->codec_tag = 0;

// 初始化音频解封装
if (avformat_find_stream_info(audio_input_format_ctx, nullptr) < 0) {
    throw std::runtime_error("Failed to find stream information in audio input file");
}


    // 获取文件夹中的图片文件
    std::vector<std::string> files = get_image_files(input_directory);
    if (files.empty()) {
        std::cout << "Error: No image files found in the specified directory." << std::endl;
        return 1;
    }

    // 初始化输出文件格式
    AVFormatContext* format_ctx = nullptr;
    int ret = avformat_alloc_output_context2(&format_ctx, nullptr, nullptr, output_file.c_str());
    if (ret < 0) {
        throw std::runtime_error("Failed to allocate output context: " + ffmpeg_error_string(ret));
    }
    AVOutputFormat* output_fmt = format_ctx->oformat;

    // 初始化编码器
    AVCodec* codec = avcodec_find_encoder(output_fmt->video_codec);
    if (!codec) {
        throw std::runtime_error("Video codec not found.");
    }

    AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) {
        throw std::runtime_error("Failed to allocate video codec context.");
    }

    codec_ctx->bit_rate = 400000;
    codec_ctx->width = width;
    codec_ctx->height = height;
    codec_ctx->time_base = (AVRational){1, fps};
    codec_ctx->gop_size = 12;
    codec_ctx->max_b_frames = 2;
    codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;

    if (codec->id == AV_CODEC_ID_H264) {
        av_opt_set(codec_ctx->priv_data, "preset", "slow", 0);
    }

    ret = avcodec_open2(codec_ctx, codec, nullptr);
    if (ret < 0) {
    throw std::runtime_error("Failed to open video codec: " + ffmpeg_error_string(ret));
    }
   // 创建视频流
AVStream* video_stream = avformat_new_stream(format_ctx, nullptr);
if (!video_stream) {
    throw std::runtime_error("Failed to create video stream.");
}
video_stream->id = format_ctx->nb_streams - 1;
video_stream->time_base = (AVRational){1, fps};
avcodec_parameters_from_context(video_stream->codecpar, codec_ctx);

// 打开输出文件
ret = avio_open(&format_ctx->pb, output_file.c_str(), AVIO_FLAG_WRITE);
if (ret < 0) {
    throw std::runtime_error("Failed to open output file: " + ffmpeg_error_string(ret));
}

// 写输出文件头
ret = avformat_write_header(format_ctx, nullptr);
if (ret < 0) {
    throw std::runtime_error("Failed to write output file header: " + ffmpeg_error_string(ret));
}

// 初始化帧结构
AVFrame* frame = av_frame_alloc();
if (!frame) {
    throw std::runtime_error("Failed to allocate video frame.");
}
frame->format = codec_ctx->pix_fmt;
frame->width = codec_ctx->width;
frame->height = codec_ctx->height;
ret = av_image_alloc(frame->data, frame->linesize, frame->width, frame->height, codec_ctx->pix_fmt, 32);
if (ret < 0) {
    throw std::runtime_error("Failed to allocate image buffer: " + ffmpeg_error_string(ret));
}

// 初始化重采样上下文
SwsContext* sws_ctx = sws_getContext(width, height, AV_PIX_FMT_RGB24,
                                     width, height, codec_ctx->pix_fmt,
                                     SWS_BILINEAR, nullptr, nullptr, nullptr);
if (!sws_ctx) {
    throw std::runtime_error("Failed to initialize the scaling context.");
}

// 逐帧读取图片文件并编码
int pts = 0;
for (const auto& file : files) {
    AVFrame* input_frame = nullptr;

    try {
        // 读取图片文件
        input_frame = av_frame_alloc();
        AVFormatContext* input_format_ctx = nullptr;
        ret = avformat_open_input(&input_format_ctx, file.c_str(), nullptr, nullptr);
        if (ret < 0) {
            throw std::runtime_error("Failed to open input file: " + ffmpeg_error_string(ret));
        }
        ret = avformat_find_stream_info(input_format_ctx, nullptr);
        if (ret < 0) {
            throw std::runtime_error("Failed to find input stream information: " + ffmpeg_error_string(ret));
        }
        int stream_index = av_find_best_stream(input_format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
        if (stream_index < 0) {
            throw std::runtime_error("Failed to find video stream in input file: " + ffmpeg_error_string(ret));
        }
        AVPacket input_packet;
        av_init_packet(&input_packet);
        input_packet.data = nullptr;
        input_packet.size = 0;
        ret = av_read_frame(input_format_ctx, &input_packet);
        if (ret < 0) {
            throw std::runtime_error("Failed to read input frame: " + ffmpeg_error_string(ret));
        }
        AVCodec input_codec = avcodec_find_decoder(input_format_ctx->streams[stream_index]->codecpar->codec_id);
        AVCodecContext input_codec_ctx = avcodec_alloc_context3(input_codec);
        avcodec_parameters_to_context(input_codec_ctx, input_format_ctx->streams[stream_index]->codecpar);
                ret = avcodec_open2(input_codec_ctx, input_codec, nullptr);
        if (ret < 0) {
            throw std::runtime_error("Failed to open input codec: " + ffmpeg_error_string(ret));
        }

        ret = avcodec_send_packet(input_codec_ctx, &input_packet);
        if (ret < 0) {
            throw std::runtime_error("Failed to send packet to input codec: " + ffmpeg_error_string(ret));
        }

        ret = avcodec_receive_frame(input_codec_ctx, input_frame);
        if (ret < 0) {
            throw std::runtime_error("Failed to receive frame from input codec: " + ffmpeg_error_string(ret));
        }

        // 将输入帧从RGB24转换为YUV420P
        sws_scale(sws_ctx, input_frame->data, input_frame->linesize, 0, height, frame->data, frame->linesize);

        // 设置帧的PTS
        frame->pts = pts++;

        // 编码帧
        AVPacket output_packet;
        av_init_packet(&output_packet);
        output_packet.data = nullptr;
        output_packet.size = 0;

        ret = avcodec_send_frame(codec_ctx, frame);
        if (ret < 0) {
            throw std::runtime_error("Failed to send frame to output codec: " + ffmpeg_error_string(ret));
        }

        while (ret >= 0) {
                // 读取音频帧
    AVPacket audio_input_packet;
    av_init_packet(&audio_input_packet);
    audio_input_packet.data = nullptr;
    audio_input_packet.size = 0;

    while (av_read_frame(audio_input_format_ctx, &audio_input_packet) >= 0) {
        // 只处理音频流的数据包
        if (audio_input_packet.stream_index == audio_stream_index) {
            // 将音频帧写入输出文件
            av_packet_rescale_ts(&audio_input_packet, audio_input_format_ctx->streams[audio_stream_index]->time_base, audio_output_stream->time_base);
            audio_input_packet.stream_index = audio_output_stream->index;

            if (av_interleaved_write_frame(format_ctx, &audio_input_packet) < 0) {
throw std::runtime_error("Failed to write audio frame to output file");
}
}
av_packet_unref(&audio_input_packet);


            ret = avcodec_receive_packet(codec_ctx, &output_packet);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                break;
            } else if (ret < 0) {
                throw std::runtime_error("Failed to receive packet from output codec: " + ffmpeg_error_string(ret));
            }

            av_packet_rescale_ts(&output_packet, codec_ctx->time_base, video_stream->time_base);
            output_packet.stream_index = video_stream->index;

            // 写入输出文件
            ret = av_interleaved_write_frame(format_ctx, &output_packet);
            if (ret < 0) {
                throw std::runtime_error("Failed to write output frame: " + ffmpeg_error_string(ret));
            }

            av_packet_unref(&output_packet);
        }

        // 清理输入帧和编解码器上下文
        av_frame_unref(input_frame);
        avcodec_close(input_codec_ctx);
        avcodec_free_context(&input_codec_ctx);
        avformat_close_input(&input_format_ctx);
    } catch (const std::exception& e) {
        std::cerr << "Error processing file '" << file << "': " << e.what() << std::endl;
        av_frame_unref(input_frame);
    }
}

// 写入输出文件尾部信息
ret = av_write_trailer(format_ctx);
if (ret < 0) {
    throw std::runtime_error("Failed to write output file trailer: " + ffmpeg_error_string(ret));
}

// 清理资源
avcodec_close(codec_ctx);
avcodec_free_context(&codec_ctx);
av_frame_free(&frame);
avio_closep(&format_ctx->pb);
avformat_free_context(format_ctx);
sws_freeContext(sws_ctx);

return 0;
}        


4.ffmpeg调整视频速度

抱歉,我误解了您的问题。确实,FFmpeg 是一个用 C 语言编写的库。下面是一个使用 C++ 和 FFmpeg 库调整视频播放速度的简单示例。这个示例将演示如何将视频速度调整为2倍速(加速):

首先,确保已经安装了 FFmpeg 库并设置好了相应的环境。接下来,创建一个名为main.cpp的 C++ 文件,并添加以下代码:

extern "C" {
#include 
#include 
#include 
#include 
#include 
}

#include 

int main() {
    // 注册所有编解码器、复用器和协议
    av_register_all();
    avfilter_register_all();

    // 打开输入文件并读取格式信息
    AVFormatContext* input_format_ctx = nullptr;
    if (avformat_open_input(&input_format_ctx, "input_video.mp4", nullptr, nullptr) < 0) {
        std::cerr << "Cannot open input file" << std::endl;
        return 1;
    }
    avformat_find_stream_info(input_format_ctx, nullptr);

    // 打开输出文件并设置格式信息
    AVFormatContext* output_format_ctx = nullptr;
    avformat_alloc_output_context2(&output_format_ctx, nullptr, nullptr, "output_video.mp4");
    if (!output_format_ctx) {
        std::cerr << "Cannot create output context" << std::endl;
        return 1;
    }

    // 设置倍速
    double speedup_factor = 2.0;

    // 复制输入文件的流信息到输出文件
    for (unsigned int i = 0; i < input_format_ctx->nb_streams; ++i) {
        AVStream* input_stream = input_format_ctx->streams[i];
        AVStream* output_stream = avformat_new_stream(output_format_ctx, input_stream->codec->codec);
        if (!output_stream) {
            std::cerr << "Cannot create output stream" << std::endl;
            return 1;
        }

        avcodec_copy_context(output_stream->codec, input_stream->codec);
        output_stream->codec->time_base = av_mul_q(input_stream->codec->time_base, (AVRational){speedup_factor * 1000, 1000});
        output_stream->time_base = output_stream->codec->time_base;
    }

    // 打开输出文件并开始写入
    if (!(output_format_ctx->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open(&output_format_ctx->pb, "output_video.mp4", AVIO_FLAG_WRITE) < 0) {
            std::cerr << "Cannot open output file" << std::endl;
            return 1;
        }
    }

    avformat_write_header(output_format_ctx, nullptr);

    // 读取输入文件的帧并写入输出文件
    AVPacket packet;
    while (true) {
        if (av_read_frame(input_format_ctx, &packet) < 0) {
            break;
        }

        AVStream* input_stream = input_format_ctx->streams[packet.stream_index];
        AVStream* output_stream = output_format_ctx->streams[packet.stream_index];

        // 修改时间戳以加速播放
        packet.pts = av_rescale_q(packet.pts, input_stream->time_base, output_stream->time_base);
        packet.dts = av_rescale_q(packet.dts, input_stream->time_base, output_stream->time_base);
        packet.duration = av_rescale_q(packet.duration, input_stream->time_base, output_stream->time_base);

        // 将帧写入输出文件
        if (av_interleaved_write_frame(output_format_ctx, &packet) < 0) {
            std::cerr << "Cannot write frame to output file" << std::endl;
            return 1;
        }

        av_packet_unref(&packet);
    }

    // 写入输出文件的尾部信息并关闭文件
    av_write_trailer(output_format_ctx);

    if (!(output_format_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_close(output_format_ctx->pb);
    }

    // 清理和释放资源
    avformat_close_input(&input_format_ctx);
    avformat_free_context(output_format_ctx);

    return 0;
}

这是完整的示例,它将输入视频文件(input_video.mp4)的速度调整为 2 倍速,并将其保存为输出文件(output_video.mp4)。请注意,这个示例没有处理错误的完整性,仅用于演示目的。在实际使用中,您可能需要增加更多的错误处理。

高级技巧与优化 (Advanced Techniques and Optimization)

a. 多线程编解码 (Multithreading Codec)

多线程编解码可以显著提高音视频处理的性能,特别是在多核CPU的计算机上。FFmpeg库提供了内置的多线程支持,本节将介绍如何在编解码过程中使用多线程。

1. 启用多线程编解码

在使用FFmpeg库进行编解码时,可以通过设置编解码器上下文的thread_countthread_type来启用多线程。以下是启用多线程编解码的示例:

AVCodecContext *codec_ctx = ...; // 初始化编解码器上下文

codec_ctx->thread_count = 4; // 设置线程数
codec_ctx->thread_type = FF_THREAD_SLICE | FF_THREAD_FRAME; // 设置线程类型

avcodec_open2(codec_ctx, codec, NULL); // 打开编解码器

其中,thread_count表示线程数,通常设置为CPU核心数。thread_type表示线程类型,可选的值有FF_THREAD_SLICE(片并行)和FF_THREAD_FRAME(帧并行),可以根据编解码器的特性和性能需求选择合适的线程类型。

2. 注意事项

启用多线程编解码时,需要注意以下几点:

  1. 不是所有编解码器都支持多线程。在使用多线程编解码前,请确保所选编解码器支持多线程。可以查阅FFmpeg官方文档或检查编解码器的capabilities字段(如codec->capabilities & AV_CODEC_CAP_FRAME_THREADS)来确定编解码器是否支持多线程。
  2. 多线程编解码可能导致帧的输出顺序与输入顺序不一致。在处理帧时,请确保根据帧的pict_typedisplay_picture_number属性正确处理帧顺序。
  3. 多线程编解码会增加资源消耗。在启用多线程编解码时,请确保系统具有足够的资源来支持多线程操作。

通过使用FFmpeg库的多线程编解码功能,您可以在提高音视频处理性能的同时,充分利用计算机的多核处理能力。在实际应用中,请根据硬件环境和性能要求选择合适的线程数和线程类型。

b. 硬件加速 (Hardware Acceleration)

硬件加速能够显著提高音视频处理性能,降低CPU的负担。FFmpeg支持多种硬件加速技术,如NVIDIA的NVENC/NVDEC、Intel的QSV和AMD的VAAPI等。本节将简要介绍如何使用FFmpeg实现硬件加速编解码。

1. 初始化硬件加速设备

首先,需要创建硬件加速设备上下文,以便使用特定硬件加速API。

AVBufferRef *hw_device_ctx = NULL;
int err = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA,
                                 NULL, NULL, 0);
if (err < 0) {
    // 错误处理
}

这里的示例使用NVIDIA的CUDA硬件加速。您可以根据实际硬件设备,选择其他硬件加速类型,如AV_HWDEVICE_TYPE_QSVAV_HWDEVICE_TYPE_VAAPI等。

2. 设置编解码器上下文

创建硬件加速设备上下文后,需要将其与编解码器上下文关联。

AVCodecContext *codec_ctx = ...; // 初始化编解码器上下文
codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx); // 关联硬件加速设备上下文

3. 获取硬件加速帧

在解码过程中,硬件加速帧将存储在AVFramehw_frames_ctx字段中。需要将其转换为普通的AVFrame以便进一步处理。

AVFrame *hw_frame = ...; // 获取硬件加速帧
AVFrame *sw_frame = av_frame_alloc();

err = av_hwframe_transfer_data(sw_frame, hw_frame, 0);
if (err < 0) {
    // 错误处理
}

4. 释放资源

在处理完成后,需要释放硬件加速设备上下文和其他资源。

av_buffer_unref(&hw_device_ctx);

通过上述步骤,您已经可以使用FFmpeg实现硬件加速编解码。在实际应用中,请根据硬件环境和性能需求选择合适的硬件加速技术。注意,不是所有编解码器都支持硬件加速,使用硬件加速时,请确保所选编解码器支持相应的硬件加速API。

c. 自定义滤镜开发 (Custom Filter Development)

在某些情况下,FFmpeg提供的内置滤镜可能无法满足特定的音视频处理需求。此时,您可以通过开发自定义滤镜来扩展FFmpeg的功能。本节将简要介绍如何开发一个自定义滤镜。

1. 定义滤镜结构体

首先,需要定义一个AVFilter结构体来描述滤镜的属性和功能。

static const AVFilter ff_vf_custom_filter = {
    .name          = "custom_filter",
    .description   = "Custom video filter for specific processing",
    .inputs        = custom_filter_inputs,
    .outputs       = custom_filter_outputs,
    .priv_class    = &custom_filter_class,
    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
    .init          = custom_filter_init,
    .uninit        = custom_filter_uninit,
    .query_formats = custom_filter_query_formats,
    .priv_size     = sizeof(CustomFilterContext),
    .filter_frame  = custom_filter_filter_frame,
};

AVFilter结构体包含多个回调函数,这些函数在滤镜的生命周期内被调用。例如,inituninit分别在滤镜初始化和释放时调用;filter_frame用于处理输入帧并生成输出帧。

2. 实现回调函数

接下来,需要实现AVFilter结构体中定义的回调函数。以下是一个简单的filter_frame回调函数示例:

static int custom_filter_filter_frame(AVFilterLink *inlink, AVFrame *in)
{
    AVFilterContext *ctx = inlink->dst;
    AVFilterLink *outlink = ctx->outputs[0];
    AVFrame *out = av_frame_clone(in);

    if (!out) {
        av_frame_free(&in);
        return AVERROR(ENOMEM);
    }

    // 在这里实现自定义的滤镜处理逻辑
    // ...

    av_frame_free(&in);
    return ff_filter_frame(outlink, out);
}

filter_frame回调函数负责处理输入帧in,并生成输出帧out。在这个例子中,我们简单地复制输入帧作为输出帧。您可以在此基础上实现自定义的滤镜处理逻辑。

3. 注册滤镜

最后,需要在FFmpeg的滤镜系统中注册自定义滤镜。在libavfilter/allfilters.c文件中添加以下代码:

extern AVFilter ff_vf_custom_filter;

然后,在register_all()函数中调用REGISTER_FILTER()宏来注册自定义滤镜:

REGISTER_FILTER(CUSTOM_FILTER, custom_filter, vf);

现在,您已经成功开发并注册了一个自定义滤镜。在实际应用中,请根据具体需求实现滤镜的处理逻辑,并确保在编译FFmpeg时包含自定义滤镜的代码。

d. 零拷贝解码和编码 (Zero-Copy Decoding and Encoding)

零拷贝技术是一种避免不必要的数据复制的方法,它可以提高音视频处理性能,特别是在硬件加速场景中。以下是使用零拷贝解码和编码的简要说明。

1. 解码过程

在解码过程中,零拷贝可以通过avcodec_receive_frame()直接获取硬件加速帧,而无需将帧数据从GPU内存复制到CPU内存。您可以使用以下代码获取硬件加速帧:

AVFrame *frame = av_frame_alloc();
int err = avcodec_receive_frame(codec_ctx, frame);
if (err >= 0) {
    if (frame->format == AV_PIX_FMT_CUDA) {
        // 处理硬件加速帧
    } else {
        // 处理非硬件加速帧
    }
}

2. 编码过程

在编码过程中,零拷贝可以通过将硬件加速帧传递给avcodec_send_frame()实现。首先,需要创建一个硬件加速帧:

AVFrame *frame = av_frame_alloc();
frame->format = AV_PIX_FMT_CUDA;
frame->width = width;
frame->height = height;

然后,使用av_hwframe_ctx_alloc()av_hwframe_get_buffer()为硬件加速帧分配内存:

AVBufferRef *hw_frames_ctx = av_hwframe_ctx_alloc(hw_device_ctx);
if (!hw_frames_ctx) {
    // 错误处理
}

AVHWFramesContext *hw_frames_ctx_data = (AVHWFramesContext *)hw_frames_ctx->data;
hw_frames_ctx_data->format = AV_PIX_FMT_CUDA;
hw_frames_ctx_data->sw_format = AV_PIX_FMT_NV12;
hw_frames_ctx_data->width = width;
hw_frames_ctx_data->height = height;
hw_frames_ctx_data->initial_pool_size = 20;

err = av_hwframe_ctx_init(hw_frames_ctx);
if (err < 0) {
    // 错误处理
}

frame->hw_frames_ctx = av_buffer_ref(hw_frames_ctx);

err = av_hwframe_get_buffer(frame->hw_frames_ctx, frame, 0);
if (err < 0) {
    // 错误处理
}

最后,将硬件加速帧发送到编码器进行编码:

err = avcodec_send_frame(codec_ctx, frame);
if (err < 0) {
    // 错误处理
}

通过上述方法,您可以在解码和编码过程中实现零拷贝操作,从而提高音视频处理性能。需要注意的是,零拷贝技术通常与硬件加速配合使用,因此请确保所选编解码器支持相应的硬件加速API。

e. 利用矩阵变换优化视频旋转和翻转 (Optimizing Video Rotation and Flip with Matrix Transformations)

在处理视频时,可能需要对视频进行旋转或翻转。传统的方法通常涉及对每个像素进行计算和移动,这在处理大量视频时可能会导致性能问题。利用矩阵变换,您可以在不影响性能的情况下实现视频旋转和翻转。

以下是一个使用FFmpeg的libavfilter库实现视频旋转和翻转的示例:

// 初始化滤镜图
AVFilterGraph *filter_graph = avfilter_graph_alloc();

// 定义输入滤镜
AVFilterContext *buffer_ctx;
const AVFilter *buffer = avfilter_get_by_name("buffer");
AVFilterInOut *input = avfilter_inout_alloc();
avfilter_graph_create_filter(&buffer_ctx, buffer, "in", "width=1280:height=720:pix_fmt=0:time_base=1/25", NULL, filter_graph);
input->filter_ctx = buffer_ctx;
input->pad_idx = 0;
input->next = NULL;

// 定义输出滤镜
AVFilterContext *buffersink_ctx;
const AVFilter *buffersink = avfilter_get_by_name("buffersink");
AVFilterInOut *output = avfilter_inout_alloc();
avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph);
output->filter_ctx = buffersink_ctx;
output->pad_idx = 0;
output->next = NULL;

// 添加旋转和翻转滤镜
const AVFilter *transpose = avfilter_get_by_name("transpose");
AVFilterContext *transpose_ctx;
avfilter_graph_create_filter(&transpose_ctx, transpose, "transpose", "dir=1", NULL, filter_graph);
avfilter_link(buffer_ctx, 0, transpose_ctx, 0);
avfilter_link(transpose_ctx, 0, buffersink_ctx, 0);

// 配置滤镜图
avfilter_graph_config(filter_graph, NULL);

在这个示例中,我们首先创建了一个滤镜图,然后添加了输入和输出滤镜。接着,我们添加了一个transpose滤镜来实现视频旋转和翻转。transpose滤镜的dir参数定义了旋转和翻转的方式:

  • dir=0: 顺时针旋转90度
  • dir=1: 逆时针旋转90度
  • dir=2: 顺时针旋转180度
  • dir=3: 逆时针旋转180度

通过这种方法,您可以高效地实现视频旋转和翻转操作,同时保持较高的性能。

这些高级技巧可以帮助您优化音视频处理过程,提高应用程序的性能。在实际开发中,请根据需求和目标硬件平台选择适当的技巧。

f. 利用FFmpeg实现实时音视频处理 (Real-time Audio and Video Processing with FFmpeg)

在某些应用场景下,如直播、实时监控等,需要实时处理音视频数据。FFmpeg作为一个强大的多媒体处理库,同样适用于实时音视频处理任务。以下是实现实时音视频处理的一些建议:

1. 减少延迟

为实现低延迟处理,可以尝试以下方法:

  • 选择低延迟编解码器(如H.264 Low-Latency模式)
  • 调整编解码器参数,降低延迟(如减少GOP大小、使用零延迟B帧等)
  • 使用硬件加速,提高编解码速度

2. 数据流处理

处理实时数据时,可以使用pipe协议将数据从一个阶段传输到下一个阶段。这可以减少数据在不同阶段之间的存储开销,降低处理延迟。

// 读取数据流
AVFormatContext *input_ctx = NULL;
avformat_open_input(&input_ctx, "pipe:0", NULL, NULL);

// 处理数据流
// ...

// 输出数据流
AVFormatContext *output_ctx = NULL;
avformat_alloc_output_context2(&output_ctx, NULL, NULL, "pipe:1");

3. 实时处理音频和视频

在处理实时音频和视频时,需要确保音频和视频的同步。使用AVFilter库,可以方便地将音频和视频数据引导到不同的滤镜,然后根据需求进行处理。处理后的音频和视频数据可以重新封装到一个输出流中。

4. 资源管理与错误处理

实时应用中,资源管理和错误处理尤为重要。要确保合理分配并释放内存,避免内存泄漏。同时,对于音视频处理过程中可能出现的错误,要有充分的错误处理机制,确保应用的稳定性。

综上所述,通过以上高级技巧和方法,可以利用FFmpeg实现实时音视频处理。在实际应用中,请根据具体需求选择合适的技术和策略,以实现最佳的性能和实时性。

Fmpeg各个库的底层原理

本章节将从源码层面详细介绍FFmpeg各个库的底层原理。FFmpeg主要包含以下几个核心库:libavformat, libavcodec, libavutil, libavfilter, libswscale, 和 libswresample。我们将按照这些库的顺序进行逐一介绍。

  1. libavformat

    libavformat 库负责处理各种多媒体容器格式。它为程序员提供了统一的API来读取、写入多种格式的音视频数据,并提供了多媒体文件的元信息,如码率、分辨率、时长等。

    1.1 解封装:libavformat通过注册各种封装格式的解封装器(demuxer),用于读取文件或流数据。每个解封装器负责识别容器格式,提取音视频帧并将其转换为统一的AVPacket结构。解封装器通常会根据容器格式的特点处理时间戳和同步问题。

    1.2 封装:封装器(muxer)的作用与解封装器相反,将音视频帧封装为特定的容器格式。libavformat支持多种封装器,可以根据需要选择合适的封装格式。封装器负责添加合适的时间戳和处理多路复用问题。

  2. libavcodec

    libavcodec 是 FFmpeg 中负责音视频编解码的核心库。它提供了统一的编解码 API,支持多种编解码器。

    2.1 编解码器注册:libavcodec 中的编解码器需要注册到全局编解码器列表,以便在处理音视频数据时可以被正确识别和调用。

    2.2 编码流程:编码器根据输入的音视频数据生成压缩后的数据流。音视频编码过程可能包括预处理、变换、量化、熵编码等步骤。每个编解码器可能具有不同的实现细节和优化方法。

    2.3 解码流程:解码器将压缩数据流还原为原始音视频数据。解码过程包括熵解码、反量化、逆变换等步骤。与编码过程类似,每个解码器的实现细节和优化方法也可能不同。

  3. libavutil

    libavutil 是 FFmpeg 的实用功能库,提供了一些基本数据结构、算法和辅助函数,以便在其他库中使用。

    3.1 基本数据结构:包括内存分配、引用计数、队列、字典等通用数据结构。

    3.2 常用算法:例如查找表、数学函数、比特操作等。

    3.3 辅助功能:例如日志、错误处理、选项解析等。

  4. libavfilter 是 FFmpeg 中负责音视频滤镜处理的库。滤镜可以对音视频数据进行各种处理和变换,如剪辑、缩放、旋转、叠加等。libavfilter 提供了灵活的滤镜图表达式和滤镜链机制,允许用户灵活地组合多个滤镜以实现复杂的处理任务。

    4.1 滤镜注册:滤镜需要注册到全局滤镜列表,以便在构建滤镜图时可以被正确识别和调用。

    4.2 滤镜链与滤镜图:滤镜链是指按顺序排列的滤镜集合,用于处理单个媒体流。滤镜图由多个滤镜链组成,可以处理复杂的音视频处理场景。

    4.3 滤镜上下文:每个滤镜在处理数据时,都有一个与之关联的滤镜上下文。滤镜上下文中包含了滤镜的状态、配置选项等信息。

  5. libswscale

    libswscale 是 FFmpeg 中负责图像尺寸缩放和颜色空间转换的库。它提供了高质量的图像处理算法,支持多种缩放算法和色彩空间。

    5.1 缩放算法:libswscale 支持诸如双线性、双三次、Lanczos 等多种缩放算法。用户可以根据需求和性能要求选择合适的缩放算法。

    5.2 色彩空间转换:libswscale 支持多种色彩空间的转换,如 YUV、RGB、XYZ 等。色彩空间转换对于视频播放、编码和处理过程中的颜色准确性至关重要。

  6. libswresample

    libswresample 是 FFmpeg 中负责音频采样率转换、声道重映射和格式转换的库。它支持高质量的音频处理算法,以满足各种音频处理需求。

    6.1 采样率转换:libswresample 支持多种采样率转换算法,如线性插值、多阶插值等。用户可以根据音质和性能要求选择合适的算法。

    6.2 声道重映射:libswresample 可以对音频数据进行声道重映射,实现立体声、环绕声等声道布局的转换。

    6.3 格式转换:libswresample 支持多种音频采样格式的转换,如整数、浮点、平面、交错等。

flash文件与ffmpeg

FFmpeg 可以解码 Flash 视频文件(FLV,以及部分 F4V 文件),而 Flash 文件通常指代的是以 .swf 为扩展名的文件,这类文件包含了音频、视频、矢量图形和 ActionScript 等多种元素。FFmpeg 并不支持直接解码 .swf 文件,因为这类文件的结构和内容远超出了 FFmpeg 的音频和视频处理范畴。

解码 Flash 视频文件(FLV)的原理:

  1. 文件结构:FLV 文件由一个文件头(Header)和若干个文件体(Body)组成,文件头包含了文件的元数据和版本信息等,文件体则包含了音频和视频数据。
  2. 解码过程:FFmpeg 根据 FLV 文件头的信息,识别文件的音频和视频编码格式(例如,Sorenson Spark、VP6、H.264 等视频编码,以及 MP3、AAC、Nellymoser 等音频编码)。然后,FFmpeg 使用相应的解码器对音频和视频数据进行解码。
  3. 输出:解码后的音频和视频数据可以根据需要进行进一步的处理(例如,转码、编辑等),或者转换为其他容器格式(例如,MP4、MKV等)进行输出。

然而,对于 .swf 文件,如果您需要提取其中的音频或视频内容,可以尝试使用第三方工具,例如 SWF Decompiler 等。这些工具可以解析 .swf 文件的结构,提取其中的音频、视频、图片等资源。提取出的音频或视频文件可以用 FFmpeg 进行进一步处理。

flv和swf文件的区别

FLV(Flash Video)和SWF(Shockwave Flash)都与Adobe Flash技术有关,但它们在背景、文件结构和播放方式等方面有很大的区别。下面分别从这几个角度对它们进行对比。

背景:

  1. FLV:FLV 是一种流行的网络视频格式,它由Adobe公司推出,主要用于在线视频播放。早期的YouTube、优酷等视频网站都采用了FLV格式来存储和传输视频。
  2. SWF:SWF 是一种由Macromedia公司开发的矢量图形动画格式,后被Adobe收购。SWF文件可以包含矢量图形、位图图像、音频、视频和脚本等多种元素,用于创建富媒体动画、游戏、应用程序等。

文件结构:

  1. FLV:FLV文件由文件头(Header)和若干个文件体(Body)组成。文件头包含了文件的元数据和版本信息等,文件体则包含了音频和视频数据。FLV文件主要支持Sorenson Spark、VP6、H.264等视频编码以及MP3、AAC、Nellymoser等音频编码。
  2. SWF:SWF文件具有复杂的结构,可以包含矢量图形、位图图像、音频、视频和脚本等多种元素。SWF文件中的元素通过标签(Tags)组织,每个标签对应一种类型的资源或操作。SWF文件还可以包含ActionScript脚本来实现交互功能。

播放方式:

  1. FLV:FLV文件可以通过Adobe Flash Player插件或其他支持FLV格式的播放器进行播放。在现代Web技术中,随着HTML5的普及,FLV逐渐被MP4(H.264/AAC)等格式替代。
  2. SWF:SWF文件可以通过Adobe Flash Player插件、独立的Flash播放器或其他兼容SWF格式的播放器进行播放。随着Flash技术的逐渐淘汰,许多现代浏览器已经不再支持Flash Player插件,因此SWF文件在Web环境中的应用越来越少。

综上所述,FLV和SWF虽然都与Adobe Flash技术相关,但它们在用途、文件结构和播放方式等方面有显著区别。FLV主要用于在线视频播放,而SWF则用于创建富媒体动画、游戏和应用程序。随着Web技术的发展,FLV和SWF格式都逐渐被HTML5相关技术所取代。

其他不支持的格式

虽然 FFmpeg 支持众多的音频、视频和容器格式,但仍然有一些常见的媒体格式不受 FFmpeg 支持。以下是一些 FFmpeg 不支持的常见类型媒体格式:

  1. DRM 保护的媒体文件:许多音视频平台为了保护版权,对媒体文件加入了 DRM(Digital Rights Management,数字版权管理)技术。FFmpeg 无法解码这些 DRM 保护的媒体文件,如 iTunes 中的 M4V 文件、Amazon Video 等。
  2. RealMedia 格式:RealMedia 是一种由 RealNetworks 开发的流媒体格式,包括 RealAudio(.ra)、RealVideo(.rv)和 RealMedia 容器(.rm)。尽管 FFmpeg 可以处理一些 RealMedia 格式的文件,但对于部分编码器的支持不完全,可能导致解码失败或质量损失。
  3. Windows Media Photo / HD Photo / JPEG XR:这是一种由微软开发的图像格式,旨在替代 JPEG 格式。尽管它具有一定的技术优势,但在实际应用中并未得到广泛采用。FFmpeg 并不支持这种图像格式。
  4. Silverlight:Silverlight 是微软开发的一种用于创建和播放富媒体应用程序和流媒体的技术。它与 Adobe Flash 类似,但在市场份额上较小。FFmpeg 并不支持处理 Silverlight 内容。
  5. 一些编解码器专有格式:部分专有格式由于授权问题或技术原因,可能不受 FFmpeg 支持,例如一些数字摄像头或编辑软件使用的特定编解码器。

需要注意的是,FFmpeg 是一个持续更新和发展的开源项目,可能会随着版本更新而支持新的媒体格式。如果您需要处理 FFmpeg 不支持的媒体格式,可以尝试使用其他专门针对该格式的工具或转换软件。在处理这些格式时,请确保遵循相关的法律和版权规定。

优化ffmpeg的方法

优化解码

优化FFmpeg解码步骤可以提高解码性能,以下是一些建议:

  1. 使用多线程解码:
    • 部分解码器支持多线程解码,这可以显著提高解码性能。在设置解码器上下文时,指定线程数。例如:decoder_context->thread_count = thread_num;,其中thread_num是您希望使用的线程数。
    • 根据您的硬件资源和需求选择合适的线程数。通常,线程数与CPU核心数相同或略高。
  2. 利用硬件加速:
    • 如果您的硬件支持硬件解码(如H.264、HEVC等),使用硬件加速解码可以大幅提高性能。FFmpeg支持多种硬件解码器,如NVIDIA NVDEC/CUVID、Intel Quick Sync Video、AMD AMF等。
    • 使用硬件解码器时,需要先初始化相应的硬件上下文,然后将其传递给解码器。具体实现取决于硬件类型和平台。
  3. 优化内存分配和数据拷贝:
    • 避免不必要的内存分配和释放,尤其是在解码循环中。可以使用内存池或对象池减少内存分配的开销。
    • 减少数据拷贝,尽可能在原始缓冲区上进行操作。如果需要拷贝,请考虑使用SIMD指令集进行优化。
  4. 优化图像格式转换和缩放:
    • 在解码后,可能需要对图像进行格式转换或缩放。使用FFmpeg的libswscale库进行高效的图像处理。选择合适的缩放算法和色彩空间转换算法,以平衡性能和图像质量。
    • 如果可能,请尽量避免不必要的格式转换和缩放操作。
  5. 优化I/O操作:
    • I/O操作(如文件读取、网络传输)可能会影响解码性能。尝试使用异步I/O、缓冲区和多线程处理I/O操作,以减少解码过程中的阻塞。
  6. 代码优化和性能分析:
    • 使用代码分析工具(如Valgrind、gprof等)分析解码过程中的性能瓶颈,优化关键部分。
    • 使用编译器优化选项,如-O2-O3,生成高效的目标代码。
  7. 参考FFmpeg官方文档和示例:
    • 阅读FFmpeg官方文档,了解最佳实践和性能优化技巧。
    • 参考FFmpeg官方示例和社区资源,学习其他人的经验和技巧。

通过上述方法优化FFmpeg解码步骤

优化编码

优化FFmpeg编码步骤可以提高编码性能和输出质量。以下是一些建议:

  1. 使用多线程编码:
    • 部分编码器支持多线程编码,这可以显著提高编码性能。在设置编码器上下文时,指定线程数。例如:encoder_context->thread_count = thread_num;,其中thread_num是您希望使用的线程数。
    • 根据您的硬件资源和需求选择合适的线程数。通常,线程数与CPU核心数相同或略高。
  2. 利用硬件加速:
    • 如果您的硬件支持硬件编码(如H.264、HEVC等),使用硬件加速编码可以大幅提高性能。FFmpeg支持多种硬件编码器,如NVIDIA NVENC、Intel Quick Sync Video、AMD AMF等。
    • 使用硬件编码器时,需要先初始化相应的硬件上下文,然后将其传递给编码器。具体实现取决于硬件类型和平台。
  3. 选择合适的编码参数:
    • 合适的编码参数对于编码性能和输出质量至关重要。选择合适的码率、分辨率、GOP大小、帧率等参数,以平衡性能和质量。
    • 为不同场景选择合适的编码预设,例如,实时场景可以选择较低的编码复杂度,而离线场景可以选择较高的编码复杂度。
  4. 优化内存分配和数据拷贝:
    • 避免不必要的内存分配和释放,尤其是在编码循环中。可以使用内存池或对象池减少内存分配的开销。
    • 减少数据拷贝,尽可能在原始缓冲区上进行操作。如果需要拷贝,请考虑使用SIMD指令集进行优化。
  5. 优化图像格式转换和缩放:
    • 在编码前,可能需要对图像进行格式转换或缩放。使用FFmpeg的libswscale库进行高效的图像处理。选择合适的缩放算法和色彩空间转换算法,以平衡性能和图像质量。
    • 如果可能,请尽量避免不必要的格式转换和缩放操作。
  6. 优化I/O操作:
    • I/O操作(如文件写入、网络传输)可能会影响编码性能。尝试使用异步I/O、缓冲区和多线程处理I/O操作,以减少编码过程中的阻塞。

Qt与ffmpeg

Qt是一个跨平台的应用程序开发框架,主要用于开发图形界面程序,而FFmpeg是一套功能强大的音视频处理库。尽管两者在设计和功能上有很大差异,但它们可以结合使用,为用户提供一个功能丰富的多媒体应用程序。

Qt和FFmpeg可以搭配使用的接口主要包括以下几个方面:

  1. 播放器控件:Qt提供了QMediaPlayer和QVideoWidget控件,可以将FFmpeg解码后的视频帧显示出来。
#include 
#include 

// 初始化播放器和视频控件
QMediaPlayer *player = new QMediaPlayer;
QVideoWidget *videoWidget = new QVideoWidget;

player->setVideoOutput(videoWidget);
videoWidget->show();

// 使用FFmpeg解码音视频流并传递给QMediaPlayer播放
// ...

绘图与图像处理:Qt提供了QPainter和QImage类,可以实现对FFmpeg解码后的图像帧进行绘制和处理。
#include 
#include 

// 将FFmpeg解码后的帧数据转换为QImage
AVFrame *frame = // 使用FFmpeg获取的帧
QImage image(frame->data[0], frame->width, frame->height, frame->linesize[0], QImage::Format_RGB32);

// 使用QPainter绘制图像
QPainter painter(this);
painter.drawImage(0, 0, image);

  1. 音频处理与播放:Qt提供了QAudioOutput类,可以将FFmpeg解码后的音频数据播放出来。
#include 
#include 

// 设置音频格式
QAudioFormat format;
format.setSampleRate(44100);
format.setChannelCount(2);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);

// 创建音频输出对象
QAudioOutput *audioOutput = new QAudioOutput(format, this);

// 获取音频设备
QIODevice *audioDevice = audioOutput->start();

// 使用FFmpeg解码音频数据并写入audioDevice播放
// ...

  1. 实时音视频处理与推流:结合Qt的网络模块(如QTcpSocket、QUdpSocket、QNetworkAccessManager等)和FFmpeg,可以实现实时音视频处理和推流功能。
#include 
#include 

// 创建网络连接
QTcpSocket *socket = new QTcpSocket();
socket->connectToHost("example.com", 1234);

// 使用FFmpeg处理音视频数据
// ...

// 将处理后的音视频数据发送到服务器
socket->write(processedData);

// 关闭连接
socket->close();
  1. 视频编辑功能:将FFmpeg与Qt的图形界面和多媒体组件相结合,可以实现一些视频编辑功能,如剪辑、合并、添加滤镜效果等。
#include 
#include 

// 创建QGraphicsScene和QGraphicsVideoItem显示FFmpeg处理后的视频帧
QGraphicsScene *scene = new QGraphicsScene(this);
QGraphicsVideoItem *videoItem = new QGraphicsVideoItem;
scene->addItem(videoItem);

// 创建QSlider调整视频播放位置
QSlider *slider = new QSlider(Qt::Horizontal);
connect(slider, &QSlider::valueChanged, this, [this](int value) {
    // 使用FFmpeg跳转到指定时间点并刷新视频帧
    // ...
});

// 添加视频编辑操作按钮(如剪辑、合并、添加滤镜效果等)
// ...
  1. 视频录制与截图:利用FFmpeg对音视频数据进行编码和封装,以及Qt的图形界面和多媒体组件,可以实现视频录制和截图功能。
#include 
#include 

// 使用Qt获取屏幕截图
QScreen *screen = QGuiApplication::primaryScreen();
QPixmap pixmap = screen->grabWindow(0);

// 将QPixmap转换为FFmpeg可处理的数据格式
// ...

// 使用FFmpeg对截图进行编码和封装,保存为视频或图片文件
// ...

通过上述接口,您可以将Qt和FFmpeg搭配使用,实现音视频播放、实时音视频处理与推流、视频编辑、视频录制与截图等功能。在实际开发过程中,请根据您的需求选择合适的接口和技术,构建功能丰富的多媒体应用程序。

调试FFmpeg的方法

调试FFmpeg代码需要一定的经验和技巧。以下是一些建议,可以帮助您更有效地调试FFmpeg代码:

  1. 使用调试器:
    • 对于C/C++开发,通常使用GDB(GNU调试器)或LLDB(LLVM调试器)来调试代码。对于Windows平台,可以使用Visual Studio的调试器。
    • 设置断点,逐步执行代码,观察变量的值和程序的执行路径。
    • 使用调试器的内存分析功能,检测内存泄漏或其他内存问题。
  2. 查看FFmpeg日志:
    • FFmpeg提供了丰富的日志信息,可以帮助您定位问题。要启用日志,可以使用av_log_set_level()函数设置日志级别,例如av_log_set_level(AV_LOG_DEBUG)
    • 在代码中,您可以使用av_log()函数输出自定义日志,以便了解程序执行过程。
  3. 阅读FFmpeg文档和源代码:
    • FFmpeg官方文档提供了API的详细信息和使用指南。在调试过程中,确保您正确理解和使用API。
    • 阅读FFmpeg源代码可以帮助您了解内部实现细节,以便更好地定位问题。在遇到问题时,参考相关模块的源代码是一种有效的方法。
  4. 使用简化的测试用例:
    • 编写简单的测试用例,用于验证您的代码和FFmpeg API。这可以帮助您分离问题,更快地定位错误。
    • 使用不同类型的音视频文件进行测试,以确保您的代码兼容各种格式。
  5. 使用代码分析和性能分析工具:
    • 使用静态代码分析工具(如Clang-Tidy、cppcheck等)检查潜在的代码问题。
    • 使用性能分析工具(如Valgrind、gprof等)分析代码的性能瓶颈,优化关键部分。
  6. 参考社区资源:
    • 搜索FFmpeg邮件列表、论坛、GitHub Issues等,查找类似问题的解决方案。
    • 向FFmpeg社区提问,分享您的问题和经验,获取他人的帮助和建议。

调试FFmpeg代码需要时间和耐心。通过学习和实践,您将逐渐积累经验,掌握调试技巧。

FFmpeg与其他框架的对比

FFmpeg是一个非常流行和功能强大的开源多媒体框架,它提供了一组用于处理音频、视频和图像的工具和库。与其他多媒体框架相比,FFmpeg具有以下特点:

  1. 功能强大:FFmpeg支持大量的音视频编解码器、容器格式和协议,可以处理几乎所有常见的多媒体文件。它还提供了丰富的音视频滤镜,用于裁剪、缩放、叠加等操作。
  2. 高性能:FFmpeg针对各种处理器架构进行了优化,可以在不同平台上实现高性能的音视频处理。
  3. 跨平台:FFmpeg可以在多种操作系统和硬件平台上运行,如Windows、Linux、macOS、Android和iOS等。
  4. 社区支持:FFmpeg拥有活跃的开发者社区,定期发布更新和修复。此外,许多其他开源项目也基于FFmpeg,如VLC、HandBrake等。

与FFmpeg相比,其他多媒体框架可能有以下特点:

  1. GStreamer:这是一个用于音频和视频流处理的跨平台库,其特点是基于插件的架构。与FFmpeg相比,GStreamer更注重实时流媒体应用,如视频会议、广播等。GStreamer的API比FFmpeg更高级,但它支持的编解码器和格式相对较少。
  2. DirectShow(Windows):这是微软提供的一种多媒体框架,用于在Windows平台上处理音频和视频。DirectShow易于集成到Windows应用程序中,但它的功能和性能可能不如FFmpeg。
  3. AVFoundation(macOS/iOS):这是苹果提供的一种多媒体框架,用于在macOS和iOS平台上处理音频和视频。与DirectShow类似,AVFoundation易于集成到苹果应用程序中,但其功能和性能可能不如FFmpeg。
  4. OpenCV:这是一个用于计算机视觉应用的开源库,也提供了一些音视频处理功能。与FFmpeg相比,OpenCV更专注于图像处理和计算机视觉任务,如图像识别、追踪等。如果您的项目主要涉及计算机视觉,可以考虑使用OpenCV。

总之,FFmpeg是一个功能强大、性能优秀的多媒体框架。根据您的项目需求和目标平台,可以与其他多媒体框架进行比较,选择最适合您的解决方案。

FFmpeg的优点和局限性

FFmpeg 是一个强大且广泛使用的开源音视频处理库,具有许多优势:

  1. 功能丰富:FFmpeg 提供了大量的音视频处理功能,包括解码、编码、转码、裁剪、合并、滤镜、流媒体处理等。这使得 FFmpeg 成为许多音视频处理任务的理想选择。
  2. 跨平台兼容性:FFmpeg 支持各种操作系统,如 Windows、macOS、Linux 等,可以在多种平台上进行编程,无需担心平台兼容性问题。
  3. 广泛的格式支持:FFmpeg 支持大量的音频、视频和容器格式,包括常见的 MP4、MKV、MOV、MP3、AAC 等,以及许多不太常见的格式。这使得 FFmpeg 能够处理几乎所有类型的音视频文件。
  4. 高性能:FFmpeg 的性能经过优化,可提供高速的音视频处理。此外,FFmpeg 还支持硬件加速(例如,NVIDIA NVENC、Intel QSV、AMD VAAPI 等),进一步提高处理速度。
  5. 灵活性:FFmpeg 提供了灵活的 API,允许开发者根据需要进行定制和扩展。这意味着您可以使用 FFmpeg 构建符合您特定需求的音视频处理应用。
  6. 活跃的社区支持:FFmpeg 是一个活跃的开源项目,拥有庞大的开发者社区。这使得在遇到问题时,您很可能会找到现成的解决方案,或者在社区中获得帮助。
  7. 免费和开源:FFmpeg 是一个免费和开源的项目,这意味着您可以免费使用、修改和分发它。这对于许多开发者和企业来说是一个巨大的优势。

尽管 FFmpeg 具有许多优势,但它也存在一些局限性:

  1. 学习曲线陡峭:FFmpeg 的 API 和命令行工具功能丰富且强大,但对于初学者来说,学习和掌握它们可能需要花费较长时间。文档有时可能不够详细或难以理解,这可能使学习过程更具挑战性。
  2. 不支持一些特定格式:虽然 FFmpeg 支持大量的音视频格式,但仍然有一些常见的媒体格式不受支持,如 DRM 保护的媒体文件、部分专有格式等。在处理这些格式时,您可能需要寻找其他工具或解决方案。
  3. 版权和许可问题:FFmpeg 使用了许多编解码器,这些编解码器可能受到专利和许可限制。在商业环境中使用 FFmpeg 时,可能需要确保遵守相关的法律法规和版权要求。
  4. 缺乏图形用户界面:FFmpeg 本身是一个命令行工具和库,缺乏直观的图形用户界面。虽然有一些第三方项目为 FFmpeg 提供了图形界面,但这些项目可能不如 FFmpeg 本身功能齐全或更新及时。
  5. 不适合实时应用:虽然 FFmpeg 性能优异,但它可能不是实时音视频处理应用的最佳选择。实时音视频处理对延迟和稳定性要求很高,而 FFmpeg 主要专注于离线处理。
  6. 对于新手不够友好:FFmpeg 的复杂性和学习曲线可能使其对新手不够友好,尤其是那些没有音视频处理经验的开发者。对于这些开发者来说,可能需要寻找更简单易用的替代方案。

基于FFmpeg 的个人项目

使用 FFmpeg 和 C++,您可以实现许多有趣的个人项目,涉及音视频处理、编辑、转码等。以下是一些项目建议:

  1. 视频转码器:使用 FFmpeg 编写一个简单的视频转码器,可以将各种输入格式(例如 AVI、MOV、MKV 等)转换为指定的输出格式(例如 MP4)。您可以添加参数以设置输出视频的分辨率、比特率、帧率等。
  2. 媒体播放器:创建一个基于 FFmpeg 的简单媒体播放器,用于播放音频和视频文件。您可以使用 SDL、Qt 或其他图形库构建播放器的用户界面。
  3. 视频编辑器:编写一个简单的视频编辑器,实现基本的剪辑、合并、旋转和缩放功能。您还可以添加音频轨道处理和滤镜效果(如黑白、模糊等)。
  4. 视频监控系统:使用 FFmpeg 和摄像头或网络摄像头捕捉实时视频流,并将其保存到本地或云存储。您还可以实现基本的运动检测功能,以便在检测到运动时触发警报。
  5. 视频格式分析器:编写一个工具,可以分析视频文件的详细信息,如编解码器、分辨率、帧率、比特率等。此外,您还可以检测视频的关键帧、GOP 结构等。
  6. 字幕提取和合并:创建一个工具,可以从视频文件中提取字幕轨道(例如 SRT、ASS 等格式),并将其合并到另一个视频文件中。您可以提供字幕轨道的选择和调整字幕延迟等功能。
  7. 视频缩略图生成器:编写一个工具,可以从视频文件中自动生成缩略图。用户可以指定缩略图的数量、尺寸和输出格式(如 JPEG、PNG 等)。
  8. 视频截图工具:创建一个简单的工具,可以在指定时间点或间隔截取视频帧,并将其保存为图像文件。
  9. 音频转码器:编写一个音频转码器,可以将各种输入音频格式(例如 MP3、WAV、AAC 等)转换为指定的输出格式。您可以添加参数以设置输出音频的采样率、比特率等。
  10. 实时流媒体服务器:创建一个基于 FFmpeg 的实时流媒体服务器,可以将音视频流发送到客户端。您可以使用 RTMP、HLS、DASH 等流媒体协议。

这些只是一些基于 FFmpeg 的个人项目建议。您可以根据自己的兴趣和需求扩展或组合这些项目。

FFmpeg 搭配SDL

FFmpeg 是一个强大的多媒体处理库,主要用于编解码、转码、滤镜等操作。然而,FFmpeg 本身并不提供图形界面以及音频和视频的渲染功能。因此,当开发一个视频播放器时,需要借助其他库,如 SDL,来实现图形界面和多媒体渲染。

SDL(Simple DirectMedia Layer)是一个跨平台的多媒体库,提供了对音频、视频、输入设备和图形渲染的底层访问。因此,在开发视频播放器时,结合 FFmpeg 和 SDL 可以实现一个完整的多媒体播放解决方案。

使用 FFmpeg + SDL 的好处如下:

  1. 跨平台支持:SDL 和 FFmpeg 都是跨平台的库,可以在各种操作系统(如 Windows、macOS、Linux 等)上运行。这意味着您的视频播放器可以轻松地移植到其他平台。
  2. 高性能:SDL 和 FFmpeg 都经过了优化,以实现高性能的音频和视频播放。这使得您的播放器能够实现流畅的多媒体体验。
  3. 多种格式支持:FFmpeg 支持大量的音频和视频编解码器,使得您的播放器可以播放各种格式的多媒体文件。
  4. 易于集成:SDL 和 FFmpeg 的 API 设计得相对简单,使得它们可以轻松集成到您的项目中。此外,许多现有的教程和示例代码可以帮助您快速入门。
  5. 丰富的功能:通过结合 FFmpeg 和 SDL,您可以实现许多高级功能,如实时流媒体、硬件加速解码、多种滤镜效果等。

综上所述,虽然单独使用 FFmpeg 可以处理音频和视频数据,但要实现一个完整的视频播放器,还需要借助 SDL 等库来提供图形界面和多媒体渲染功能。这样的组合可以让您充分利用这两个库的优势,实现高性能、跨平台和功能丰富的视频播放器。

FFmpeg 和 SDL 在音频处理方面的职责和功能是不同的。FFmpeg 主要负责编解码、转码和处理音频数据,而 SDL 主要负责音频的播放和渲染。在制作音频播放器时,FFmpeg 用于解码音频文件并提供音频数据,SDL 用于设置音频设备并播放解码后的音频。

当您在播放器中使用 SDL 设置音频属性时,您实际上是在配置音频设备的播放参数。例如,您可以设置采样率、声道数、音频缓冲区大小等。这些设置影响到音频设备如何播放音频,以及音频播放的性能和质量。

而当您使用 FFmpeg 设置音频属性时,您通常是在处理音频文件。例如,您可以更改音频的采样率、比特率或声道布局等。这些设置主要用于转码和处理音频文件,而不是直接影响到音频的播放。

音频播放器通常使用 SDL 设置音频属性,因为它们需要控制音频设备的播放参数。FFmpeg 提供的音频数据是解码后的,播放器需要使用 SDL(或其他音频库)将这些数据播放出来。在这种情况下,播放器需要确保音频设备的设置与解码后的音频数据匹配,以获得正确的播放效果。

SDL 音频并不支持比 FFmpeg 更多的内容,它们只是在音频处理中扮演了不同的角色。FFmpeg 负责处理音频文件,提供丰富的编解码器支持和音频处理功能,而 SDL 负责播放音频,提供跨平台的音频设备支持。在音频播放器中,这两者需要结合使用,以实现完整的音频播放功能。

结语

在过去的学习过程中,我们已经了解了FFmpeg的基本原理和使用方法。现在让我们从心理学的角度来为我们的FFmpeg编程学习做个结语。

  1. 动机:学习FFmpeg编程的动机非常重要。正如心理学所强调的,保持内在动机(例如兴趣、满足感)可以帮助我们更有效地学习。了解自己学习FFmpeg编程的目的,这将有助于提高学习效果。
  2. 自我效能:信心对学习过程至关重要。心理学上的自我效能是指一个人相信自己能够成功完成任务的信念。在学习FFmpeg编程时,不断积累经验和成果,以增强自己的信心和自我效能。
  3. 学习策略:心理学研究表明,有效的学习策略可以帮助我们更好地吸收和记忆知识。在FFmpeg编程学习中,可以采用分阶段学习、定期回顾、实践操作等策略,提高学习效果。
  4. 情绪管理:情绪对学习的影响不容忽视。学习FFmpeg编程时可能会遇到挫折和困难,应学会调整心态,保持积极乐观,以克服挑战。
  5. 社会支持:心理学研究表明,来自他人的支持对学习有很大帮助。与他人分享学习经验、参与社群讨论或寻求导师指导,都有助于提高FFmpeg编程学习的效果。

总之,从心理学角度看,保持内在动机、建立自我效能、采用有效学习策略、管理情绪以及寻求社会支持等因素都对FFmpeg编程学习过程具有重要意义。将这些心理学原理应用于实际学习过程中,可以帮助我们更好地掌握FFmpeg编程技能。

你可能感兴趣的:(C/C++,编程世界:,探索C/C++的奥妙,ffmpeg,音视频,c语言,开发语言,c++)