FFmpeg

FFmpeg是一套可以用来记录、处理数字音频、视频,并将其转换为流的开源框架,采用LPL或GPL许可证,提供了录制、转换以及流化音视频的完整解决方案

1、FFmpeg编译选项详解

FFmpeg和大部分的GNU软件的编译方式类似,都是通过configure脚本来实现编译

前定制的,这种方式允许用户在编译前对软件进行裁剪,同时通过对最终运行到的

系统以及目标平台的配置来决定对某些模块设定合适的配置。

标准选项:GNU软件配置项目,例如安装路径、--prefix=…等。

编译、链接选项:默认配置是生成静态库而不是生成动态库,例如--disable-static、--enable-shared等。

可执行程序控制选项:决定是否生成FFmpeg、ffplay、ffprobe和ffserver等。

模块控制选项:裁剪编译模块,包括整个库的裁剪,例如--disable-avdevice;
一组模块的筛选,例如--disable-decoders;单个模块的裁剪,例如--disable-demuxer。

能力展示选项[图片上传中...(屏幕快照 2019-09-05 上午9.21.53.png-7df104-1567646545418-0)]
:列出当前源代码支持的各种能力集,例如--list-decoders、--list-encoders。

其他:允许开发者深度定制,如交叉编译环境配置、自定义编译

默认的编译会生成4个可执行文件和8个静态库。

可执行文件:

1、ffmpeg:转码、推流、媒体文件
2、ffplay:播放媒体文件,需要libSDL的预先编译
3、ffprobe: 获取文件详细信息
4、ffserver:作为简单流媒体服务器

8个静态库:

AVUtil:基础的模块之一,基本的音视频处理操作。

AVFormat:文件格式(avi、MP4、flv、mov等)和协议库,封装了Protocol层和Demuxer、Muxer层。

AVCodec:编解码库,例如MPEG-4。可以将其他的第三方的Codec以插件的方式添加进来,libx264、FDK-AAC、lame等库。

AVFilter:音视频滤镜库,包括音频特效和视频特效的处理。

AVDevice:输入输出设备库,比如,需要编译出播放声音或者视频的工具ffplay,需要该类。

SwrRessample:该模块可用于音频重采样,可以对数字音频进行声道数、数据格式、采样率等多种基本信息的转换。

SWScale:该模块是将图像进行格式转换的模块,比如,可以将YUV的数据转换为RGB的数据。

PostProc:该模块可用于进行后期处理,当我们使用AVFilter的时候需要打开该模块的开关,因为Filter中会使用到该模块的一些基础函数。

2、FFmpeg安装(安装命令行工具)

ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)" 安装homebrewbrew install ffmpeg 安装ffmpeg

常用的命令行工具:

ffmpeg是进行媒体文件转码的命令行工具,ffprobe是用于查看媒体文件头信息的工具,ffplay则是用于播放媒体文件的工具。

1、ffprobe 查看流媒体格式

首先用ffprobe查看一个音频的文件:
ffprobe ~/Desktop/output.mp3 Duration: 00:00:09.44, bitrate: 77 kb/s
表明该音频文件的时长是09秒44毫秒,开始播放时间是0,整个媒体文件的比特率是77Kbit/s Stream #0:0: Audio: aac (LC), 44100 Hz, mono, fltp, 77 kb/s

第一个流是音频流,编码格式是aac格式,采样率是44.1kHz,声道是mono,采样表示格式是fltp),这路流的比特率是77Kbit/s。 ...有的视频中是有多路音频文件的,比如国语或者是粤语、英语等然后再使用ffprobe查看一个视频的文件:
ffprobe ~/Desktop/aroey.mp4 Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf58.20.100
这行信息表明了该文件的Metadata信息,比如encoder是Lavf55.12.100,其中Lavf代表的是FFmpeg输出的文件,后面的编号代表了FFmpeg的版本代号,接下来的一行信息如下: Duration: 00:00:09.94, start: 0.000000, bitrate: 5166 kb/s
Duration是0分09秒94毫秒,开始播放的时间是从0ms开始播放的,整个文件的比特率是5166Kbit/s Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1080x1920, 5100 kb/s, 29.97 fps, 29.97 tbr, 30k tbn, 59.94 tbc (default)
这行信息表示第一个stream是视频流,编码方式是H264的格式(封装格式是AVC1),每一帧的数据表示是YUV420P的格式,分辨率是1080x1920,这路流的比特率是5100Kbit/s,帧率是每秒钟29.97帧(fps是29.97)其他音视频文件信息

