====== FFmpeg ====== * Official site:[[https://ffmpeg.org/|ffmpeg.org]] * 静态编译下载:[[https://johnvansickle.com/ffmpeg/]] * ffmpeg 原理:[[https://ffmpeg.xianwaizhiyin.net/]] ===== FFmpeg使用===== * 命令行使用:https://www.ostechnix.com/20-ffmpeg-commands-beginners/ * 使用入门:https://www.ruanyifeng.com/blog/2020/01/ffmpeg.html * [[https://ffmpeg.guide/| 辅助工具,根据节点线框,生成对应的 FFmpeg 命令]] * FFmpeg 的命令可以分成五个部分:ffmpeg [$1] {[$2] -i $3} ... {[$4] $5} ... * $1: 全局参数 * $2: 输入文件参数 * $3: 输入文件 * $4: 输出文件参数 * $5: 输出文件 * 其中 输入部分 ''{[$2] -i $3}'' 可多个, 输出部分 ''{[$4] $5}'' 也可多个 * 常用命令行参数: * ''-c'': 指定编码器 * ''-c copy'':直接复制原编码,不重新编码(这样比较快) * ''-c:v'':指定视频编码器 * ''-c:a'':指定音频编码器 * ''-i'': 指定输入文件 * ''-an'': 去除音频流 * ''-vn'': 去除视频流 * ''-preset'':指定输出的视频质量,会影响文件的生成速度,有以下几个可用的值 ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow。 * ''-y'': 不经过确认,输出时直接覆盖同名文件。 ==== 常用指令 ==== * 列出支持的容器类型:''ffmpeg -formats'' * 列出支持的编码格式:''ffmpeg -codecs'' * 列出安装的编码器:''ffmpeg -encoders'' * 列出视频信息:''ffmpeg -i video.mp4'' * 隐藏ffpmeg 本身的信息: ''ffmpeg -hide_banner -i video.mp4'' * 提取音频:ffmpeg -i input.mp4 -vn -c:a copy output.aac * 合并音频:ffmpeg -i input.aac -i input.mp4 output.mp4 * 解码为YUV:ffmpeg -i input.mp4 -c:v rawvideo -pix_fmt yuv420p output.yuv * 单张图转视频,方法之一:ffmpeg -i 1.jpg -filter_complex color=s=1280x720:c=black[vbg];[0:v]scale=1280x720[sv];[vbg][sv]overlay[vout] -map [vout] -ss 0 -to 10 -y 1.mp4 * 生成画面带时间戳的测试视频:ffmpeg -f lavfi -i testsrc=duration=100:size=1280x720:rate=30:decimals=2 -pix_fmt yuv420p -vcodec libx264 output.mp4 生成空画面测试视频:ffmpeg -f lavfi -i color=c=blue:s=1280x720:r=30 -pix_fmt yuv420p ...详细看 ffmpeg 滤镜文档[[https://ffmpeg.org/ffmpeg-filters.html#toc-allrgb_002c-allyuv_002c-color_002c-haldclutsrc_002c-nullsrc_002c-pal75bars_002c-pal100bars_002c-rgbtestsrc_002c-smptebars_002c-smptehdbars_002c-testsrc_002c-testsrc2_002c-yuvtestsrc|ffmpeg-filters]]。如果是实时视频流,比如往v4l2推流,加 ''-re'' 参数来以视频原始速度来生成:ffmpeg -re -f lavfi -i color=c=blue:s=1280x720:r=30 -pix_fmt yuv420p -f v4l2 /dev/video0 * mp4 转 m3u8 ffmpeg -i input.mp4 -profile:v baseline -level 3.0 -start_number 0 -hls_time 10 -hls_list_size 0 -f hls .\outputDir\index.m3u8 * 只测试解码不保存ffmpeg -i input.mp4 -f null /dev/null * rtsp 推流,使用 [[https://github.com/bluenviron/mediamtx|mediamtx]] 在8554端口建立rtsp 服务,此时可用 ffmpeg 推流:ffmpeg -re -stream_loop -1 -i input.mp4 -vcodec copy -vbsf h264_mp4toannexb -f rtsp -rtsp_transport tcp rtsp://192.168.0.165:8554/chn_name * 按比例缩放并补边:ffmpeg -i input704x576.mp4 -vf "scale=384:216:force_original_aspect_ratio=decrease,pad=384:216:-1:-1:color=green" output.mp4 * 低延时播放测试:ffplay -v debug -x 640 -y 380 https://192.168.0.151:6161/dev0.flv -fflags nobuffer -analyzeduration 1000000 ===== FFmpeg编程 ===== ==== 硬解 ==== * ffmpeg 硬解:https://trac.ffmpeg.org/wiki/HWAccelIntro * 硬解示例:https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/hw_decode.c * VAAPI: [[https://trac.ffmpeg.org/wiki/Hardware/VAAPI|ffmpeg]], 测试方式:ffmpeg -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi -i input.mp4 -f null - 注意,此条命令只测试硬解效率,实际硬解时时间主要耗费在从GPU拷贝数据 ''av_hwframe_transfer_data'' 速率慢。详见 * [[https://zhuanlan.zhihu.com/p/69192869|采取GPU-COPY技术做Memory-Mapping]], * Intel 在 ffmpeg 上关于 QSV GPU-COPY 的提交:[[https://github.com/FFmpeg/FFmpeg/commit/5345965b3f088ad5acd5151bec421c97470675a4|lavc/qsvdec: Add GPU-accelerated memory copy support]] * [[https://zhuanlan.zhihu.com/p/62246545|FFMPEG+Intel QSV硬解的环境安装]] * 这有个关于硬解解码后内存拷贝性能的[[https://github.com/Intel-Media-SDK/MediaSDK/issues/1550|讨论串]],看懂它 * Ubuntu18.04里的 ''i965_dri_video.so'' 有点老不能支持比较新的intel cpu, 判断方法: * 执行 ''lspci'' ,记住 VGA 该行的 id, 比如下面的 ''3e98'' lspci | grep VGA 00:02.0 VGA compatible controller: Intel Corporation Device 3e98 (rev 02) * 在 [[https://github.com/intel/intel-vaapi-driver/blob/master/src/i965_pciids.h| i965_pciids.h]] 里查找上面所记的 ''0x3e98'', 如果有对应的id, 则可以编译对应新版本的 intel-vaapi-driver 来解决。如果没有,则大概需要使用 [[https://github.com/intel/media-driver|media-driver]] * 更新libva 及 i965_dri_video.so 步骤: # 编译对应版本的libva sudo apt install autoconf libtool build-essential pkg-config git clone https://github.com/intel/libva.git cd libva git checkout 2.13.0 # 改为你需要的对应版本 ./autogen.sh --prefix=/opt/intel/libva --libdir=/opt/intel/libva/lib make sudo make install # 编译 intel-vaapi-driver git cone https://github.com/intel/intel-vaapi-driver.git cd intel-vaapi-driver git checkout 2.4.1 # 改为你需要的对应版本 export PKG_CONFIG_PATH=/opt/intel/libva/lib/pkgconfig ./autogen.sh make sudo make install # 设置库路径就可以使用新驱动来硬解 export LD_LIBRARY_PATH=/opt/intel/libva/lib:$LD_LIBRARY_PATH ffmpeg -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi -i input.mp4 -f null - ==== Tips ==== * 编程入门博客:https://blog.csdn.net/leixiaohua1020 * [[https://blog.csdn.net/leixiaohua1020/article/details/38868499|最简单的基于FFMPEG+SDL2的视频播放器]] * 网络上流行的 ffmpeg 示例代码(包括上面的leixiaohua博客), 都未做错误处理,部分函数在 ffmpeg3.0 后都被标注为废弃,不要直接拷贝用在生产环境中。看懂流程后自己按官方文档或参考 ffplay 源码来写。 * 解码第一帧前把解码器pkt_timebase 设置为流的timebase ''decctx->pkt_timebase = stream->time_base'', 可抛弃一些样本,优化部分延时问题。设置为无缓存 ''AVFMT_FLAG_NOBUFFER'' 也可解决部分延时: AVFormatContext* pFormatCtx = avformat_alloc_context(); pFormatCtx->flags = AVFMT_FLAG_NOBUFFER; int ret = avformat_open_input(&pFormatCtx, ...); * ''avformat_open_input'' 默认是阻塞的,如果是 tcp 连接,可设置 ''listen_timeout''(单位秒) 或者 ''stimeout''(单位微秒) 来达到超时处理;如果是 udp 连接,则可以设置中断回调 ''pFormatCtx->interrupt_callback'' * 关于视频流初始解析时间: * ''avformat_find_stream_info'' 的返回时间和 ''pFormatCtx->probesize'' 与 ''pFormatCtx->max_analyze_duration'' 相关,哪个先达到就返回。 * 需要第一个关键帧信息的解码器(比如h264), 如果在 ''avformat_find_stream_info'' 期间内遇不到关键帧, 则 ''pix_fmt''、宽高等信息将为空, 需要等到实际解码循环时才能得到 * 所以h264解码时,如果流协议没有让发送端主动发送关键帧的功能,那么初始解析时间就取决于流对关键帧间隔的设置 * h264软解时, 得手动在 ''AVCodecContext'' 设置多线程才能利用多核CPU,这样1080P以上视频解码才不卡 codec_ctx_->thread_count = av_cpu_count(); codec_ctx_->thread_type = FF_THREAD_FRAME; * 第三方库比如 libx264 libopenh264 是在解码时直接指定了才会用到,并且 ffmpeg 软解 h264 时使用的是内置解码器,所以编译 ffmpeg 时可以酌情去掉这些第三方库依赖。 * 新的解码循环,''avcodec_send_packet'' 与 ''avcodec_receive_frame'' 并不是一一对应的,两者是异步分开的 * 例如h264软解时,解码器会缓存十几帧数据,即喂了十几个包才会出第一个帧,包与帧可按输入与接收的顺序来一一对应,或通过 pts 确定。 * ''frame->best_effort_timestamp'' 可能是更好的帧播放 timestamp. * 如果 ''avcodec_send_packet''时 packet参数 为 NULL,解码器进入 flush 模式,此时可循环 ''avcodec_receive_frame'' 获取缓存的frame, 直到返回 ''AVERROR_EOF'' * ''avcodec_flush_buffers'' 可直接清除解码器缓存帧,用于 seek 操作或切换流操作。 * ffmpeg 在 android 上解码音频:[[https://medium.com/@donturner/using-ffmpeg-for-faster-audio-decoding-967894e94e71|using-ffmpeg-for-faster-audio-decoding]] ==== 错误处理 ==== * ffmpeg 函数的返回值为 ''AVERROR'' 修饰,定义见 ''libavutil/error.h'' * 错误值可用 ''av_strerror'' 函数来得到文本描述字符串;参考 ffplay 代码: string AvStrError(int err) { char errbuf[128]; const char *errbuf_ptr = errbuf; if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) { errbuf_ptr = strerror(AVUNERROR(err)); } return string(errbuf_ptr); } * 大部分错误码为POSIX标准中错误码的负值 * 日志:提供了''av_log_set_callback''函数来设置日志回调函数,自行输出各等级日志,方便查看具体信息。回调函数必须线程安全。