☰
FFmpeg 开源多媒体处理框架深度解析:从命令行到 libav* 全家桶
2026/10/13 10:19:09 网站建设 项目流程

适用人群:C/C++ 开发者、音视频处理入门到进阶、工业数采项目中需要音视频能力的边缘设备开发者 技术栈:C 语言实现,提供 C API 与命令行双形态


1. 背景

视频格式的"混乱"是历史常态:同一段视频由封装格式(MP4/MKV/FLV/TS...)和编码格式(H.264/H.265/VP9/AV1...)两个正交维度描述,再加上音频编码(AAC/MP3/Opus...)、字幕、流媒体协议(RTMP/HLS/DASH...)的排列组合,单靠手写解析器维护一个项目几乎是灾难。

FFmpeg 之所以成为"多媒体领域的事实标准",在于它用一个统一框架吃下了这条长尾:

  • 全格式覆盖:数百种解封装/封装格式、编解码器、滤镜,ffmpeg -formats 一屏都列不完;
  • 跨平台:纯 C 编写,Windows/Linux/macOS/Android/iOS/嵌入式全平台可编译;
  • 双形态:命令行 ffmpeg 适合快速验证与批处理;libavcodec / libavformat / libavfilter 等 C API 适合深度集成——OBS、VLC、Chrome、Android、几乎所有视频播放器的底层都在用它;
  • 硬件加速生态:NVENC/NVDEC、VAAPI、QSV、VideoToolbox、MediaCodec、AMF 全覆盖。

对工业数采场景而言,FFmpeg 的价值同样直接:摄像头/网络流(RTSP)取流 → 转码/抽帧 → 本地落盘或推流上云,是一条完整的"视觉数据采集"链路,与传统的传感器点位数采互补。

2. 架构全景

FFmpeg 由一套共享基础设施 + 多个功能库 + 三个命令行工具组成。

2.1 六大核心库

库全称职责开发者最常用 API
libavutilAV Util基础工具:内存、日志、数学、像素/采样格式、时间基、AVFrame/AVPacket/AVBufferav_frame_alloc/ref/unref、av_log、av_rescale_q、AVRational
libavcodecAV Codec编解码器核心:H.264/H.265/VP9/AV1/AAC/MP3... 与硬件加速avcodec_send_packet/receive_frame
libavformatAV Format解封装/封装/协议:MP4/MKV/FLV/TS/RTSP/HLS...avformat_open_input/read_frame/write_frame
libavfilterAV Filter滤镜图:缩放/裁剪/水印/转场/音频 EQ...avfilter_graph_*、av_buffersrc_add_frame
libswscaleSW Scale像素格式与尺寸转换(CPU)sws_getContext/sws_scale
libswresampleSW Resample音频重采样与声道/采样率转换swr_alloc/swr_convert

2.2 三件套工具

  • ffmpeg:转码/转封装/滤镜/推流主工具;
  • ffprobe:探针工具,读取媒体文件/流的元信息(格式、编码、时长、帧率、码率),调试时第一选择;
  • ffplay:极简播放器,快速预览。

2.3 构建与安装

# Windows(推荐从 gyan.dev 或 BtbN 下载预编译包,加入 PATH) ffmpeg -version # Ubuntu sudo apt install ffmpeg # 从源码构建(最小可用) ./configure --enable-shared --disable-everything --enable-decoder=h264 --enable-demuxer=mov --enable-parser=h264 make -j$(nproc)

提示:正式集成建议用 --disable-everything 按需裁剪组件,二进制体积可从 100+MB 降到几 MB,这是嵌入式设备的标准做法。


3. 核心概念

这几个概念不搞懂,写 C 代码必踩坑,是 FFmpeg 的"心智门槛"。

3.1 封装格式 vs 编码格式

  • 编码(Codec):像素/采样数据如何压缩。H.264 是编码器,压缩的是帧。
  • 封装(Container):压缩后的数据如何打包成文件/流。MP4 是容器,描述的是"H.264 视频轨 + AAC 音频轨 + 元数据"怎么排布。

ffprobe 输出中 codec_name 是编码,format_name 是容器:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input.mp4': Duration: 00:00:10.00, bitrate: 1000 kb/s Stream #0:0: Video: h264 (High) ... ← 编码 Stream #0:1: Audio: aac (LC) ... ← 编码

上面的 mov,mp4,... 就是容器。