2、ffplay

ffplay是以FFmpeg框架为基础,外加渲染音视频的库libSDL来构建的媒体文件播放器。

ffplay 32037.mp3
播放音频ffplay 32037.mp4
播放视频,业内的ijkplayer播放器就是基于ffplay开发的其他播放相关的信息

3、ffmpeg

ffmpeg强大的媒体文件转换工具。它可以转换任何格式的媒体文件,并且还可以用自己的AudioFilter以及VideoFilter进行处理和编辑。

通用参数
-f fmt:指定格式(音频或者视频格式)。
-i filename:指定输入文件名,在Linux下当然也能指定:0.0(屏幕录制)或摄像头。
-y:覆盖已有文件。
-t duration:指定时长。
-fs limit_size:设置文件大小的上限。
-ss time_off:从指定的时间(单位为秒)开始,也支持[-]hh:mm:ss[.xxx]的格式。
-re:代表按照帧率发送,尤其在作为推流工具的时候一定要加入该参数,否则ffmpeg会按照最高速率向流媒体服务器不停地发送数据。
-map:指定输出文件的流映射关系。例如:“-map 1:0-map 1:1”要求将第二个输入文件的第一个流和第二个流写入输出文件。如果没有-map选项,则ffmpeg采用默认的映射关系。视频参数
-b:指定比特率(bit/s),ffmpeg是自动使用VBR的,若指定了该参数则使用平均比特率。
-bitexact:使用标准比特率。
-vb:指定视频比特率(bits/s)。
-r rate:帧速率(fps)。
-s size:指定分辨率(320×240)。

-aspect aspect:设置视频长宽比(4:3,16:9或1.3333,1.7777)。
-croptop size:设置顶部切除尺寸(in pixels)。
-cropbottom size:设置底部切除尺寸(in pixels)。
-cropleft size:设置左切除尺寸(in pixels)。
-cropright size:设置右切除尺寸(in pixels)。
-padtop size:设置顶部补齐尺寸(in pixels)。
-padbottom size:底补齐(in pixels)。
-padleft size:左补齐(in pixels)。
-padright size:右补齐(in pixels)。
-padcolor color:补齐带颜色(000000-FFFFFF)。
-vn:取消视频的输出。
-vcodec codec:强制使用codec编解码方式('copy'代表不进行重新编码)。音频参数
-ab:设置比特率(单位为bit/s,老版的单位可能是Kbit/s),对于MP3格式,若要听到较高品质的声音则建议设置为160Kbit/s(单声道则设置为80Kbit/s)以上。
-aq quality:设置音频质量(指定编码)。
-ar rate:设置音频采样率(单位为Hz)。
-ac channels:设置声道数,1就是单声道,2就是立体声。
-an:取消音频轨。
-acodec codec:指定音频编码('copy'代表不做音频转码,直接复制)。
-vol volume:设置录制音量大小(默认为256)。

简单使用
转化格式: ffmpeg -i 目标地址.webm 最终.mp4


分离视频: ffmpeg -i 目标地址.mp4 -vcodec copy -an 最终.mp4
分离音频: ffmpeg -i 目标地址.mp4 -acodec copy -vn 最终.aac

3、FFmpeg交叉编译生成IOS/Android端的代码API

下载编译FFmpeg所需要的脚本文件gas-preprocessor.pl 下载地址: https://github.com/libav/gas-preprocessor 复制gas-preprocessor.pl到/usr/sbin下,(这个应该是复制到/usr/local/bin) 修改文件权限:chmod 777 /usr/local/bin/gas-preprocessor.pl下载脚本FFmpeg脚本 地址: https://github.com/kewlbear/FFmpeg-iOS-build-script 解压,找到文件 build-ffmpeg.sh 执行服本文件:./build-ffmpeg.sh

介绍几个术语:

容器/文件(Conainer/File):即特定格式的多媒体文件,比如MP4、flv、mov等。

