FFmpeg
- 处理音视频开源框架
- C/C++编写
- 非常优秀的多媒体框架
- 解码,编码,转码,复用,解复用,过滤音视频数据
音视频的广泛应用
- 直播类:音视频会议,教育直播,娱乐/游戏直播等
- 短视频:抖音,快手,小咖秀等
- 网络视频:优酷,腾讯视频,爱奇艺等
- 音视频通话:微信,QQ,Skype等
- 视频监控
- 人工智能:人脸识别,智能音箱等
播放器架构
FFmpeg常用命令
FFmpeg处理流程
- 首先需要一个输入文件(.mp4/flv/avi),相当于一个装有信息的盒子
- 具有封装格式的数据
- 有音频数据
- 视频数据
- 字母数据
- 然后我们需要打开这个盒子(demuxer)
- 打开后盒子有
- 编码的视频数据
- 编码的音频数据
- 对压缩的数据进行解码(decoder)
- 解码的数据与原数据还是有所差别的
- 对解码的数据进行处理
- 720p->480p
- 加滤镜
- ……
- 再次编码成数据包(encoder)
- 封装成流行的格式(muxer)
- 输出数据
基本信息查询
- -version:显示版本
- -demuxers:显示可用的demuxers
- -muxers:显示可用的muxers
- -devices:显示可用的设备
- -codecs:显示所有编解码器
- -decoders:显示可用的解码器
- -encoders:显示可用的编码器
- -bsfs:显示比特流filer
- -formats:显示可用的格式
- -protocols:显示可用的协议
- -filters:显示可用的过滤器
- -pix_fmts:显示可用的像素格式
- -sample-fmts:显示可用的采样可是
- -layouts:显示channel名称
- -color:显示识别的颜色名称
录制命令
录制屏幕
ffmpeg -f avfoundation -i 1 -r 30 out.yuv
以上为一个录制屏幕的命令
- 参数介绍
- -f:指定使用使用 avfoundation库(mac系统下) 来录制
- -i:指定输入设备
- 1:设备索引值,1代表屏幕,0代表摄像头,等等
- -r:指定帧率为30
- out.yuv:采集的数据保存为yuv数据格式(一个无压缩的数据格式)
Input #0, avfoundation, from '1':
Duration: N/A, start: 19326.762000, bitrate: N/A
Stream #0:0: Video: rawvideo (UYVY / 0x59565955), uyvy422, 1920x1080, 1000k tbr, 1000k tbn, 1000k tbc
Stream mapping:
Stream #0:0 -> #0:0 (rawvideo (native) -> rawvideo (native))
Press [q] to stop, [?] for help
Output #0, rawvideo, to 'out.yuv':
Metadata:
encoder : Lavf58.20.100
Stream #0:0: Video: rawvideo (UYVY / 0x59565955), uyvy422, 1920x1080, q=2-31, 995328 kb/s, 30 fps, 30 tbn, 30 tbc
Metadata:
encoder : Lavc58.35.100 rawvideo
从上面Stream行可以发现:视频的像素格式是 uyvy422,大小是1920x1080
使用:ffplay -s 1920x1080 -pix_fmt uyvy422 out.yuv 播放视频
- -s:指定屏幕大小
- -pix_fmt:指定像素格式
ffmpeg -f avfoundation -list_devices true -i ""
- 查找avfoundation支持的设备
[AVFoundation input device @ 0x7fbb18d03e80] AVFoundation video devices:
[AVFoundation input device @ 0x7fbb18d03e80] [0] FaceTime HD Camera (Built-in)
[AVFoundation input device @ 0x7fbb18d03e80] [1] Capture screen 0
[AVFoundation input device @ 0x7fbb18d03e80] AVFoundation audio devices:
[AVFoundation input device @ 0x7fbb18d03e80] [0] Built-in Microphone
- 由上可知:
- avfoundation支持的视频(video)设备
- HD Camera,其设备索引值为 0
- Capture Screen,其索引值为 1
- 支持的音频(audio)
- Build—in Microphone,其索引值为 0
- avfoundation支持的视频(video)设备
录制声音
ffmpeg -f avfoundation -i :0 out.wav
- 参数介绍
- -f:使用的设备
- -i:指定输入设备
- :0:音频设备在
:
(冒号)之前指定,视频设备在 冒号 之后指定
分解/复用
- 各种个样文件格式之间的转换
- 从一个视频中单独抽取音频或画面
ffmpeg -i out.mp4 -vcodec copy -acodec copy out.flv
- 参数解释
- -i:输入文件
- -vcodec:设置视频编解码
- copy:表示不改变有编解码
- -acodec:设置音频编解码
- out.flv:输出文件
Metadata:
major_brand : mp42
minor_version : 1
compatible_brands: isommp423gp5
creation_time : 2018-11-02T08:07:34.000000Z
encoder : FormatFactory : www.pcfreetime.com
Duration: 00:06:24.08, start: 0.000000, bitrate: 356 kb/s
Stream #0:0(und): Video: mpeg4 (Simple Profile) (mp4v / 0x7634706D), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 228 kb/s, 14.91 fps, 14.91 tbr, 14906 tbn, 7453 tbc (default)
- 由命令行输出可知:
- Duration:视频长度 6分24秒,起始时间0.0秒,码率:356kb/s
- Stream:视频格式是yuv420p,大小是1920x1080,帧率事故14.91
ffpeg -i test.mov -an -vcodec copy out.h264
这是一个只要视频,不要音频的分割命令
- 参数介绍:
- -i:输入资源
- -an:autio No,不要音频资源
- -vn:vedio No,不要视频资源
- -vcodec:视频编码格式设置
处理原始数据命令
- 通过 FFmpeg 提取YUV数据
ffmpeg -i input.mp4 -an -c:v rawvideo -pix_fmt yuv420p out.yuv
-
参数解释:
- -an:audio No,输出结果不包括音频
- -c:v:对视频进行编码,用rawvideo进行编码
- -pix_fmt:像素格式为 yuv420p
-
FFmpeg 提取PCM数据
ffmpeg -i out.mp4 -vn -ar 44100 -ac 2 -f s16le out.pcm
- 参数解释:
- -vn:vedio No,不要视频
- -ar:audio read,音频采样率,指定采样率44.1k
- -ac:audio ,声道数目,单声道/双声道/立体声等
- -f:抽取出来的PCM数据的存储格式
ffplay -ar 44100 -ac 2 -f s16le out.pcm
- 这里告诉ffplay播放器 应该以何种方法播放 PCM 文件
掌握这些获取原始数据的命令,可以方便进行对比
通过ffmpeg做二次开发,需要提取出其中的yuv数据,做每一帧的分析等,需要知道这些命令
例如:声道反转等,需要知道这些原始数据
剪裁与合并命令
ffmpeg -i in.mp4 -ss 00:00:00 -t 10 out.ts
- 参数解释:
- -ss:视频从什么时间点开始裁剪–时:分:秒
- -t:截取点时间长度,单位为s
ffmpeg -f concat -i inputs.txt out.flv
- 参数解释:
- -f concat:表示对后面对文件进行拼接
- -i inputs.txt:inputs.txt中记录对是文件列表
- input.txt:内容为 “file filename”格式
input.txt文件
file '1.ts'
file '2.ts'
file '3.ts'
图片/视频互转命令
-
视频转图片
ffmpeg -i in.flv -r 1 -f image2 image-%3d.jpeg -
参数解释:
- -r:指定转换图片的帧率,每秒多少张图片
- -f:将输入文件转成什么格式,这转为image2格式
- image-%3d.jpeg:图片名字
-
图片转视频
ffmpeg -i image-%3d.jpeg out.mp4 -
参数解释:
- -i:输入文件列表
直播相关命令
- 推流
ffmpeg -re -i out.mp4 -c copy -f flv rtmp://server/live/streamName
-
参数解释:
- -re:减慢帧率速度,让它的帧率与真正的帧率保持同步
- -i:推出去的流的多媒体文件
- -c:音视频编辑码
- -f:推出去的文件格式
- rtmp的服务器地址
-
拉流
ffmpeg -i rtmp://server/live/streamName -c copy dump.flv
- 参数解释:
- -i:流的资源
- -c:音视频编码
ffmpeg -i rtmp://ivi.bupt.edu.cn/hls/cctv1hd.m3u8 -c copy out.flv
这样ffmpeg 就会从某个服务器中网本地存数据
各种滤镜命令
- 最复杂的一类命令,因为可是实现各种各样想要达到的效果
- 拿到解码后的数据枕
- 经过滤镜程序进行过滤
- 将过滤后的数据帧重新编码
ffmpeg -i test.mp4 -vf crop=in_w-200:in_h-200 -c:v libx264 -c:a copy out.mp4
- 参数解释:
- -vf:vedio fuilter,指定视频滤镜
- in_w:crop的参数,本身视频宽度
- in_w-200:本身视频宽度减去200
- in_h:本身视频的高度
- c:v:指定使用的视频的编码器,libx264
- c:a:指定使用的音频的编码器
初级开发内容
- ffmpeg代码结构
- libavcodec:提供了一系列编译器的实现
- libavformat:实现在流协议,容器格式及其IO访问
- libavutil:包括了hash器,解码器和各种工具函数
- libavfilter:提供了各种音频视频过滤器
- libavdevice:提供了访问捕获设备和回放设备的接口
- libswresample:实现了混音和重采样
- libswscale:实现了色彩转换和缩放功能
FFmpeg 日志的使用
#include <libavutil/log.h>
av_log_set_level(AV_LOG_DEBUG)
av_log(NULL, AV_LOG_INFO, "...%s\n", op);
-
这个的函数 av_log_set_level是设置输出日志的等级,凡是比这个参数等级高的都要输出
- AV_LOG_DEBUG是Debug级别的,是最低级的,也就意味着全部输出
- 还有ERROR,WARNING,INFO等
-
这个函数 av_log(NULL, AV_LOG_INFO, “…%s\n”, op)
- 第一个参数 一般设为NULL
- 第二个参数 设置打印日志的级别,同上
- 第三个参数 格式化输出
-
常用的日志级别
- AV_LOG_ERROR 等级最高
- AV_LOG_WARNING 等级次高
- AV_LOG_INFO 等级次低
- AV_LOG_DEBUG 等级最低
#include "libavutil/log.h"
#include <stdio.h>
int main(){
set_av_log_level(AV_LOG_DEBUG);
av_log(NULL, AV_LOG_INFO, "HelloWorld\n");
return 0;
}
文件删除与重命名
- avpriv_io_delete()
- 传入参数为删除文件地址
- 用于删除文件
- avpriv_io_move()
- 两个参数
- 第一个参数 原地址
- 第二个参数 移动后地址
- 可以移动或者重命名
- 两个参数
#include <libavformat/avformat.h>
#include "libavutil/log.h"
int main(){
set_av_log_level(AV_LOG_DEBUG);
int ret = avpriv_io_delete("./test.txt");
if(ret < 0){
av_log(NULL, AV_LOG_ERROR, "delete file error\n");
return -1;
}
ret = avpriv_io_move("11.txt", "22.txt");
if(ret < 0){
av_log(NULL, AV_LOG_ERROR, "move file error\n");
return -1;
}
return 0;
}
操作目录
- avio_open_dir();
- 打开一个文件夹
- 第一个参数 AVIODirContext 结构体指针
- 第二个参数 打开目录
- 第三个参数 一般为NULL
- avio_read_dir();
- 读取这个文件夹中每个文件/文件夹的信息
- avio_close_dir();
- 读完之后关闭dir
重要结构体
- AVIODirContext:操作目录的上下文
- AVIODirEntry:目录项,用于存放文件名,文件大小信息等
当调用 open_dir 的时候,ffmepg就会生成AVIODirContext结构体
read_dir的时候,就要传入这个 AVIODirContext结构体
close_dir的时候,传入的特使AVIODirContext
使用ffmpeg实现 ls 命令
#include <libavutil/log.h>
#include <libavformat/avformat.h>
int main(){
av_log_set_level(AV_LOG_INFO);
AVIODirContext *ctx = NULL;
AVIODirEntry *entry = NULL;
int ret = avio_open_dic(&ctx, "./", NULL);
if(ret < 0){
av_log(NULL, AV_LOG_ERROR, "Can't open dir %s\n", av_err2str(ret));
return -1;
}
while(1){
ret = avio_read_dir(ctx, &entry);
if(ret < 0){
av_log(NULL, AV_LOG_ERROR, "Can't read dir %s\n", av_err2str(ret));
return -1;
}
if(!entry){//读到目录最后一项
break;
}
av_log(NULL, AV_LOG_INFO, "%12"PRId64" %s \n",entry->size, entry->name );//打印大小和名字
}
return 0;
}
多媒体文件的基本概念
- 多媒体文件其实是个容器
- 存放了各种类型的数据:字幕,音频,视频。。。
- 容器里面有很多流(Stream/Track)(以前称之为轨)
- 每种流是由不同的编码器编码的
- 从流中读出的数据成为包
- 在一个包中包含着一个或多个帧
几个重要的结构体
- AVFormatContext:格式上下文,连接多个api的桥梁
- AVStream:流/轨,用于读取流
- AVPacket:用于截取流中的数据
执行顺序:解复用 -> 获取流 -> 读取数据包 -> 释放资源
打印 音/视 频信息
- av_register_all()
- 讲ffmpeg将ffmpeg中的协议,编解码库等全部注册到程序中去
- 必须调用的
- avformat_open_input()/avformat_cloase_input()
- 第一个参数:AVFormatContext结构体
- 第二个参数:要打开的文件
- 第三个参数:打开的文件格式,如果为NULL,则默认按照传入文件的后缀名解析
- 第四个参数:通过命令行传递参数,一般为NULL就行
- 打开一个多媒体文件,根据文件后缀名识别多媒体格式
- 输出一个结构体——AVFormatContext,格式上下文
- av_dump_format()
- 第一个参数:AVFormatContext
- 第二个参数:流的索引值,直接填 0
- 第三个参数:多媒体文件的名字
- 第四个参数:输入流(0)还是输出流(1)
- 将多媒体文件中的mate信息打印出来
#include <libavutil/log.h>
#include <libavformat/avformat.h>
int main(){
av_log_set_level(AV_LOG_INFO);
av_regester_all();
AVFormatContext *fmt_context = NULL;
int ret = avformat_open_input(&fmt_context, "./test.mp4", NULL);
if(ret < 0){
av_log(NULL, AV_LOG_ERROR, "con't open file %s\n", av_err2str(ret));//打印错误码
return -1;
}
av_dump_format(fmt_context, 0, "./test.mp4", 0);
avformat_close_input(&fmt_context);
return 0;
}
抽取音频数据
-
av_init_packet()
- 初始化数据包结构体
- 从多媒体文件中读取的每一个数据包都可以放在这里
-
av_find_best_stream()
- 找到最好的一路流
- 第一个参数:AVFormatContext
- 第二个参数:流的类型,AVMEDIA_TYPE_***,ffmpeg宏定义的
- 第三个参数:要处理的流的索引号,不知道填-1
- 第四个参数:相关的流的索引号,不知道填-1
- 第五个参数:流的编解码器,不知道填NULL
- 第六个参数:flag
-
av_read_frame()
- 将流中的数据包获取到,可以进行后续处理
- 从流中读取相应数据包后,数据包引用计数+1
- 第一个参数:AVFormatContext
- 第二个参数:使用的包,AVPacket
-
av_packet_unref()
- 数据包不用的话要引用计数-1
- 内存管理机制
#include <libavutil/log.h>
#include <libavformat/avformat.h>
void adts_header(char *szAdtsHeader, int dataLen){
int audio_object_type = 2;
int sampling_frequency_index = 7;
int channel_config = 2;
int adtsLen = dataLen + 7;
szAdtsHeader[0] = 0xff;
szAdtsHeader[1] = 0xf0;
szAdtsHeader[1] |= (0<<3);
szAdtsHeader[1] |= (0<<1);
szAdtsHeader[1] |= 1;
szAdtsHeader[2] = (audio_object_type - 1)<<6;
szAdtsHeader[2] |= (sampling_frequency_index & 0x0f)<<2;
szAdtsHeader[2] |= (0 << 1);
szAdtsHeader[2] |= (channel_config & 0x04)>>2;
szAdtsHeader[3] = (channel_ config & 0x03)<<6;
szAdtsHeader[3] |= (0 << 5) ;
szAdtsHeader[3] |= (0 << 4) ;
szAdtsHeader[3] |= (0 << 3) ;
szAdtsHeader[3] |= (0 << 2) ;
szAdtsHeader[3] |= ( (adtsLen & 0x1800) >> 11) ;
szAdtsHeader[4] = (uint8_ t)( (adtsLen & 0x7f8) >> 3) ;
szAdtsHeader[5] = (uint8_ t)( (adtsLen & 0x7) << 5);
szAdtsHeader[5] |= 0x1f ;
szAdtsHeader[6] = 0xfc;
}
int main(){
av_log_set_level(AV_LOG_INFO);
av_regester_all();
AVFormatContext *fmt_context = NULL;
int ret = avformat_open_input(&fmt_context, "./test.mp4", NULL);
if(ret < 0){
av_log(NULL, AV_LOG_ERROR, "con't open file %s\n", av_err2str(ret));//打印错误码
return -1;
}
av_dump_format(fmt_context, 0, "./test.mp4", 0);
avformat_close_input(&fmt_context);
// 获取 想要处理的流
int audio_index;
audio_index = av_find_best_stream(fmt_context, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);//如果audio_index >=0,则其代表找到是音频数据的索引值
if(audio_index < 0){
av_log(NULL, AV_LOG_ERROR, "con't file stream file %s\n", av_err2str(ret));//打印错误码
return -1;
}
// 打开一个可供写入的文件
FILE* dst_fd = fopen(“./test.aac”, "wb");
if(!dst_fd){
av_log(NULL, AV_LOG_ERROR, "Can't open out file\n");
avformat_close_input(&fmt_context);
return -1;
}
// 读取信息
AVPacket pkt;
av_init_packet(&pkt);
while(av_read_frame(fmt_context, &pkt) >= 0){// 要读取所有的数据帧,
if(audio_index == pkt.stream_index){// 如果相同则代表找到流了
// 添加一个头,表示如何播放分离出来的audio。如果不加上头,不知道如何播放
char adts_header_buf[7];
adts_header(adts_header_buf, pkt.size);
fwrite(dats_header_buf, 1, 7, dst_fd);
// 将获取的音频数据输出到一个新文件中
int len = fwrite(pkt.data, 1 , pkt.size, dst_fd);//写如文件
if(len != pkt.size){
av_log(NULL, AV_LOG_WARNING, "waning file write length data error");
}
}
av_packet_unref(pkt);//需要释放引用计数,防止内存泄漏
}
// 关闭文件
avformat_close_input(&fmt_context);
if(dst_fd){
fclose(dft_fd);
}
return 0;
}
抽取视频 H264 数据
-
Start Code
- 区分一个个的视频帧,及其顺序
- 每一帧前面都加上一个特征码(Start Code),以区分每一个帧的边界
-
SPS/PPS
- 解码的视频参数,包括视频帧的 宽高/帧率等等,都存在SPS/PPS中
- 理论上只要一个SPS/PPS,但是实际上需要多个SPS/PPS,因为视频播放时需要切换清晰度时,就需要更换SPS/PPS
- 在每个关键帧的前面都会增加SPS/PPS数据包(防止直播切换清晰度时,发来的SPS/PPS包丢了而导致错误)
-
codec->extradata
- 从codec结构的extradata中获取SPS/PPS
int main(){
int err_code;
char errors[1024];
char *src_filename = NULL;
char *dst_fileanme = NULL;
FILE *dst_fd = NULL;
int video_stream_index = -1;
AVFormatContext *fmt_ctx = NULL;
AVPacket pkt;
av_log_set_level(AV_LOG_DEBUG);
if(argc < 3){
av_log(NULL, AV_LOG_DEBUG, "the count of parameters shoudler be more than three~");
return -1;
}
src_filename = argv[1];
dsf_filename = argv[2];
if(src_filename == NULL || dsf_filename == NULL){
av_log(NULL, AV_LOG_ERROR, "src or dts file is null
\n");
return -1;
}
av_register_all();
dst_fd = fopen(dst_filename, "wb");
if(!dst_fd){
av_log(NULL, AV_LOG_ERROR, "Can't open destionation file\n");
return -1;
}
if((err_code = avformat_open_input(&fmt_ctx, src_filename, NULL, NULL)) < 0){
av_strerror(err_code, errors, 1024);
av_log(NULL, AV_LOG_DEBUG, "Con't open source file:%s ,%d(%s)\n", src_filename, err_code, errors);
return -1;
}
av_dump_format(fmt_ctx, 0, src_filename, 0);//打印多媒体信息
av_init_packet(&pkt);
pkt_data = NULL;
pkt_size = 0;
video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if(video_stream_index < 0){
av_log(NULL, AV_LOG_DEBUG, "Can't find %s stream in input file %s\n", av_get_media_type_string(AVMEDIA_TYPE_VIDEO), src_filename);
return AVERROR(EINVAL);
}
while(av_read_frame(fmt_ctx, &pkt) >= 0){
if(pkt.stream_index == video_stream_index){
h264_mp4toannexb(fmt_ctx, &pkt, dst_df);//自定义函数,设置 Start Code和SPS/PPSs,懒得写。。。。
}
av_packet_unref(&pkt);// 处理过的包就去掉引用
}
}
环境打死配不好,手敲要死了
感谢帮助
https://blog.csdn.net/lijjianqing/article/details/54707785
https://blog.csdn.net/zhouxj0818/article/details/52679741