3.2 时间基(Time Base)与 AVRational

FFmpeg 不用浮点秒表示时间,而是用有理数时间基AVRational { num, den },即每秒 den 个 tick、每 tick 为 num/den 秒。

AVRational tb = {1, 90000}; // 90000Hz 时钟,常见于 MPEG-TS double seconds = pts * av_q2d(tb); // pts 数值 × 1/90000 = 秒

关键坑:不同层的时间基不一样——容器层(AVStream.time_base)、编解码器层(AVCodecContext.time_base)、滤镜层各有各的基。跨层换算必须用 av_rescale_q(),禁止直接乘除。

// 把 AVPacket.pts(stream 时间基)换算成毫秒 int64_t ms = av_rescale_q(pkt->pts, stream->time_base, (AVRational){1, 1000});

3.3 PTS / DTS

  • PTS(Presentation Timestamp):帧显示时间;
  • DTS(Decode Timestamp):帧解码顺序时间。

B 帧存在时两者不同(I/P 帧先解、B 帧后解但先显示),因此DTS 单调不减,PTS 不一定。B 帧常见于 H.264 High Profile;流式转码如不需要 B 帧可用 -bf 0。

3.4 滤镜图(Filter Graph)

滤镜 = 对帧做变换的节点,节点连成有向图。最简单的图是 scale=1280:720,复杂图如:

input(视频) → scale → watermark → output input(音频) → volume → output

滤镜图在 libavfilter 中由 AVFilterGraph、AVFilterContext(节点)、AVFilterLink(边)表示,数据通过 av_buffersrc_add_frame(进)与 av_buffersink_get_frame(出)流动。命令行里用逗号(同一链)和分号(并行链)分隔。

3.5 AVFrame / AVPacket 与引用计数

  • AVPacket:编码后的数据(压缩字节),一头在解封装侧、一头在编码侧;
  • AVFrame:解码后/编码前的数据(原始像素/采样),或解码器输出的 YUV 帧。

两者都不是"一次性 malloc 的结构",而是引用计数的:av_frame_ref() 只增加引用不拷贝数据,av_frame_unref() 释放本引用,数据在最后一个引用释放时才真正回收

AVFrame *dst = av_frame_alloc(); av_frame_ref(dst, src); // 浅拷贝 + 引用 +1 // ... 使用 dst ... av_frame_unref(dst); // 引用 -1,src 仍可用

永远不要手工 free 帧数据,这是内存管理的第一铁律。


4. API 说明

4.1 解封装(libavformat)核心 API

// 1. 打开输入 AVFormatContext *fmt_ctx = NULL; if (avformat_open_input(&fmt_ctx, url, NULL, NULL) < 0) { /* 失败 */ } // 2. 读取流信息(流数量、时长、元数据) avformat_find_stream_info(fmt_ctx, NULL); // 3. 遍历流,找到视频流 int v_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); // 4. 循环读取 AVPacket AVPacket *pkt = av_packet_alloc(); while (av_read_frame(fmt_ctx, pkt) >= 0) { if (pkt->stream_index == v_idx) { /* 处理视频包 */ } av_packet_unref(pkt); } // 5. 收尾 avformat_close_input(&fmt_ctx); av_packet_free(&pkt);

4.2 解码(libavcodec)核心 API

新版 API 采用send/receive 推拉模型:发送一个压缩包,循环接收零到多个帧(解码器有内部缓冲,可能一次输入产出多帧、也可能需要多包才能出一帧)。

const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264); AVCodecContext *dec_ctx = avcodec_alloc_context3(codec); // 关键:把流的参数(SPS/PPS、宽高、时间基)拷给解码器 avcodec_parameters_to_context(dec_ctx, stream->codecpar); avcodec_open2(dec_ctx, codec, NULL); // 推拉循环 avcodec_send_packet(dec_ctx, pkt); // 输入压缩包 while (avcodec_receive_frame(dec_ctx, frame) >= 0) { // 拿到原始帧:frame->data[0..3]、frame->linesize[0..3](YUV420 为例) // frame->pts 是解码器时间基下的显示时间 }

avcodec_receive_frame 返回值语义:

  • 0:拿到一帧,继续循环;
  • AVERROR(EAGAIN):当前输入还不够出帧,需要继续喂包;
  • AVERROR_EOF:输入结束,解码器内部缓冲已全部冲刷。

4.3 编码(libavcodec)核心 API