媒体流(Stream):表示时间轴上的一段连续数据,如一段声音数据、一段视频数据或一段字幕数据,可以是压缩的,也可以是非压缩的,压缩的数据需要关联特定的编解码器。

数据帧/数据包(Frame/Packet):通常,一个媒体流是由大量的数据帧组成的,对于压缩数据,帧对应着编解码器的最小处理单元,分属于不同媒体流的数据帧交错存储于容器之中。

编解码器:编解码器是以帧为单位实现压缩数据和原始数据之间的相互转换的。

其中AVFormatContext就是对容器或者说媒体文件层次的一个抽象,该文件中(或者说在这个容器里面)包含了多路流(音频流、视频流、字幕流等),对流的抽象就是AVStream;在每一路流中都会描述这路流的编码格式,对编解码格式以及编解码器的抽象就是AVCodecContext与AVCodec;对于编码器或者解码器的输入输出部分,也就是压缩数据以及原始数据的抽象就是AVPacket与AVFrame;除了编解码之外,对于音视频的处理肯定是针对于原始数据的处理,也就是针对于AVFrame的处理,使用的就是AVFilter。

案例1:将视频文件解码成单独的PCM文件和YUV文件

//1.注册协议、格式与编解码器
//注册网络协议部分
avformat_network_init();

//注册所有的编解码器、协议、格式
av_register_all();



//2.打开媒体文件源,并设置超时回调(本地磁盘文件也可能是网络媒体资源链接,会涉及RTMP/HTTP等协议的视频源,设置超时时间)
AVFormatContext *formatCtx = avformat_alloc_context();
AVIOInterruptCB int_cb = {interrupt_callback,(__bridge void *)(self)};
formatCtx->interrupt_callback = int_cb;
avformat_open_input(formatCtx, path, NULL, NULL);
avformat_find_stream_info(formatCtx, NULL);


//3.寻找各个流,并且打开对应的解码器
for (int i = 0; i<formatctx->nb_streams; i++) {
AVStream *stream = formatCtx->streams[i];
if (AVMEDIA_TYPE_VIDEO == stream->codec->codec_type) {
//视频流
videoStreamIndex = i;
videoCodecCtx = stream->codec;
}else if(AVMEDIA_TYPE_AUDIO == stream->codec->codec_type){
//音频流
audioStreamIndex = i;
audioCodecCtx = stream->codec;
}
}

//打开音频解码器
AVCodec *codec = avcodec_find_decoder(audioCodecCtx ->codec_id);
if(!codec){


// 找不到对应的音频解码器

}
int openCodecErrCode = 0;
if ((openCodecErrCode = avcodec_open2(codecCtx, codec, NULL)) < 0){
// 打开音频解码器失败

}

//打开视频流解码器
AVCodec *codec1 = avcodec_find_decoder(videoCodecCtx->codec_id);
if(!codec) {
// 找不到对应的视频解码器

}
int openCodecErrCode1 = 0;
if ((openCodecErrCode1 = avcodec_open2(codecCtx, codec1, NULL)) < 0) {
// 打开视频解码器失败
}


//4.初始化解码后数据的结构体
//4.1需要分配出解码之后的数据所存放的内存空间
SwrContext *swrContext = NULL;
if(audioCodecCtx->sample_fmt != AV_SAMPLE_FMT_S16) {
// 如果不是我们需要的数据格式
swrContext = swr_alloc_set_opts(NULL,outputChannel, AV_SAMPLE_FMT_S16, outSampleRate,in_ch_layout, in_sample_fmt, in_sample_rate, 0, NULL);
if(!swrContext || swr_init(swrContext)) {
if(swrContext) {
swr_free(&swrContext);

}

}
audioFrame = avcodec_alloc_frame();
}
//4.2以及进行格式转换所需要用到的对象
AVPicture picture;
bool pictureValid = avpicture_alloc(&picture,PIX_FMT_YUV420P,videoCodecCtx->width,videoCodecCtx->height) == 0;
if (!pictureValid){

// 分配失败
return false;

}
swsContext = sws_getCachedContext(swsContext,
videoCodecCtx->width,
videoCodecCtx->height,
videoCodecCtx->pix_fmt,
videoCodecCtx->width,
videoCodecCtx->height,
PIX_FMT_YUV420P,
SWS_FAST_BILINEAR,
NULL, NULL, NULL);
videoFrame = avcodec_alloc_frame();


