ffmpeg入——安装
Fmpeg地址
FFmpeg源码地址:https://github.com/FFmpeg/FFmpeg
FFmpeg可执行文件地址:https://ffmpeg.org/download.html
Windows平台
Windows平台下载解压后如图所示(文件名称以-share结尾的是开发库)
FFmpeg库介绍
◆ ffmpeg.exe:用于音视频转码, 也可以从url/现场音频/视频源抓取输入源。
◆ ffplay.exe:一个非常简单和可移植的媒体播放器,使用FFmpeg库和SDL库。
◆ ffprobe.exe:查看多媒体文件的信息
将FFmpeg可执行文件加入系统环境变量,如下图所示:
Linux
ffmpeg官方 GitHub - FFmpeg/FFmpeg: Mirror of https://git.ffmpeg.org/ffmpeg.git
下载压缩包 或者 直接clone
- 直接clone:git clone https://github.com/FFmpeg/FFmpeg.git
- 下载完成,执行./configure
如果出现了错误 nasm/yasm not found or too old. Use --disable-x86asm for a crippled build.
。
这是因为 FFMPEG为了提高编译速度,使用了汇编指令,如MMX和SSE等。如果系统中没有yasm
指令的话,就会该错误。
1)下载:wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
2)解压:tar zxvf yasm-1.3.0.tar.gz
3)切换路径: cd yasm-1.3.0
4)执行配置: ./configure
5)编译:make
6)安装:make install
3.make && sudo make install
测试代码:
#include<stdio.h>
#include<libavutil/log.h>
int main()
{
av_log_set_level(AV_LOG_DEBUG); //设置日志等级
av_log(NULL, AV_LOG_INFO, "Hello~~~\n"); // INFO等级,输出hello
return 0;
}
编译链接: gcc -o ff_log ff_log.c -lavutil
或者 gcc -o ff_log ff_log.c `pkg-config --cflags --libs libavutil`
(“pkg-config工具:1)会检查库的版本号。如果所需要的库的版本不满足要求,它会打印出错误信息,避免链接错误版本的库文件;2)会获得编译预处理参数,如宏定义,头文件的位置。3)还会获得链接参数,如库及依赖的其它库的位置,文件名及其它一些连接参数)
选项--cflags 它是用来指定程序在编译时所需要头文件所在的目录, 选项 --libs则是指定程序在链接时所需要的动态链接库的目录。
ffmpeg——log日志系统
log日志位于libavutil模块,log level的声明位于log.h
日志级别:
// 静默模式,不打印日志
#define AV_LOG_QUIET -8
// 立即崩溃,退出程序
#define AV_LOG_PANIC 0
// 严重出错,无法修复
#define AV_LOG_FATAL 8
// 程序出错
#define AV_LOG_ERROR 16
// 警告
#define AV_LOG_WARNING 24
// 信息
#define AV_LOG_INFO 32
// 详细信息
#define AV_LOG_VERBOSE 40
// 调试日志
#define AV_LOG_DEBUG 48
// 跟踪日志
#define AV_LOG_TRACE 56
设置日志等级的方法set_log_level()位于log.c,其中av_log_level是个静态全局变量,具体如下
static int av_log_level = AV_LOG_INFO;
void av_log_set_level(int level)
{
av_log_level = level;
}
常用的打印日志方法av_log(),声明位于log.h:
/**
* 发送特定消息到小于等于当前等级的日志,默认全部发送到stderr
*
* @param avcl 指向任意结构体的指针,结构体第一个变量为AVClass或NULL
* @param level 日志等级
* @param fmt 字符串格式
*/
void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);
ffmpeg——文件的删除与重命名
- avpriv_io_delete() //删除
- avpriv_io_move() //重命名
编译链接: gcc xxx.c -o xxx -lavformat -lavutil
#include <libavformat/avformat.h>
int main(int argc,char *argv[])
{
int ret;
set_av_log_level(AV_LOG_DEBUG);
ret = avpriv_io_move("111.txt","222.txt");
if(ret < 0)
{
av_log(NULL,AV_LOG_ERROR,"Failed to move file:%s\n","111.txt");
return -1;
}
ret = avpriv_io_delete("./mytestfile.txt");
if(ret < 0)
{
av_log(NULL,AV_LOG_ERROR,"Failed to delete file:%s\n","mytestfile.txt");
return -1;
}
av_log(NULL,AV_LOG_DEBUG,"Success to delete file:%s\n","mytestfile.txt");
return 0;
}
ffmpeg——目录操作
重要结构体:
AVIODirContext 操作目录的上下文(记录avio_open_dir打开目录信息)
AVIODirEntry 目录项。用于存放文件名,文件大小等信息
操作目录API:
- avio_open_dir()
- avio_read_dir()//读取目录中每一个文件的属性
- avio_close_dir()
编译链接: gcc xxx.c -o xxx -lavformat -lavutil
// 实现简单的ls命令
#include <libavutil/log.h>
#include <libavformat/avformat.h>
int main(int argc,char* argv[])
{
int ret;
AVIODirContext *ctx = NULL;
AVIODirEntry *entry = NULL;
av_log_set_level(AV_LOG_INFO);
ret = avio_open_dir(&ctx,"./",NULL);
if(ret < 0){
av_log(NULL,AV_LOG_ERROR,"Cant open dir:%s\n",av_err2str(ret));
return -1;
}
while(1){
ret = avio_read_dir(ctx,&entry);//malloc entry
if(ret < 0){
av_log(NULL,AV_LOG_ERROR,"Cant read dir:%s\n",av_err2str(ret));
avio_close_dir(&ctx);
return -1;
}
if(!entry){
//the end
break;
}
av_log(NULL,AV_LOG_INFO,"%12"PRId64" %s\n",
entry->size,
entry->name);
avio_free_directory_entry(&entry);//free entry
}
avio_close_dir(&ctx);
return 0;
}
ffmpeg——重要的结构体
- AVIOContext(I/O上下文) // 实现文件或者流的I/O操作
- AVFormatContext(封装格式上下文) // 管理媒体文件的格式和封装信息
- AVCodecContext(编解码器上下文) // 控制和配置音视频编解码器的行为
- AVStream(音视频流) // 描述音视频流的属性
- AVPacket (数据包) // 用于在媒体文件、流之间传递数据
- AVFrame(帧) // 存储和传递音视频数据
- AVDictionary(字典) // 存储配置选项和元数据
- AVFilterContext(滤镜上下文) // 处理音视频数据,应用滤镜效果。
- SwsContext(图像转换上下文) // 进行图像处理和转换
- SwrContext(音频重采样上下文) // 进行音频处理和转换
AVIOContext结构体,用于管理媒体文件或者网络流的输入和输出操作
AVCodecContext结构体,包含了音频和视频编解码器的配置信息,如编码参数、解码参数、码率控制等。部分字段说明:
codec | 编解码器的AVCodec,比如指向AVCodec ff_aac_latm_decoder |
width, height | 图像的宽高(只针对视频) |
pix_fmt | 像素格式(只针对视频) |
sample_rate | 采样率(只针对音频) |
channels | 声道数(只针对音频) |
sample_fmt | 采样格式(只针对音频 |
AVFormatContext结构体,是与多媒体文件格式相关的结构体,用于打开、读取和写入媒体文件。它包含了文件的格式信息、音视频流、文件I/O操作等。部分字段说明:
struct AVInputFormat* iformat | 输入媒体的AVInputFormat(输入数据的封装格式),比如指向AVInputFormatff_flv_demuxer |
struct AVOutputFormat *oformat | 用于指定输出文件的格式以及文件写入的操作函数 |
AVIOContext *pb | 用于读取和写入媒体数据的 I/O 上下文 |
unsigned int nb_streams | 输入媒体的AVStream 个数 |
AVStream** streams | 输入媒体的AVStream []数组 |
int64_t start_time, duration | 媒体文件的起始时间戳和持续时间(以微秒为单位),计算方式可以参考av_dump_format()函数 |
int bit_rate | 输入媒体的码率 |
AVStream结构体,代表了音频或视频流,包括编解码参数、时间基准、时间戳等信息。部分字段说明
index | 标识该视频/音频流 |
time_base | 该流的时基, PTS*time_base=真正的时间(秒) |
avg_frame_rate | 该流的帧率 |
duration | 该视频/音频流长度 |
codecpar | 编解码器参数属性 |
AVPacket结构体,用于存储音频或视频数据包,包括编码后的数据和时间戳。部分字段说明:
pts | 显示时间戳 |
dts | 解码时间戳 |
data | 压缩编码数据 |
size | 压缩编码数据大小 |
pos | 数据的偏移地址 |
stream_index | 所属的AVStream |
AVFrame结构体,用于存储音频或视频帧的数据。它包括像素数据、采样数据、时间戳等。部分字段说明:
data | 解码后的图像像素数据(音频采样数据) |
linesize | 对视频来说是图像中一行像素的大小;对音频来说是整个音频帧的大小 |
width, height | 图像的宽高(只针对视频) |
key_frame | 是否为关键帧(只针对视频) |
pict_type | 帧类型(只针对视频) 。例如I, P, B |
sample_rate | 音频采样率(只针对音频) |
nb_samples | 音频每通道采样数(只针对音频) |
pts | 显示时间戳 |
AVDictionary结构体,用于存储键值对,通常用于配置选项的传递和存储。
SwrContext结构体,用于音频重采样,支持不同采样率、通道数之间的转换
SwsContext结构体,用于图像转换,支持不同的像素格式和大小之间的转换
AVFilterContext结构体,用于配置和管理滤镜,包括音频滤镜和视频滤镜。
播放器框架
有关参考常用api:
https://www.cnblogs.com/linuxAndMcu/p/12041359.html
官方api文档:FFmpeg: Main Page
ffmpeg——多媒体文件简单操作
多媒体文件是一个容器(也可以理解为包装盒,常见的mp4、flv、mkv都是不同的包装盒 ),可以存放很多数据,最常见的有字幕数据、视频数据、音频数据。
在容器中有很多流(Stream/Track),流媒体文件中不同的流之数据不会相交的,比如音频流和视频流就是各自独立的。
每种流是由不同的编码器编码的,每条流数据在多媒体文件中是经过压缩的,但是其使用压缩的编码器都是不一样的。比如音频有可能是MP3,有可能是AAC,视频常见的压缩器有可能是H264或者H265。
从流中读出的数据称为包,在一个包中包含着一个或者多个帧。
ffmpeg操作流数据的基本步骤:
提取音、视频数据
//提取多媒体文件的音频数据
//编译链接:gcc -o extra_audio extra_audio.c `pkg-config --libs --cflags libavutil libavformat libavcodec`
//执行 ./extra_audio test.mp4 1.aac
#include<stdio.h>
#include<libavutil/log.h>
#include <libavformat/avformat.h>
int main(int argc, char* argv[])
{
int ret = -1;
int idx = -1;
// 1. 处理输入参数
char* src, * dst;
AVFormatContext* pFmtCtx = NULL; // 多媒体上下文
AVFormatContext* oFmtCtx = NULL; // 目标文件上下文信息
const AVOutputFormat* outFmt = NULL; // 输出文件格式信息
AVStream* outStream = NULL; //输出文件的流
AVStream* inStream = NULL; //输入文件的流
AVPacket pkt; // 包
av_log_set_level(AV_LOG_DEBUG);
if (argc < 3) { //该可执行程序 源文件 目标文件
av_log(NULL, AV_LOG_INFO, "Arguments must be more than 3.");
exit(-1);
}
src = argv[1];
dst = argv[2];
// 2、打开多媒体文件(包含文件头和文件体)
if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL)))
{
av_log(NULL, AV_LOG_ERROR, "%s\n", av_err2str(ret));
exit(-1);
}
// 3、从多媒体文件中找到音频流
idx = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); // 在文件中找到“最佳”流。AVMEDIA_TYPE_AUDIO 提取音频 //视频 AVMEDIA_TYPE_VIDEO
if (idx < 0) {
av_log(pFmtCtx, AV_LOG_ERROR, "Does not include audio stream\n");
goto _ERROR;
}
// 4、打开目的文件的上下文
oFmtCtx = avformat_alloc_context();
if (!oFmtCtx) {
av_log(NULL, AV_LOG_ERROR, "No Memory!\n");
goto _ERROR;
}
//设置输出文件的参数
outFmt = av_guess_format(NULL, dst, NULL); // 返回与所提供参数最匹配的已注册输出格式列表中的输出格式(NULL-系统匹配)
oFmtCtx->oformat = outFmt;
// 5、为目的文件创建一个新的音频流
outStream = avformat_new_stream(oFmtCtx, NULL);
// 6、设置输出音频参数
inStream = pFmtCtx->streams[idx];
avcodec_parameters_copy(outStream->codecpar, inStream->codecpar); //将源文件的内容复制到目的文件
outStream->codecpar->codec_tag = 0; // 根据多媒体文件自动识别编解码器
//上下文信息与输出文件绑定
ret = avio_open2(&oFmtCtx->pb, dst, AVIO_FLAG_WRITE, NULL,NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "%s", av_err2str(ret));
goto _ERROR;
}
// 7、写多媒体文件头(包含多媒体的类型、版本等信息)到目标文件
ret = avformat_write_header(oFmtCtx, NULL);
if (ret < 0) {
av_log(oFmtCtx, AV_LOG_ERROR, "%s", av_err2str(ret));
goto _ERROR;
}
// 8、从源多媒体文件中读到音频数据
while (av_read_frame(pFmtCtx, &pkt) >= 0) { // 从多媒体文件读取到帧数据
if (pkt.stream_index == idx) { // 判断是否是我们需要的流(之前找到的音频流)
// 修改目标文件时间戳 pts dts 时长 duration
pkt.pts = av_rescale_q_rnd(pkt.pts, inStream->time_base, outStream->time_base, (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = pkt.pts; // 音频文件dts 和 pts 相等,视频需要通过av_rescale_q_rnd()计算dts
pkt.duration = av_rescale_q(pkt.duration, inStream->time_base, outStream->time_base);
pkt.stream_index = 0; // 流编号信息
pkt.pos = -1; // 偏移位置
av_interleaved_write_frame(oFmtCtx, &pkt); // 将音频帧写入目标文件中
av_packet_unref(&pkt);
}
}
// 9、写多媒体文件尾到文件中
av_write_trailer(oFmtCtx);
// 10、将申请的资源释放掉
_ERROR:
if (pFmtCtx) {
avformat_close_input(&pFmtCtx);
pFmtCtx = NULL;
}
if (oFmtCtx->pb) {
avio_close(oFmtCtx->pb);
}
if (oFmtCtx) {
avformat_free_context(oFmtCtx);
oFmtCtx = NULL;
}
return 0;
}
//提取多媒体文件的视频数据
//编译链接:gcc -o extra_video extra_video.c `pkg-config --libs --cflags libavutil libavformat libavcodec`
//执行 ./extra_video test.mp4 2.h264
#include<stdio.h>
#include<libavutil/log.h>
#include <libavformat/avformat.h>
int main(int argc, char* argv[])
{
int ret = -1;
int idx = -1;
// 1. 处理输入参数
char* src, * dst;
AVFormatContext* pFmtCtx = NULL; // 多媒体上下文
AVFormatContext* oFmtCtx = NULL; // 目标文件上下文信息
const AVOutputFormat* outFmt = NULL; // 输出文件格式信息
AVStream* outStream = NULL; //输出文件的流
AVStream* inStream = NULL; //输入文件的流
AVPacket pkt; // 包
av_log_set_level(AV_LOG_DEBUG);
if (argc < 3) { //该可执行程序 源文件 目标文件
av_log(NULL, AV_LOG_INFO, "Arguments must be more than 3.");
exit(-1);
}
src = argv[1];
dst = argv[2];
// 2、打开多媒体文件(包含文件头和文件体)
if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL)))
{
av_log(NULL, AV_LOG_ERROR, "%s\n", av_err2str(ret));
exit(-1);
}
// 3、从多媒体文件中找到视频流
idx = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); // 在文件中找到“最佳”流。
if (idx < 0) {
av_log(pFmtCtx, AV_LOG_ERROR, "Does not include audio stream\n");
goto _ERROR;
}
// 4、打开目的文件的上下文
oFmtCtx = avformat_alloc_context();
if (!oFmtCtx) {
av_log(NULL, AV_LOG_ERROR, "No Memory!\n");
goto _ERROR;
}
//设置输出文件的参数
outFmt = av_guess_format(NULL, dst, NULL); // 返回与所提供参数最匹配的已注册输出格式列表中的输出格式(NULL-系统匹配)
oFmtCtx->oformat = outFmt;
// 5、为目的文件创建一个新的视频流
outStream = avformat_new_stream(oFmtCtx, NULL);
// 6、设置输出视频参数
inStream = pFmtCtx->streams[idx];
avcodec_parameters_copy(outStream->codecpar, inStream->codecpar); //将源文件的内容复制到目的文件
outStream->codecpar->codec_tag = 0; // 根据多媒体文件自动识别编解码器
//上下文信息与输出文件绑定
ret = avio_open2(&oFmtCtx->pb, dst, AVIO_FLAG_WRITE, NULL, NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "%s", av_err2str(ret));
goto _ERROR;
}
// 7、写多媒体文件头(包含多媒体的类型、版本等信息)到目标文件
ret = avformat_write_header(oFmtCtx, NULL);
if (ret < 0) {
av_log(oFmtCtx, AV_LOG_ERROR, "%s", av_err2str(ret));
goto _ERROR;
}
// 8、从源多媒体文件中读到视频数据
while (av_read_frame(pFmtCtx, &pkt) >= 0) { // 从多媒体文件读取到帧数据
if (pkt.stream_index == idx) { // 判断是否是我们需要的流(之前找到的视频流)
// 修改目标文件时间戳 pts dts 时长 duration
pkt.pts = av_rescale_q_rnd(pkt.pts, inStream->time_base, outStream->time_base, (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, inStream->time_base, outStream->time_base, (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration, inStream->time_base, outStream->time_base);
pkt.stream_index = 0; // 流编号信息
pkt.pos = -1; // 偏移位置
av_interleaved_write_frame(oFmtCtx, &pkt); // 将视频帧写入目标文件中
av_packet_unref(&pkt);
}
}
// 9、写多媒体文件尾到文件中
av_write_trailer(oFmtCtx);
// 10、将申请的资源释放掉
_ERROR:
if (pFmtCtx) {
avformat_close_input(&pFmtCtx);
pFmtCtx = NULL;
}
if (oFmtCtx->pb) {
avio_close(oFmtCtx->pb);
}
if (oFmtCtx) {
avformat_free_context(oFmtCtx);
oFmtCtx = NULL;
}
return 0;
}
用到API:
/* 打开一个输入流并读取报头。编解码器没有打开。流必须用avformat_close_input()关闭。
参数:
@param ps指向用户提供的AVFormatContext(由avformat_alloc_context)。可能是指向NULL的指针,在这种情况下,AVFormatContext由this分配函数并写入ps。注意,用户提供的AVFormatContext将被释放失败。
@param url打开的流的url。
@param fmt如果非null,该参数强制使用特定的输入格式。否则格式自动检测。
@param options一个由AVFormatContext和demuxer-private填充的字典选项。返回时,此参数将被销毁并替换为包含未找到的选项的字典。可能为NULL。
成功时返回0,失败时返回负AVERROR。
*/
int avformat open input(AVFormatContext *ps,
const char *url,
const AVinputFormat *fmt,
AVDictionary **options)
//分配一个AVFormatContext。
// avformat_free_context()可以用来释放上下文和框架在其中分配的所有内容。
AVFormatContext *avformat_alloc_context(void);
/*
在文件中找到“最佳”流。最佳流是根据各种启发式确定的,因为最可能是用户期望的。如果decoder参数非null, av_find_best_stream将为流的编解码器找到默认的解码器;找不到解码器的流将被忽略。
参数:
* @param ic 媒体文件句柄
* @param type 视频,音频,字幕等
* @param wanted_stream_nb 用户请求的流编号或-1为自动选择
* @param related_stream 尝试找到一个流相关(例如:在同一个程序中)到这个,如果没有,则为-1
* @param decoder_ret 如果非null,返回所选流的解码器
* @param flags 当前没有定义
@return
如果成功,返回非负的流号,如果没有找到请求类型的流,返回AVERROR_STREAM_NOT_FOUND,如果找到流但没有解码器,返回AVERROR_DECODER_NOT_FOUND
@note
如果av_find_best_stream返回成功并且decoder_ret不是NULL,那么*decoder_ret保证被设置为有效的AVCodec。
*/
int av_find_best_stream(AVFormatContext *ic,
enum AVMediaType type,
int wanted_stream_nb,
int related_stream,
const AVCodec **decoder_ret,
int flags);
/*返回与所提供参数最匹配的已注册输出格式列表中的输出格式,如果没有匹配则返回NULL。
@param short_name if non-NULL检查short_name是否与注册格式的名称匹配
@param filename if non-NULL检查filename是否以注册格式的扩展名结束
@param mime_type if non-NULL检查mime_type是否与注册格式的MIME类型匹配
*/
const AVOutputFormat *av_guess_format(const char *short_name,
const char *filename,
const char *mime_type);
/*向媒体文件添加新的流。
在解模时,由read_header()中的解模器调用。如果标志AVFMTCTX_NOHEADER在s.ctx_flags中设置,那么它也可以在read_packet()中被调用
当复用时,应该在avformat_write_header()之前由用户调用。
用户需要调用avformat_free_context()来清理分配,由avformat_new_stream()。
参数:
@param s 媒体文件句柄
@param c 未使用,不做任何事情
@return 新创建的流,错误时返回NULL。
*/
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
/*
***创建并初始化一个AVIOContext来访问url表示的资源。
参数:
@param s 用于返回指向创建的AVIOContext的指针。在失败的情况下,指向的值被设置为NULL。
@param url 要访问的资源
@param flags 控制如何打开url所指示的资源的标志
@param int_cb 在协议级别使用的中断回调
@param options 私有选项的字典,返回时此参数将被销毁并替换为包含未找到选项的字典可能为NULL。
@return >= 0如果成功,则为an对应的负值失败时的AVERROR代码
注:当url所指示的资源以读+写方式打开时,AVIOContext只能用于写。
*/
int avio_open2(AVIOContext **s, const char *url, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options);
/*
*** 返回流的下一帧。此函数返回存储在文件中的内容,而不验证是否存在用于解码器的有效帧。它将把存储在文件中的内容分割成帧,并为每次调用返回一个帧。它不会省略有效帧之间的无效数据,以便为解码器提供可能用于解码的最大信息。
如果成功,返回的数据包将被引用计数(pkt->但已设置)并无限期有效。当不再需要该数据包时,必须使用av_packet_unref()释放该数据包。对于视频,数据包只包含一帧。对于音频,如果每帧有一个已知的固定大小(例如PCM或ADPCM数据),它包含一个整数帧数。如果音频帧具有可变大小(例如MPEG音频),则它包含一个帧。
pkt->pts, pkt->dts和pkt->duration在AVStream中总是设置为正确的值。Time_base单位(并猜测格式是否不能提供它们)。如果视频格式有b帧,pkt->pts可以是AV_NOPTS_VALUE,所以如果不解压缩有效载荷,最好依赖pkt->dts。
return: 如果OK则返回0,错误或文件结束时返回< 0。如果出现错误,pkt将为空白(就好像它来自av_packet_alloc())。
注:注意PKT将被初始化,所以它可能是未初始化的,但它不能包含需要释放的数据。
*/
int av_read_frame(AVFormatContext *s, AVPacket *pkt);