const AVCodec *enc = avcodec_find_encoder(AV_CODEC_ID_H264); AVCodecContext *enc_ctx = avcodec_alloc_context3(enc); enc_ctx->width = 1280; enc_ctx->height = 720; enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P; enc_ctx->time_base = (AVRational){1, 25}; // 帧率 25fps enc_ctx->framerate = (AVRational){25, 1}; enc_ctx->bit_rate = 2'000'000; // 2Mbps // x264 私有参数(通过字典传递) av_opt_set(enc_ctx->priv_data, "preset", "fast", 0); avcodec_open2(enc_ctx, enc, NULL); // 推拉模型:送原始帧,收压缩包 avcodec_send_frame(enc_ctx, frame); while (avcodec_receive_packet(enc_ctx, pkt) >= 0) { // pkt 为编码后的压缩数据,可写入容器 }

4.4 封装输出(libavformat)核心 API

AVFormatContext *out_ctx = NULL; avformat_alloc_output_context2(&out_ctx, NULL, NULL, "out.mp4"); // 创建视频流 AVStream *out_stream = avformat_new_stream(out_ctx, NULL); avcodec_parameters_from_context(out_stream->codecpar, enc_ctx); // 打开输出 avio_open(&out_ctx->pb, "out.mp4", AVIO_FLAG_WRITE); avformat_write_header(out_ctx, NULL); // 写包(注意包的时间基要换算到流时间基) pkt->stream_index = out_stream->index; av_packet_rescale_ts(pkt, enc_ctx->time_base, out_stream->time_base); av_interleaved_write_frame(out_ctx, pkt); // 交错写帧,自动排序 // 收尾 av_write_trailer(out_ctx); avio_closep(&out_ctx->pb); avformat_free_context(out_ctx);

4.5 滤镜(libavfilter)最小骨架

// 1. 建图 AVFilterGraph *graph = avfilter_graph_alloc(); // 2. 建输入节点(buffer) AVFilterContext *src_ctx = NULL; avfilter_graph_create_filter(&src_ctx, avfilter_get_by_name("buffer"), "in", "video_size=1280x720:pix_fmt=yuv420p:time_base=1/25:frame_rate=25/1", NULL, graph); // 3. 建输出节点(buffersink) AVFilterContext *sink_ctx = NULL; avfilter_graph_create_filter(&sink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, graph); // 4. 中间滤镜:scale AVFilterContext *scale_ctx = NULL; avfilter_graph_create_filter(&scale_ctx, avfilter_get_by_name("scale"), "scale", "640:360", NULL, graph); // 5. 连边:in -> scale -> out avfilter_link(src_ctx, 0, scale_ctx, 0); avfilter_link(scale_ctx, 0, sink_ctx, 0); avfilter_graph_config(graph, NULL); // 6. 喂帧/收帧 av_buffersrc_add_frame_flags(src_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF); AVFrame *out = av_frame_alloc(); av_buffersink_get_frame(sink_ctx, out);

4.6 像素转换(libswscale)与音频重采样(libswresample)

// 像素格式/尺寸转换(如 YUV420P 1280x720 -> RGB24 640x360) struct SwsContext *sws = sws_getContext(1280, 720, AV_PIX_FMT_YUV420P, 640, 360, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL); sws_scale(sws, frame->data, frame->linesize, 0, 720, rgb_data, rgb_linesize); sws_freeContext(sws); // 音频重采样(如 44100Hz 立体声 -> 48000Hz 单声道) SwrContext *swr = swr_alloc(); av_opt_set_int(swr, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0); av_opt_set_int(swr, "out_channel_layout", AV_CH_LAYOUT_MONO, 0); av_opt_set_int(swr, "in_sample_rate", 44100, 0); av_opt_set_int(swr, "out_sample_rate", 48000, 0); av_opt_set_sample_fmt(swr, "in_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0); swr_init(swr); swr_convert(swr, out_data, out_nb_samples, in_data, in_nb_samples); swr_free(&swr);

4.7 错误处理

FFmpeg 错误码统一为负的 AVERROR 值(本质是 errno 的负映射)。调试时把错误码转成可读文本:

char errbuf[AV_ERROR_MAX_STRING_SIZE]; av_strerror(errcode, errbuf, sizeof(errbuf)); av_log(NULL, AV_LOG_ERROR, "错误: %s\n", errbuf);