//5.读取流内容并且解码
//解码器之后,就可以读取一部分流中的数据(压缩数据),然后将压缩数据作为解码器的输入,解码器将其解码为原始数据(裸数据),之后就可以将原始数据写入文件了
AVPacket packet;
int gotFrame = 0;
while(true) {
if(av_read_frame(formatContext, &packet)) {
// End Of File
break;

}
int packetStreamIndex = packet.stream_index;
if(packetStreamIndex == videoStreamIndex) {
int len = avcodec_decode_video2(videoCodecCtx, videoFrame,&gotFrame, &packet);
if(len < 0) {
break;

}
if(gotFrame) {
self->handleVideoFrame();

}

} else if(packetStreamIndex == audioStreamIndex) {
int len = avcodec_decode_audio4(audioCodecCtx, audioFrame,&gotFrame, &packet);
if(len < 0) {
break;

}
if(gotFrame) {
self->handleVideoFrame();

}

}
}


//7、处理解码后的裸数据
//裸数据,音频就是PCM数据,视频就是YUV数据。下面将其处理成我们所需要的格式并且进行写文件
//7.1 音频裸数据处理
//将音频裸数据直接写入文件,比如写入到文件audio.pcm中
void* audioData;
int numFrames;
if(swrContext) {
int bufSize = av_samples_get_buffer_size(NULL, channels,(int)(audioFrame->nb_samples * channels),AV_SAMPLE_FMT_S16, 1);
if (!_swrBuffer || _swrBufferSize < bufSize) {
swrBufferSize = bufSize;
swrBuffer = realloc(_swrBuffer, _swrBufferSize);

}
Byte *outbuf[2] = { _swrBuffer, 0 };
numFrames = swr_convert(_swrContext, outbuf,(int)(audioFrame->nb_samples * channels),(const uint8_t **)_audioFrame->data,audioFrame->nb_samples);
audioData = swrBuffer;

} else {
audioData = audioFrame->data[0];
numFrames = audioFrame->nb_samples;

}

//7.2视频的裸数据的处理
//接收到YUV数据,写入文件video.yuv中
uint8_t * luma;
uint8_t *chromaB;
uint8_t *chromaR;
if (videoCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P || videoCodecCtx->pix_fmt == av_pix+AV_PIX_FMT_YUVJ420P) {
luma = copyFrameData(videoFrame->data[0],
videoFrame->linesize[0],

videoCodecCtx->width,
videoCodecCtx->height);

chromaB = copyFrameData(videoFrame->data[1],
videoFrame->linesize[1],
videoCodecCtx->width / 2,
videoCodecCtx->height / 2);
chromaR = copyFrameData(videoFrame->data[2],
videoFrame->linesize[2],
videoCodecCtx->width / 2,
videoCodecCtx->height / 2);

}else{
sws_scale(_swsContext,
(const uint8_t **)videoFrame->data,
videoFrame->linesize,
0,
videoCodecCtx->height,
picture.data,
picture.linesize);
luma = copyFrameData(picture.data[0],
picture.linesize[0],
videoCodecCtx->width,
videoCodecCtx->height);
chromaB = copyFrameData(picture.data[1],
picture.linesize[1],
videoCodecCtx->width / 2,
videoCodecCtx->height / 2);
chromaR = copyFrameData(picture.data[2],
picture.linesize[2],
videoCodecCtx->width / 2,
videoCodecCtx->height / 2);

}

//8、关闭所有的资源
//退出的时候,要将用到的FFmpeg框架中的资源,包括FFmpeg框架对外的连接资源等全都释放掉
//清空所有的音频资源
if (swrBuffer) {
free(swrBuffer);
swrBuffer = NULL;
swrBufferSize = 0;
}
if (swrContext) {
swr_free(&swrContext);

swrContext = NULL;
}
if (audioFrame) {
av_free(audioFrame);
audioFrame = NULL;
}
if (audioCodecCtx) {
avcodec_close(audioCodecCtx);
audioCodecCtx = NULL;
}
//清空所有的视频资源
if (swsContext) {
sws_freeContext(swsContext);
swsContext = NULL;
}
if (pictureValid) {
avpicture_free(&picture);
pictureValid = false;
}
if (videoFrame) {
av_free(videoFrame);
videoFrame = NULL;
}
if (videoCodecCtx) {
avcodec_close(videoCodecCtx);
videoCodecCtx = NULL;
}

