注意:本指南默认您已掌握C语言基础和Linux命令行操作。若不会使用gcc
,建议先学习《C语言:从入门到内存泄漏》。
一、FFmpeg简介:音视频界的瑞士军刀
FFmpeg集成了600+编解码器和100+封装格式的超级工具链:
![image]()
核心组件功能:
- libavcodec:编解码器大本营(H.264/HEVC/AAC/MP3)
- libavformat:封装/解封装指挥官(MP4/FLV/TS等)
- libavdevice:硬件设备特工(摄像头/麦克风/屏幕捕获)
- libswscale:像素魔术师(YUV↔RGB转换)
- libavutil:工具百宝箱(哈希/数学/日志)
官网金句:FFmpeg is the future, but you can type ffmpeg today
下面用「一句话 + 命令/思路」的形式,把 FFmpeg 能做的「日常必备」到「高阶黑科技」一次性列给你。直接复制命令即可跑;带 ‼
的表示进阶/易踩坑。
1.常用操作(90% 场景覆盖)
目的 |
一句话示例 |
查看媒体信息 |
ffprobe -hide_banner -i input.mp4 |
无损合并 ts 分段 |
`ffmpeg -i “concat:1.ts |
提取音频 |
ffmpeg -i in.mp4 -vn -c:a copy a.aac |
提取视频 |
ffmpeg -i in.mp4 -an -c:v copy v.mp4 |
裁剪片段 |
ffmpeg -ss 00:01:00 -to 00:01:30 -i in.mp4 -c copy clip.mp4 |
转码 H.264 → H.265 |
ffmpeg -i in.mp4 -c:v libx265 -crf 28 -c:a copy out.mp4 |
调整分辨率 |
ffmpeg -i in.mp4 -vf scale=1280:-2 -c:a copy out.mp4 |
加水印 |
ffmpeg -i in.mp4 -i logo.png -filter_complex "overlay=W-w-10:H-h-10" out.mp4 |
GIF 转 MP4 |
ffmpeg -f gif -i anim.gif -pix_fmt yuv420p out.mp4 |
合并音视频 |
ffmpeg -i v.mp4 -i a.m4a -c copy va.mp4 |
2. 高级玩法(性能/画质/实时向)
目的 |
一句话示例 / 思路 |
4K60 实时 NVENC 硬编‼ |
ffmpeg -hwaccel cuda -i in.mp4 -c:v h264_nvenc -preset p7 -b:v 50M out.mp4 |
录屏+摄像头画中画‼ |
ffmpeg -f x11grab -s 1920x1080 -i :0.0 -f v4l2 -video_size 320x240 -i /dev/video0 -filter_complex "[0:v][1:v]overlay=W-w-10:H-h-10" -c:v libx264 out.mkv |
直播推流 RTMP‼ |
ffmpeg -re -i in.mp4 -c:v libx264 -preset veryfast -f flv rtmp://server/app/stream |
10-bit HDR → SDR tonemap‼ |
ffmpeg -i hdr.mp4 -vf zscale=transfer=linear,tonemap=clip,zscale=transfer=bt709,format=yuv420p -c:v libx265 sdr.mp4 |
多线程极速转码‼ |
ffmpeg -i in.mp4 -c:v libx264 -preset ultrafast -threads 0 -c:a aac out.mp4 |
抽帧做缩略图网格‼ |
ffmpeg -i in.mp4 -vf "select=not(mod(n\,30)),scale=320:-1,tile=4x4" -frames 1 grid.jpg |
实时音频重采样‼ |
ffmpeg -f pulse -i default -af aresample=44100,aformat=fltp -c:a aac out.m4a |
无重新编码快速倒放‼ |
ffmpeg -i in.mp4 -vf reverse -af areverse -c copy reverse.mp4 |
生成测试信号(彩条+噪声) |
ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=30 -f lavfi -i sine=f=1000 -shortest test.mp4 |
提取 RAW YUV |
ffmpeg -i in.mp4 -c:v rawvideo -pix_fmt yuv420p out.yuv |
3.黑科技天花板(需要二次开发/脚本)
目的 |
一句话思路 |
帧级精确剪辑‼ |
ffprobe -select_streams v -show_frames -print_format csv 解析 pts → ss /to 毫秒级切割 |
AI 超分前处理‼ |
ffmpeg -i in.mp4 -vf zscale=1920:1080:filter=lanczos -c:v png frame%06d.png → 超分 → 再编码 |
低延迟云游戏采集‼ |
ffmpeg -f kmsgrab -i - -vf hwdownload,format=nv12 -c:v h264_nvenc -tune zerolatency -f rtsp rtsp://host/live |
音视频自动对齐‼ |
先用 ffprobe 读 start_time → 计算偏移 → itsoffset 参数 |
DRM 直接抓帧‼ |
ffmpeg -f kmsgrab -crtc_id 63 -framerate 60 -i - ... (需 root 权限) |
4.速查表
场景 |
关键参数 |
最高压缩 |
-c:v libx265 -crf 28 -preset slow |
实时/直播 |
-preset ultrafast -tune zerolatency |
无损备份 |
-c:v ffv1 -level 3 -c:a flac |
仅复制流 |
-c copy |
精确毫秒 |
-ss 00:01:23.456 -to 00:01:25.000 |
一句话总结:
从「剪个 10 秒小视频」到「4K60 帧低延迟直播」,FFmpeg 一条命令行就能搞定;再往上,用脚本/代码把 FFmpeg 当「底层 GPU 音视频引擎」用,天花板几乎没有。
二、核心数据结构:四大天王
1. AVFormatContext
- 容器指挥官
1 2
| AVFormatContext* fmt_ctx = NULL; avformat_alloc_output_context2(&fmt_ctx, NULL, "mp4", "output.mp4");
|
职责:
- 管理文件/流的封装信息
- 关键字段:
nb_streams
:流数量
pb
:I/O上下文指针
类比:快递分拣中心,管理所有包裹(音视频流)
2. AVCodecContext
- 编解码大脑
1 2 3 4
| AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264); AVCodecContext* codec_ctx = avcodec_alloc_context3(codec); codec_ctx->bit_rate = 4000000; codec_ctx->framerate = (AVRational){30, 1};
|
参数 |
视频示例 |
音频示例 |
分辨率 |
1920x1080 |
- |
采样率 |
- |
44100 Hz |
像素格式 |
YUV420P |
- |
声道布局 |
- |
AV_CH_LAYOUT_STEREO |
避坑指南:未设置帧率会导致视频加速播放
3. AVPacket
- 压缩数据包
1 2 3 4 5
| AVPacket* pkt = av_packet_alloc(); while (av_read_frame(fmt_ctx, pkt) >= 0) { av_packet_unref(pkt); }
|
特点:
4. AVFrame
- 原始数据帧
1 2 3
| AVFrame* frame = av_frame_alloc(); frame->format = AV_PIX_FMT_YUV420P; av_frame_get_buffer(frame, 0);
|
数据存储:
- 视频:
data[0]
(Y), data[1]
(U), data[2]
(V)
- 音频:
data[0]
(左声道), data[1]
(右声道)
冷知识:人眼对亮度敏感度是色度的6倍,故YUV420P更省带宽!
三、核心API详解
1.设备操作三剑客
函数 |
作用 |
返回值 |
经典错误 |
avdevice_register_all() |
注册设备 |
void |
未调用导致设备找不到 |
av_find_input_format() |
查找设备格式 |
AVInputFormat* |
返回NULL表示格式不支持 |
avformat_open_input() |
打开设备 |
int |
负数需用av_strerror解析 |
2.编解码关键流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
AVCodecContext *ctx = avcodec_alloc_context3(codec); ctx->width = 1920; ctx->height = 1080; ctx->time_base = (AVRational){1, 30};
if (avcodec_open2(ctx, codec, NULL) < 0) { char err_buf[256]; av_strerror(ret, err_buf, sizeof(err_buf)); }
avcodec_send_frame(ctx, frame); while (avcodec_receive_packet(ctx, pkt) == 0) { }
|
3.数据转换核心
函数 |
作用 |
适用场景 |
sws_getContext() |
图像缩放/格式转换 |
摄像头YUV→编码器YUV |
swr_alloc_set_opts() |
音频重采样 |
麦克风PCM→AAC编码 |
av_frame_make_writable() |
避免内存拷贝 |
高性能处理 |
四、深入PulseAudio
1.核心架构
![image]()
2.设备类型详解
类型 |
功能 |
FFmpeg标识 |
查询命令 |
Source |
音频输入 |
-f pulse -i default |
pactl list sources |
Sink |
音频输出 |
不支持直接访问 |
pactl list sinks |
Monitor |
监听输出 |
.monitor 后缀 |
pactl list sources |
3.实战场景
1 2 3 4 5 6
| avformat_open_input(&ctx, "default", av_find_input_format("pulse"), NULL);
avformat_open_input(&ctx, "alsa_output.pci-0000_00_1f.3.analog-stereo.monitor", av_find_input_format("pulse"), NULL);
|
系统救星:pulseaudio -k && pulseaudio --start
解决90%音频问题
五、音视频录制实战
1. 摄像头录制(Linux V4L2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| avdevice_register_all(); AVInputFormat* input_fmt = av_find_input_format("v4l2"); avformat_open_input(&cam_ctx, "/dev/video0", input_fmt, NULL);
if (codecpar->codec_id == AV_CODEC_ID_MJPEG) { avcodec_parameters_copy(out_stream->codecpar, codecpar); } else { AVCodec *decoder = avcodec_find_decoder(codecpar->codec_id); AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_H264); }
|
2. 麦克风录制(PulseAudio)
1 2 3 4 5 6 7
| SwrContext* swr = swr_alloc_set_opts(NULL, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_FLTP, 44100, src_ch_layout, src_sample_fmt, src_sample_rate, 0, NULL); swr_init(swr);
|
3. 音视频同步(核心!)
1 2 3 4 5 6 7 8 9 10
| int64_t video_pts = av_rescale_q(frame->pts, video_time_base, AV_TIME_BASE_Q); int64_t audio_pts = av_rescale_q(samples->pts, audio_time_base, AV_TIME_BASE_Q);
if (video_pts <= audio_pts) { av_interleaved_write_frame(fmt_ctx, video_pkt); } else { av_interleaved_write_frame(fmt_ctx, audio_pkt); }
|
同步要诀:使用av_rescale_q
而非手动计算,避免精度丢失
4.音频录制+重采样实战
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| avformat_open_input(&audio_fmt_ctx, "default", av_find_input_format("pulse"), NULL);
AVCodec *audio_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); AVCodecContext *a_codec_ctx = avcodec_alloc_context3(audio_codec); a_codec_ctx->sample_rate = 44100; a_codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
SwrContext *swr = swr_alloc(); swr_alloc_set_opts2(&swr, &a_codec_ctx->ch_layout, a_codec_ctx->sample_fmt, a_codec_ctx->sample_rate, &in_ch_layout, in_sample_fmt, in_sample_rate, 0, NULL); swr_init(swr);
while (1) { av_read_frame(audio_fmt_ctx, pkt); swr_convert(swr, out_samples, out_sample_count, (const uint8_t**)pkt->data, pkt->size/4); }
|
六、高级开发技巧
1. 性能优化三连击
1 2 3 4 5 6 7 8 9
| ctx->hw_device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA);
ctx->thread_count = 16; ctx->thread_type = FF_THREAD_FRAME;
av_frame_make_writable(frame);
|
2. 调试神器
工具 |
命令示例 |
功能 |
ffprobe |
ffprobe -show_streams input.mp4 |
分析文件结构 |
ffplay |
ffplay -f v4l2 /dev/video0 |
实时预览设备流 |
3.必备诊断命令
1 2 3 4 5 6 7 8
| ffmpeg -f pulse -list_devices true -i dummy
ffplay -f v4l2 -video_size 1280x720 /dev/video0
ffprobe -show_streams output.mp4
|
4.经典错误表
错误码 |
含义 |
解决方案 |
AVERROR(EAGAIN) |
资源暂时不可用 |
重试或等待 |
AVERROR(ENOMEM) |
内存不足 |
检查资源泄露 |
AVERROR(EINVAL) |
无效参数 |
检查参数合法性 |
AVERROR_PATCHWELCOME |
功能未实现 |
升级FFmpeg版本 |
5. 常见问题急救包
问题:视频花屏
原因:
- 关键帧丢失(I帧被狗吃了)
- PTS时间戳不连续
解决方案:
1 2 3 4 5 6
| frame->key_frame = 1; frame->pict_type = AV_PICTURE_TYPE_I;
frame->pts = last_pts + av_rescale_q(1, time_base, stream_time_base);
|
6. 跨平台兼容方案
功能 |
Linux |
Windows |
macOS |
摄像头访问 |
v4l2 |
dshow |
avfoundation |
音频录制 |
pulse/alsa |
dshow |
avfoundation |
屏幕录制 |
x11grab |
gdigrab |
avfoundation |
硬件加速 |
VAAPI/NVDEC |
DXVA2/NVDEC |
VideoToolbox |
注意事项
- Linux (PulseAudio):
- 需要安装
libpulse-dev
- 设备名称为
default
或特定设备名称
- Windows (DirectShow):
- 修改设备名称为
audio=麦克风名称
- 可能需要先列出可用设备
- macOS (AVFoundation):
- 修改设备名称为
:0
或特定设备索引
- 输入格式应为
avfoundation
七、终极Q&A
1. 为什么摄像头录制中同时需要解码器和编码器
- 摄像头输出是压缩数据(如MJPEG)
- 目标格式需要重新编码(如H.264)
- 转码流水线:
MJPEG -> YUV -> H.264
2.AVPacket 和 AVFrame 之间的关系
- 解码过程:
- 从多媒体文件中读取 AVPacket。
- 使用解码器将 AVPacket 中的压缩数据解码到 AVFrame 中。
- 处理(例如,显示或进一步处理)AVFrame 中的未压缩数据。
- 编码过程:
- 从原始数据(例如,摄像头捕获的视频帧或麦克风捕获的音频样本)创建 AVFrame。
- 使用编码器将 AVFrame 中的未压缩数据编码到 AVPacket 中。
- 将 AVPacket 写入到多媒体文件中。
- 转换过程:
- 读取 AVPacket,解码到 AVFrame。
- 对 AVFrame 进行格式转换或处理。
- 将处理后的 AVFrame 重新编码到 AVPacket。
- 写入新的 AVPacket 到输出文件。
通过这种方式,AVPacket 和 AVFrame 在 FFmpeg 的多媒体处理流程中起到了桥梁的作用,确保数据能够在不同的处理阶段之间正确传递和转换。
3.一些性能优化思路(待验证)
1.从DRM(Direct Rendering Manager)直接获取帧数据可以显著提高屏幕捕获效率,避免通过 X11 的额外开销—缺点是通过 drm 需要 root 权限,仅支持 linux,实现起来较为复杂。
2.优化转码算法,录屏耗时一般都在捕获和转码阶段,参考开源录屏应用 simplescreenrecorder(ssr) 的算法实现,可有效较少转码耗时—转码本质上是数学运算,一些 cpu 指令集对特定数学运算也有较好的加速效果。
3.RGB 到 YUV 的转换可以使用更高效的 SIMD 指令优化
4.录制音频时怎么查找系统的默认音频设备
1 2 3
| $ pactl list sources short 0 alsa_output.pci-0000_00_1f.3.analog-stereo.monitor module-alsa-card.c s16le 2ch 44100Hz SUSPENDED 1 alsa_input.pci-0000_00_1f.3.analog-stereo module-alsa-card.c s16le 2ch 44100Hz SUSPENDED
|
选 **带 ****monitor**
的那一行即可。
也可以到操作系统的设置中查看输出设备是否一致,命令可能会查询到多个输出设备
5.官方示例
以 linux 操作系统为例,如果是基于 Debian 的发行版,运行以下命令安装示例:
1
| sudo apt install ffmpeg-doc
|
![image]()
6.录制的视频有时候会花屏
1.帧间预测依赖链断裂-视频编码基于 I帧(关键帧)、P帧(前向预测) 和 B帧(双向预测)(见附2,先了解下 I P B 帧的概念)
2.时间戳不连续,也就是帧的 pts
7.视频播放速度是怎么控制的
播放行为本质上是:
- 以PTS为绝对时间基准
- 以预设帧率为初始化参考
- 通过动态同步算法实时调整
八、结语:通往音视频大师之路
当你:
- 能30分钟写出录制程序 → 青铜
- 搞定音视频同步 → 白银
- 实现4K60帧编码 → 黄金
- 修复花屏/卡顿问题 → 王者
- 给FFmpeg提交补丁 → 封神
FFmpeg不是一天学会的,每个崩溃的程序都让你离大师更近一步。
附0:ffmpeg重点接口
1.编解码相关接口
avcodec_find_encoder/avcodec_find_decoder |
查找指定的编解码器。 |
在编码或解码之前,需要通过这些函数获取正确的编解码器。 |
avcodec_open2 |
打开一个编解码器实例,初始化编解码器上下文。 |
在开始编解码操作之前,必须调用此函数以确保编解码器准备好。 |
avcodec_send_frame/avcodec_receive_packet |
分别用于将解码后的帧发送给编码器,以及从编码器接收编码后的数据包。 |
在编码过程中,用于将帧数据传递给编码器并获取编码结果。 |
avcodec_send_packet/avcodec_receive_frame |
分别用于将编码后的数据包发送给解码器,以及从解码器接收解码后的帧。 |
在解码过程中,用于处理编码数据并获取解码后的帧数据。 |
2.格式上下文相关接口
avformat_open_input |
打开一个媒体文件输入流。 |
读取媒体文件时使用,用于初始化 AVFormatContext。 |
avformat_write_header |
写入媒体文件的文件头信息。 |
在创建媒体文件时,用于写入必要的格式和流信息。 |
av_interleaved_write_frame |
将编码后的数据包写入文件,支持交错写入。 |
在录制或转码过程中,用于将编码后的数据写入目标文件。 |
avformat_close_input/avio_closep |
关闭媒体文件输入流和输出流。 |
在完成读写操作后,清理资源。 |
3.图像转换相关接口
sws_getContext |
创建一个图像转换上下文,用于在不同像素格式之间进行转换。 |
在处理屏幕捕获或视频帧格式转换时使用。 |
sws_scale |
执行图像转换,将一个图像从一种像素格式转换为另一种格式。 |
在需要调整图像大小或格式时使用,例如将捕获的屏幕图像从 RGB 转换为 YUV 格式。 |
4.其他重要接口
avformat_alloc_output_context2 |
分配一个输出上下文,用于创建输出文件。 |
在录制或转码时初始化输出文件的格式和流。 |
avcodec_parameters_from_context |
将编解码器上下文的参数复制到流参数中。 |
在创建输出流时,用于设置流的编解码器参数。 |
av_packet_alloc/av_packet_unref |
分配和释放 AVPacket 的内存。 |
在处理编码数据包时,用于管理数据包的生命周期。 |
5.高级功能
AVFilterGraph |
用于构建和处理复杂的音视频滤镜链。 |
在需要对音视频数据进行复杂的实时处理(如裁剪、旋转、添加水印等)时使用。 |
avfilter_graph_create_filter |
在滤镜图中创建一个新的滤镜实例。 |
用于初始化和配置滤镜链中的各个滤镜 |
附1:FFmpeg编解码器与封装格式大全
温馨提示:本清单仅展示FFmpeg支持的部分代表性格式(完整列表超600+),建议保存备用。当有人质疑FFmpeg能力时,请优雅地甩出此表😎
1.视频编解码器
主流编解码器 |
专有格式 |
创新编码 |
遗留格式 |
H.264/AVC |
Apple ProRes |
AV1 |
MJPEG |
H.265/HEVC |
DNxHD |
VP9 |
Motion JPEG |
AV1 |
CineForm |
Daala |
DV (Digital Video) |
VP8 |
GoPro CineForm |
Thor |
HuffYUV |
MPEG-2 |
Windows Media Video |
Dirac |
Sorenson Video |
MPEG-4 Part 2 |
RealVideo |
Snow |
Indeo |
Theora |
Flash Screen Video |
FFV1 (无损) |
MSU Screen Capture |
2.音频编解码器
通用音频 |
语音编码 |
专业音频 |
创新格式 |
AAC |
Opus |
FLAC (无损) |
Dolby Atmos |
MP3 |
Speex |
ALAC (Apple无损) |
DTS:X |
AC-3 |
AMR-NB/WB |
DSD (Direct Stream) |
CELT |
Vorbis |
G.711 (A-law/μ-law) |
PCM (各种位深) |
Siren |
WMA |
GSM |
Dolby TrueHD |
AptX |
3.视频容器格式
主流容器 |
流媒体专用 |
专业制作 |
设备专用 |
MP4 |
HLS (m3u8) |
MXF |
3GP |
MKV |
DASH |
GXF |
3G2 |
AVI |
RTMP |
MOV (ProRes版) |
F4V |
WebM |
RTSP |
XDCAM |
VOB (DVD) |
FLV |
SRT |
AVC-Intra |
MOD (摄像机) |
4.音频容器格式
通用音频 |
无损音频 |
广播专用 |
元数据载体 |
MP3 |
FLAC |
ADTS |
ID3v2 |
AAC |
WAV (PCM) |
LATM |
APE Tag |
Ogg |
AIFF |
RTP |
Vorbis Comment |
WMA |
DSF (DSD) |
AU |
Xiph Comment |
5.特殊格式
类别 |
代表成员 |
特殊技能 |
图像序列 |
PNG, JPEG, TIFF, DPX |
支持帧序列导出 |
游戏专用 |
Bink Video, Smacker |
游戏过场动画专用 |
闭路电视 |
CCTV H.264+ |
监控设备专用格式 |
光碟格式 |
Blu-ray, HD-DVD |
蓝光碟片支持 |
字幕格式 |
SRT, ASS, WebVTT, DVB-SUB |
支持硬字幕+软字幕 |
冷知识:FFmpeg甚至支持解析 .gif动图 和 .ico图标文件,堪称格式界的”瑞士军刀”
6.结语:
当看到如此庞大的格式支持列表时,请记住FFmpeg作者的名言:
“我们不是选择支持什么格式,而是选择不支持什么格式——因为实在太少了”
(注:完整列表可通过 ffmpeg -codecs
和 ffmpeg -formats
查看)
附2:视频编码中的 I P B帧
庆幸的是,在 ffmpeg 的音视频编程中,我们只需要知道 IPB 帧 的概念即可,ffmpeg会自动处理这些帧。
1.I帧(Intra-coded Frame,关键帧)
- 定义:完全独立编码的帧,不依赖其他帧即可解码。类似静态JPEG图像,仅使用帧内压缩(空间冗余压缩)。
- 特点:
- 包含完整图像数据,解码时无需参考其他帧。
- 压缩率最低(因无时间冗余利用),但可作为随机访问点(如快进、seek操作)。
- 通常定期插入(如每秒1次),用于错误恢复(若P/B帧丢失,可从下一个I帧重新开始解码)。
- 应用场景:
- 视频开头(首个帧必须是I帧)。
- 场景切换时(新旧场景无关联,需重新生成完整帧)。
- 广播流媒体(支持随机换台)。
2.P帧(Predicted Frame,前向预测帧)
- 定义:通过前向预测编码的帧,仅参考过去的I帧或P帧。
- 工作原理:
- 基于运动估计(Motion Estimation),在参考帧中查找相似块(宏块),记录运动矢量(位移数据)和残差(预测误差)。
- 例如:若背景静止,P帧只需编码移动物体的变化,大幅节省数据量。
- 特点:
- 压缩率高于I帧(利用时间冗余),但解码依赖参考帧(若参考帧丢失会出错)。
- 无法处理未来帧的遮挡关系(如物体从后方出现)。
- 应用场景:
3.B帧(Bidirectional Frame,双向预测帧)
- 定义:通过双向预测编码的帧,可同时参考过去和未来的I/P帧。
- 工作原理:
- 综合前后参考帧进行插值预测。例如:若物体从左侧移动到右侧,B帧可结合前一帧(物体在左)和后一帧(物体在右)生成中间状态。
- 支持直接模式(Direct Mode):复用运动矢量,进一步减少数据量。
- 特点:
- 压缩率最高(因时间冗余利用最充分),但引入编码延迟(需等待未来帧到达)。
- 解码顺序与显示顺序可能不同(如先解码未来的P帧再解码B帧)。
- 应用场景:
- 非实时场景(如点播视频、蓝光光盘)。
- 高速运动场景(如体育赛事),通过双向预测减少残差数据。
4.三者的协同工作流程
以典型的 IBBPBBP… GOP(Group of Pictures)结构为例:
- 解码顺序:I₀ → P₃ → B₁ → B₂ → P₆ → B₄ → B₅…
- 显示顺序:I₀ → B₁ → B₂ → P₃ → B₄ → B₅ → P₆…
- 解码器会缓存未来帧(如P₃),以便解码B₁/B₂时使用。
5.关键对比表
属性 |
I帧 |
P帧 |
B帧 |
参考帧 |
无 |
过去的I/P帧 |
过去+未来的I/P帧 |
压缩率 |
最低 |
中等 |
最高 |
解码依赖 |
独立 |
依赖前向帧 |
依赖双向帧 |
延迟 |
无 |
低 |
高(需未来帧) |
用途 |
随机访问、错误恢复 |
主流预测 |
高压缩优化 |
6.实际应用中的权衡
- 直播场景:通常减少B帧(或不用)以降低延迟(如RTMP协议常用IPPP结构)。
- 存储场景:大量使用B帧(如H.264的
IBBBP...
结构),节省50%以上码率。
- 错误恢复:若P/B帧丢失,解码器会跳过到下一个I帧(可能出现短暂花屏)。
理解这三类帧的机制,对优化编码参数(如GOP长度、B帧数量)和排查播放问题(如花屏、卡顿)至关重要。
网络上也有比较好的文章可以参考:https://zhuanlan.zhihu.com/p/614236013
附3: YUV 格式与 RGB 图像格式介绍
在YUV文件中,Y、U、V分别代表亮度(Luma)和两个色差(Chroma)分量。与一张真正的RGB图片相比,它们的对应关系如下:
1.Y (Luma) - 亮度分量
- 作用:表示图像的黑白(灰度)信息,即每个像素的明暗程度。
- 类比:相当于将彩色照片去色后的灰度图,单独看Y分量时,是一张完整的黑白图片。
- 示例:一张彩色照片中的白色区域Y值高,黑色区域Y值低。
2.U/V (Chroma) - 色差分量
- 作用:表示颜色信息(色调和饱和度),通过两个色差信号(U和V)描述颜色与亮度的差异。
- U (Cb):蓝色与亮度的差值(B - Y)。
- V (Cr):红色与亮度的差值(R - Y)。
- 类比:相当于给灰度图“上色”的指令。单独看U或V分量时,是模糊的色度图(类似低分辨率的彩色蒙版)。
- 示例:红色区域V值高,蓝色区域U值高,绿色通过U和V的组合间接表示。
3.与RGB图片的对比
RGB图片 |
YUV文件 |
每个像素由R、G、B三个值表示 |
Y、U、V可能以不同采样率存储(如YUV420) |
直接描述颜色 |
分离亮度和色度,便于压缩 |
文件较大 |
色度可下采样(如U/V分辨率减半),节省空间 |
4.实际存储示例(YUV420格式)
- Y:每像素一个值(分辨率=原图)。
- U/V:每2x2像素共享一个U和V值(分辨率=原图的1/4)。
- 效果:人眼对亮度更敏感,这种压缩对视觉影响小。
5.总结
- Y:黑白细节,决定清晰度。
- U/V:颜色信息,决定色调。
- 关系:YUV通过牺牲部分色度精度,用更小体积存储接近RGB的视觉内容。
注:以下是一个将 rgb 的图像格式转换为 yuv420 图像格式的 demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
|
#include <stdint.h> #include <stdio.h> #include <stdlib.h>
#define W 64 #define H 64 #define HALF_W (W/2) #define HALF_H (H/2)
static inline uint8_t RGB_TO_Y(uint8_t r, uint8_t g, uint8_t b) { return (uint8_t)( ( 66 * r + 129 * g + 25 * b + 128) >> 8) + 16; } static inline uint8_t RGB_TO_U(uint8_t r, uint8_t g, uint8_t b) { return (uint8_t)( (-38 * r - 74 * g + 112 * b + 128) >> 8) + 128; } static inline uint8_t RGB_TO_V(uint8_t r, uint8_t g, uint8_t b) { return (uint8_t)( (112 * r - 94 * g - 18 * b + 128) >> 8) + 128; }
int main(void) { uint8_t *y = malloc(W * H); uint8_t *u = malloc(HALF_W * HALF_H); uint8_t *v = malloc(HALF_W * HALF_H);
for (int row = 0; row < H; ++row) { for (int col = 0; col < W; ++col) { if (col < W/2) { y[row * W + col] = RGB_TO_Y(255, 255, 0); } else { y[row * W + col] = RGB_TO_Y(255, 0, 0); } } }
for (int row = 0; row < HALF_H; ++row) { for (int col = 0; col < HALF_W; ++col) { int even_row = row * 2; int even_col = col * 2;
uint8_t r, g, b; if (even_col < W/2) { r = 255; g = 255; b = 0; } else { r = 255; g = 0; b = 0; }
u[row * HALF_W + col] = RGB_TO_U(r, g, b); v[row * HALF_W + col] = RGB_TO_V(r, g, b); } }
FILE *fp = fopen("yellow_red_64x64.yuv", "wb"); if (!fp) { perror("fopen"); return 1; }
fwrite(y, 1, W * H, fp); fwrite(u, 1, HALF_W * HALF_H, fp); fwrite(v, 1, HALF_W * HALF_H, fp); fclose(fp);
free(y); free(u); free(v); puts("yellow_red_64x64.yuv 已生成"); return 0; }
|
附4:PulseAudio 中的 Sink 和 Source 详解
在 PulseAudio 音频系统中,sink 和 source 是两个核心概念,它们代表了音频流的两个不同方向:
1.Sink(接收器)
- 定义:音频输出设备(播放设备)
- 功能:接收音频流并将其播放出来
- 对应设备:
- 扬声器(内置或外接)
- 耳机
- HDMI/DisplayPort 音频输出
- 蓝牙音频设备
- 命令:
1
| pacmd list-sinks # 列出所有可用的音频输出设备
|
1
| name: <alsa_output.pci-0000_00_1f.3.analog-stereo>
|
2.Source(源)
- 定义:音频输入设备(录制设备)
- 功能:捕获音频流并将其发送到系统
- 对应设备:
- 麦克风(内置或外接)
- 线路输入
- 蓝牙麦克风
- 系统音频捕获(监视器)
- 命令:
1
| pacmd list-sources # 列出所有可用的音频输入设备
|
1
| name: <alsa_input.pci-0000_00_1f.3.analog-stereo>
|
3.特殊类型:监视器(Monitor)
在 PulseAudio 中,还有一个重要的概念是监视器(Monitor):
- 定义:一种特殊的 source(输入设备)
- 功能:捕获从 sink(输出设备)播放的音频
- 命名规则:
<sink名称>.monitor
- 用途:
- 示例:
1
| name: <alsa_output.pci-0000_00_1f.3.analog-stereo.monitor>
|
4.实际应用场景
1. 设置默认输出设备(sink)
1
| pacmd set-default-sink alsa_output.pci-0000_00_1f.3.analog-stereo
|
2. 设置默认输入设备(source)
1
| pacmd set-default-source alsa_input.pci-0000_00_1f.3.analog-stereo
|
3. 录制系统声音(使用监视器)
1
| ffmpeg -f pulse -i alsa_output.pci-0000_00_1f.3.analog-stereo.monitor output.wav
|
4. 查看所有设备状态
1 2 3 4 5
| # 完整输出设备列表 pacmd list-sinks
# 完整输入设备列表 pacmd list-sources
|
5.常见问题解决
如果您遇到 未指定有效的命令
错误,可能是以下原因:
- PulseAudio 未运行:
- 命令语法错误:
1 2 3
| # 正确命令 pactl get-default-sink pactl get-default-source
|
- 权限问题:
1
| sudo usermod -aG audio $USER
|
- PulseAudio 配置问题:
1 2 3
| rm ~/.config/pulse/* pulseaudio -k pulseaudio --start
|
理解 sink 和 source 的概念对于在 Linux 系统上管理音频设备至关重要,特别是在开发音频应用程序或进行多媒体录制时。
附5:简单 demo
在学习 ffmpeg 的过程中,我尽可能将一些简单的功能抽取了单独的 demo。See:https://github.com/ssk-wh/ffmpeg-demo