常见:AVERROR(EAGAIN)、AVERROR_EOF、AVERROR(ENOMEM)、AVERROR_INVALIDDATA。


5. 详细使用

5.1 命令行实战(高频场景)

# 转码:H.264 视频 + AAC 音频 封装为 MP4 ffmpeg -i input.mkv -c:v libx264 -preset fast -crf 23 -c:a aac -b:a 128k output.mp4 # 只换封装,不重新编码(秒级完成) ffmpeg -i input.mkv -c copy output.mp4 # 裁剪前 10 秒 ffmpeg -ss 00:00:05 -t 10 -i input.mp4 -c copy output.mp4 # 抽帧:每 2 秒存一张 JPEG ffmpeg -i input.mp4 -vf fps=1/2 frame_%03d.jpg # 缩放到 720p 并加水印 ffmpeg -i input.mp4 -vf "scale=-2:720,drawtext=text='Hello':x=10:y=10:fontsize=24:fontcolor=white" output.mp4 # RTSP 摄像头取流实时转码推流到 RTMP ffmpeg -rtsp_transport tcp -i "rtsp://user:pass@192.168.1.10:554/stream" \ -c:v libx264 -preset ultrafast -tune zerolatency -f flv "rtmp://push.example.com/live/cam01" # HLS 直播切片(3 秒一片) ffmpeg -i input.mp4 -c:v libx264 -hls_time 3 -hls_list_size 0 -f hls out.m3u8 # 音视频分离 ffmpeg -i input.mp4 -vn audio.aac -an video.mp4 # 注意:-vn 去掉视频,-an 去掉音频 # 视频转 GIF ffmpeg -i input.mp4 -vf "fps=10,scale=320:-1" -loop 0 out.gif

5.2 C 语言完整转码管线(可编译骨架)

下面是一个最小可用的转码程序骨架,串起"解封装 → 解码 → 缩放 → 编码 → 封装"整条链(已省略逐行错误处理,完整工程请按 4.x 的 API 逐段补齐):

#include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libswscale/swscale.h> #include <libavutil/opt.h> #include <libavutil/imgutils.h> int main(int argc, char *argv[]) { const char *src_file = argv[1], *dst_file = argv[2]; // ---------- 解封装 ---------- AVFormatContext *in_fmt = NULL; avformat_open_input(&in_fmt, src_file, NULL, NULL); avformat_find_stream_info(in_fmt, NULL); int v_in = av_find_best_stream(in_fmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); AVStream *in_stream = in_fmt->streams[v_in]; // ---------- 解码器 ---------- const AVCodec *dec = avcodec_find_decoder(in_stream->codecpar->codec_id); AVCodecContext *dec_ctx = avcodec_alloc_context3(dec); avcodec_parameters_to_context(dec_ctx, in_stream->codecpar); avcodec_open2(dec_ctx, dec, NULL); // ---------- 编码器(H.264 720p) ---------- const AVCodec *enc = avcodec_find_encoder(AV_CODEC_ID_H264); AVCodecContext *enc_ctx = avcodec_alloc_context3(enc); enc_ctx->width = 1280; enc_ctx->height = 720; enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P; enc_ctx->time_base = (AVRational){1, 25}; enc_ctx->framerate = (AVRational){25, 1}; av_opt_set(enc_ctx->priv_data, "preset", "fast", 0); avcodec_open2(enc_ctx, enc, NULL); // ---------- 封装输出 ---------- AVFormatContext *out_fmt = NULL; avformat_alloc_output_context2(&out_fmt, NULL, NULL, dst_file); AVStream *out_stream = avformat_new_stream(out_fmt, NULL); avcodec_parameters_from_context(out_stream->codecpar, enc_ctx); avio_open(&out_fmt->pb, dst_file, AVIO_FLAG_WRITE); avformat_write_header(out_fmt, NULL); // ---------- 像素转换(输入帧 -> 720p YUV) ---------- struct SwsContext *sws = sws_getContext(dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt, enc_ctx->width, enc_ctx->height, enc_ctx->pix_fmt, SWS_BILINEAR, NULL, NULL, NULL); // ---------- 主循环 ---------- AVPacket *pkt = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); AVFrame *scaled = av_frame_alloc(); scaled->format = enc_ctx->pix_fmt; scaled->width = enc_ctx->width; scaled->height = enc_ctx->height; av_frame_get_buffer(scaled, 0); int64_t pts = 0; while (av_read_frame(in_fmt, pkt) >= 0) { if (pkt->stream_index == v_in) { avcodec_send_packet(dec_ctx, pkt); while (avcodec_receive_frame(dec_ctx, frame) >= 0) { sws_scale(sws, frame->data, frame->linesize, 0, dec_ctx->height, scaled->data, scaled->linesize); scaled->pts = pts++; // 重新生成时间戳 avcodec_send_frame(enc_ctx, scaled); while (avcodec_receive_packet(enc_ctx, pkt) >= 0) { pkt->stream_index = out_stream->index; av_packet_rescale_ts(pkt, enc_ctx->time_base, out_stream->time_base); av_interleaved_write_frame(out_fmt, pkt); } } } av_packet_unref(pkt); } // flush 编码器尾帧 avcodec_send_frame(enc_ctx, NULL); while (avcodec_receive_packet(enc_ctx, pkt) >= 0) { pkt->stream_index = out_stream->index; av_packet_rescale_ts(pkt, enc_ctx->time_base, out_stream->time_base); av_interleaved_write_frame(out_fmt, pkt); } // ---------- 收尾 ---------- av_write_trailer(out_fmt); avio_closep(&out_fmt->pb); avformat_free_context(out_fmt); avcodec_free_context(&dec_ctx); avcodec_free_context(&enc_ctx); avformat_close_input(&in_fmt); sws_freeContext(sws); av_frame_free(&frame); av_frame_free(&scaled); av_packet_free(&pkt); return 0; }