//关闭连接资源
if (formatCtx) {
avformat_close_input(&formatCtx);
formatCtx = NULL;
}
/<formatctx->注册协议、格式与编解码器打开媒体文件源,并设置超时回调(本地磁盘文件也可能是网络媒体资源链接,会涉及RTMP/HTTP等协议的视频源,设置超时时间)寻找各个流(音频视频流),并且打开对应的解码器(音频视频解码器)初始化解码后数据的结构体 需要分配出解码之后的数据所存放的内存空间 进行格式转换所需要用到的对象读取流内容并且解码。解码器之后,就可以读取一部分流中的数据(压缩数据),然后将压缩数据作为解码器的输入,解码器将其解码为原始数据(裸数据),之后就可以将原始数据写入文件了处理解码后的裸数据。裸数据,音频就是PCM数据,视频就是YUV数据。下面将其处理成我们所需要的格式并且进行写文件 音频裸数据处理,将音频裸数据直接写入文件,比如写入到文件audio.pcm中 视频的裸数据的处理,接收到YUV数据,写入文件video.yuv中关闭所有的资源 退出的时候,要将用到的FFmpeg框架中的资源,包括FFmpeg框架对外的连接资源等全都释放掉

FFmpeg主要的文件libavformat、libavcodec

libavformat和libavcodec

libavformate主要架构

AVFormatContext是API层直接接触到的结构体,它会进行格式的封装与解封装,它的数据部分

由底层提供,底层使用了AVIOContext,这个AVIOContext实际上就是为普通的I/O增加了一层

Buffer缓冲区,再往底层就是URLContext,也就是到达了协议层,协议层的具体实现有很多,包

括rtmp、http、hls、file等,这就是libavformat的内部封装了。

libavcodec主要架构

这一层我们能接触到的最顶层的结构体就是AVCodecContext,该结构体包含的就是与实际的

编解码有关的部分。首先,AVCodecContext是包含在一个AVStream里面的,即描述了这路流的

编码格式是什么,其中存放了具体的编码格式信息,根据Codec的信息可以打开编码器或者解码

器,然后利用该编码器或者解码器进行AVPacket与AVFrame之间的转换(实际上就是解码或者

编码的过程),这是FFmpeg中最重要的一部分。

FFmpegAPI

1、通用API

av_register_all分析
内部实现会先调用avcodec_register_all来注册所有config.h里面开放的编解码器,然后会注册所有的Muxer和Demuxer(也就是封装格式),最后注册所有的Protocol(即协议层的东西)。这样一来,在configure过程中开启(enable)或者关闭(disable)的选项就就作用到了运行时,该函数的源码分析涉及的源码文件包括:url.c、allformats.c、mux.c、format.c等文件av_find_codec分析
寻找解码器,一部分是寻找编码器avcodec_open2分析

打开编解码器(Codec)的函数avcodec_close分析
close就是一个逆过程,找到对应的实现文件中的close函数指针所指向的函数,然后该函数会调用对应第三方库的API来关闭掉对应的编码库

2、FFmpeg解码API

avformat_open_input分析
avformat_open_input会根据所提供的文件路径判断文件的格式,其实就是通过这一步来决定使用的到底是哪一个Demuxeravformat_find_stream_info分析
直播场景下的拉流客户端中“秒开首屏”,就是与该函数分析的代码实现息息相关的avcodec_decode分析
一部分是解码视频,一部分是解码音频avformat_close_input分析
首先会调用对应的Demuxer中的生命周期read_close方法,然后释放掉AVFormatContext,最后关闭文件或者远程网络连接。

3、FFmpeg编码API

avformat_alloc_output_context2分析
调用方法avformat_alloc_context来分配一个AVFormatContext结构体,当然最关键的还是根据上一步注册的Muxer和Demuxer部分(也就是封装格式部分)去找到对应的格式

更多音视频免费视频资料获取 后台私信【音视频】