5.3 工业数采结合场景:RTSP 取流抽帧 + 推流

// 核心思路:RTSP 源 -> 解码 -> 抽关键帧(或按 fps 抽帧)-> JPEG 编码 -> 上报/落盘 // 1) 打开 RTSP(TCP 传输更稳) AVDictionary *opts = NULL; av_dict_set(&opts, "rtsp_transport", "tcp", 0); av_dict_set(&opts, "stimeout", "3000000", 0); // 3 秒超时 avformat_open_input(&fmt_ctx, "rtsp://192.168.1.10:554/stream", NULL, &opts); // 2) 解码后每 N 帧编码一张 JPEG AVCodecContext *jpeg_ctx = /* avcodec_find_encoder(AV_CODEC_ID_MJPEG) ... */; // 3) JPEG 数据可直接写 Kafka/TDengine/对象存储,或经 MQTT 上报 // 4) 断流检测:av_read_frame 返回 AVERROR_EOF / ETIMEDOUT 后按退避策略重连

6. 常错点

按"最容易踩、后果最重"排序:

坑点后果正确做法
1时间基混用:直接用 pkt->pts 乘除秒音画不同步、时长错乱一律 av_rescale_q 换算
2忘 avcodec_parameters_to_context解码失败/花屏open2 前必拷参数
3手工 free AVFrame/AVPacket 数据双重释放崩溃用 av_frame_unref/av_packet_unref
4只 av_frame_alloc 不 av_frame_get_buffer解码器写入野指针崩溃解码帧由解码器分配;自建帧先 get_buffer
5avcodec_receive_frame 返回 EAGAIN 就放弃丢帧EAGAIN 是正常状态,继续喂包
6包写到输出前不 rescale_ts输出文件时长错误/播放器快进编码时间基 → 流时间基换算
7-c copy 时源与目标容器不兼容输出损坏先 ffprobe 确认 codec tag 兼容
8输入像素格式与编码器要求不一致直接 send_frame编码失败先用 sws_scale 转 YUV420P(多数编码器要求)
9滤镜图不调 avfilter_graph_config图不生效/崩溃建图后必须 config
10av_buffersrc_add_frame 后还继续用 frame数据被滤镜改坏加 AV_BUFFERSRC_FLAG_KEEP_REF 或拷贝
11RTSP 默认 UDP 传输丢包花屏/卡顿-rtsp_transport tcp
12解码循环只处理视频包不 unref 音频包内存暴涨每个 pkt 用后必 unref
13多线程访问同一 AVCodecContext未定义行为/崩溃编解码器上下文线程亲和,加锁或每线程一实例
14硬解/硬编不检查设备初始化静默回退 CPU 性能暴跌检查 open2 返回码与 hw_frames_ctx
15B 帧场景下用 DTS 当 PTS 显示播放顺序错乱显示用 PTS,写入用解码排序
16忘 flush:不 avcodec_send_frame(enc_ctx, NULL)末尾丢几帧编码结束必须 flush
17-ss 放在 -i 前/后语义不同剪切位置不对前面是"快进到时间点再解",后面是"解到时间点再剪"
18HLS 切片时间基准与 -hls_time 不符直播延迟/列表异常注意 GOP 对齐,加 -g 帧率×秒数
19音视频同步只对齐起始 PTS 不持续校正越播越不同步以音频为时钟,视频持续追赶或等待
20忽略 av_read_frame 的 EOF/超时死循环/无限重连风暴区分正常 EOF 与错误,退避重连

6.1 两个经典疑难详解

画面花屏但文件时长正常:多半是编码器输入像素格式不对(比如编码器要 NV12 你给了 YUV420P),或 avcodec_parameters_to_context 缺失导致 SPS/PPS 解析错位。先用 ffprobe -show_streams 确认 pix_fmt,再用 ffmpeg -pix_fmts 查编码器支持列表。

音画不同步:转码时最常见原因是时间基换算出错或 pts 重生成逻辑不一致。排查步骤:ffprobe -show_frames 对比音视频 pkt_pts_time 序列,检查首帧差值是否恒定;若差值恒定说明起始不同步,若递增说明时钟漂移。


7. 性能优化

  1. 按需裁剪组件:--disable-everything + 只开需要的 demuxer/decoder/encoder,减小体积与初始化开销;
  2. 线程化:解码 avcodec_open2 前设 dec_ctx->thread_count(x264 编码器默认已多线程);多路流并行解码用多线程/多进程;
  3. 零拷贝路径:解码帧直接送滤镜/编码器,避免 av_frame_clone 后改数据;
  4. 硬件加速:NVIDIA 用 NVENC/NVDEC(h264_nvenc),Intel 用 QSV(h264_qsv),Linux 通用 VAAPI;纯 CPU 场景 -preset 从 slow 降到 fast/veryfast 可显著提速;
  5. 转封装优先于转码:能 -c copy 就不要重编码,吞吐量差一个数量级;
  6. 滤镜图合并:多个 scale/crop 合并进一张图,避免多次像素格式转换;
  7. 音频格式对齐:解码后直接按编码器格式 swr_convert,避免中间格式转换;
  8. 缓冲调优:推流场景 -max_delay、-re(实时读)按网络带宽调整,-g GOP 与切片对齐。

8. 总结

五句话带走:

  1. 容器 ≠ 编码:排查问题先 ffprobe 分清 format_name 与 codec_name;
  2. 时间基是一切:PTS/DTS 换算永远用 av_rescale_q,跨层必转;
  3. 引用计数内存模型:AVFrame/AVPacket 只 ref/unref,不手工 free;
  4. send/receive 推拉模型:EAGAIN 是正常状态,EOF 要 flush 冲刷缓冲;
  5. 先命令行验证、再 C API 集成:ffmpeg -vf 能跑通的效果,转成 C 就是一段可对照的滤镜图代码。

FFmpeg 是"读文档不如读代码、读代码不如跑命令"的典型项目——每一个命令行参数背后都是 libav* 的一串 API 调用,用 ffmpeg -h full 和 ffprobe 做探针,是学习它最快的路径。


9. FAQ

问题速答
视频转码 CPU 占用 100% 正常吗?软件编码正常;要降占用用硬件编码或低 preset
-c copy 为什么有时报错?目标容器不支持源编码的 codec tag,需转码
如何判断文件是什么编码?ffprobe -show_streams file 看 codec_name
RTSP 画面花屏怎么处理?换 TCP 传输、加大 -analyzeduration、检查设备码流
抽帧用什么格式最快?MJPEG 编码器抽 JPEG,比 PNG 快且体积小
FFmpeg 可以商用吗?核心是 LGPL/GPL,按自己是否修改源码/链接方式选许可证,商用前评估 LGPL 动态链接
HLS 直播延迟大怎么调?-hls_time 1-2、-hls_flags independent_segments、减小 GOP
如何实时预览滤镜效果?ffplay -vf "scale=640:360,drawtext=..." input.mp4
多路 RTSP 同时转码怎么规划?每路一个进程/线程组,独立 AVFormatContext,必要时 GPU 硬解多路
音频声道数变了但时长不变正常吗?正常,重采样只改变采样布局不影响内容时长

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询