音视频开发---FFmpeg开发流程总结

目录

1. FFmpeg简介

1.1 FFmpeg的封装模块AVFormat

1.2 FFmpeg的编解码板块AVCodec

1.3 FFmpeg的滤镜模块AVFilter

1.4 FFmpeg的视频图像转换计算模块swscale

1.5 FFmpeg的音频转换计算模块swresample

2. ffmpeg处理媒体文件思路

2.1 先要明白媒体中包含有什么信息

2.2 为什么要拿到这些信息

2.3 怎么拿到这些信息

a) 解协议(http,rtsp,rtmp,mms)

b) 解封装(flv,avi,rmvb,mp4)

c) 解码(h264,mpeg2,aac,mp3)

d) 存数据

3. FFmpeg编程

3.1 解封装

3.2 解码

3.3 原始数据处理(缩放、裁剪等)

3.4 编码、存储

3.5 流程总结


 

本文对FFmpeg学习过程中的一些知识点,开发流程中用到的数据结构、函数进行梳理总结。

雷神博客:http://blog.csdn.net/column/details/ffmpeg-devel.html

音视频开发--从零到整: https://www.jianshu.com/p/c99ce47f4280

ffmpeg论坛:http://bbs.chinaffmpeg.com/forum.php

ffmpeg官方文档:http://ffmpeg.org/ffmpeg.html

一些基础概念:https://www.cnblogs.com/leisure_chn/p/10285829.html

移动端音视频入门:https://www.imooc.com/learn/959

 

1. FFmpeg简介

FFmpeg既是一款音视频编解码工具,同时也是一组音视频编解码开发套件,作为编解码开发套件,它为开发者提供了丰富的音视频的调用接口。

FFMpeg提供了多种媒体格式的封装和解封装,包括多种音视频编码、多种协议的流媒体、多种色彩格式转换、多种采样率转换、多种码率转换等;FFmpeg框架提供了多种丰富的插件模块,包含封装与解封装的插件、编码与解码的插件等。

FFmpeg中的“FF”指的是“Fast Forward”,FFmpeg中的“mpeg”则是“Moving Picture Experts Group(动态图像专家组)”。

FFmpeg的基本组成

FFmpeg框架的基本组成包括AVFormat、AVFilter、AVDevice、AVUtil等模块库。
下面针对这些模块做一个大概的介绍。


1.1 FFmpeg的封装模块AVFormat

AVFormat中实现了目前多媒体领域中的绝大多数媒体封装格式,包括封装和解封装,如MP4、FLV、KV、TS等文件封装格式,RTMP、RTSP、MMS、HLS等网络协议封装格式。
FFmpeg是否支持某种媒体封装格式,取决于编译时是否包含了该格式的封装库。根据实际需求,可进行媒体封装格式的拓展,增加自己定制的封装格式,即在AVFormat中增加自己的封装处理模块。

1.2 FFmpeg的编解码板块AVCodec

AVCodec中实现了目前多媒体领域绝大多数常用的编解码格式,既支持编码,也支持解码。AVCodec除了支持MPEG4、AAC、MJPEG等自带的媒体编解码格式之外,还支持第三方的编解码器,如H.264(AVC)编码,需要使用x264编码器;H.265(HEVC)编码,需要使用X265编码器;MP3(mp3lame)编码,需要使用libmp3lame编码器。如果希望增加自己的编码格式,或者硬件编解码,则需要在AVCodec中增加相应的编解码模块。

1.3 FFmpeg的滤镜模块AVFilter

AVFilter库提供了一个通用的音频、视频、字幕等滤镜处理框架。在AVFilter中,滤镜框架可以有多个输入或多个输出。

1.4 FFmpeg的视频图像转换计算模块swscale

swscale模块提供了高级别的图像转换API,例如它允许进行图像缩放和像素格式转换,常见于将图像从1080p转换成720p或者480p等的缩放,或者将图像数据从YUV420P转换成YUYV,或者YUV转RGB等图像格式转换。

1.5 FFmpeg的音频转换计算模块swresample

swresample模块提供了高级别的音频重采样API。例如它允许操作音频采样、音频通道布局转换与布局调整。

 

2. ffmpeg处理媒体文件思路

2.1 先要明白媒体中包含有什么信息

    可能包含有视频,音频,字幕等,以及总时长信息

    封装格式

    音频的编码格式,采样率,通道数,位宽等

    视频的编码格式,分辨率,码率,帧率

    其他metadata信息,比如所有者,日期等

2.2 为什么要拿到这些信息

    媒体文件是把原始的声音,图片等信息,经过压缩编码,封装后的结果,相当于对原始数据加了一层外壳。 我们要对媒体文件进行操作(诸如播放,裁剪等),都需要拿到原始的声音,图片信息,也就意味着我们需要经过一系列反操作,拿到最原始的信息进行处理。

2.3 怎么拿到这些信息

从工作流程角度来说,ffmpeg的主要工作流程相对比较简单,具体如下:
1)解封装
2)解码
3)编码
4)封装

音视频开发---FFmpeg开发流程总结_第1张图片
其中需要经过6个步骤,具体如下:
1)读取输入源
2)进行音视频的解封装
3)解码每一帧音视频数据
4)编码每一帧音视频数据
5)进行音视频的重新封装
6)输出到目标

 

从开发角度来说,FFMPEG中结构体很多,最关键的结构体可以分成以下几类:

a) 解协议(http,rtsp,rtmp,mms)

AVIOContext,URLProtocol,URLContext主要存储视音频使用的协议的类型以及状态。URLProtocol存储输入视音频使用的封装格式。每种协议都对应一个URLProtocol结构。(注意:FFMPEG中文件也被当做一种协议“file”)

AVIOContext是FFMPEG管理输入输出数据的结构体

b) 解封装(flv,avi,rmvb,mp4)

AVFormatContext主要存储视音频封装格式中包含的信息,AVFormatContext是包含码流参数较多的结构体,在使用FFMPEG进行开发的时候,AVFormatContext是一个贯穿始终的数据结构,很多函数都要用到它作为参数。它是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体;AVInputFormat存储输入视音频使用的封装格式。每种视音频封装格式都对应一个AVInputFormat 结构。

AVFormatContext关键字段解释:

struct AVInputFormat *iformat:输入数据的封装格式
AVIOContext *pb:输入数据的缓存
unsigned int nb_streams:视音频流的个数
AVStream **streams:视音频流
char filename[1024]:文件名
int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000)
int bit_rate:比特率(单位bps,转换为kbps需要除以1000)
AVDictionary *metadata:元数据

   元数据(metadata)信息可以通过AVDictionary获取。元数据存储在AVDictionaryEntry结构体中,如下所示

struct AVDictionary {
    int count;
    AVDictionaryEntry *elems;
};
typedef struct AVDictionaryEntry {
    char *key;
    char *value;
} AVDictionaryEntry;

 每一条元数据分为key和value两个属性。

c) 解码(h264,mpeg2,aac,mp3)

一个媒体中包含多个AVStream流, 每个AVStream存储一个视频/音频流的相关数据;每个AVStream对应一个AVCodecContext,每个AVCodecContext对应一个AVCodec(AVCodecContext是一个描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息,AVCodec包含该视频/音频对应的解码器,每个解码器都对应一个AVCodec结构,且在编译时确定),存储该视频/音频流使用解码方式的相关数据;

AVStream可从AVFormatContext中获取,在解封装操作完成后,我们便可拿到视频/音频流

AVStream结构体关键字段说明:

int index:标识该视频/音频流
AVCodecContext *codec:指向该视频/音频流的AVCodecContext(它们是一一对应的关系
AVRational time_base:时基。通过该值可以把PTS,DTS转化为真正的时间。FFMPEG其他结构体中也有这个字段,但是根据我的经验,只有AVStream中的time_base是可用的。PTS*time_base=真正的时间
int64_t duration:该视频/音频流长度
AVDictionary *metadata:元数据信息
AVRational avg_frame_rate:帧率(注:对视频来说,这个挺重要的)
AVPacket attached_pic:附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面。
AVCodec结构体关键字段说明:

const char *name:编解码器的名字,比较短
const char *long_name:编解码器的名字,全称,比较长
enum AVMediaType type:指明了类型,是视频,音频,还是字幕
enum AVCodecID id:ID,不重复
const AVRational *supported_framerates:支持的帧率(仅视频)
const enum AVPixelFormat *pix_fmts:支持的像素格式(仅视频)
const int *supported_samplerates:支持的采样率(仅音频)
const enum AVSampleFormat *sample_fmts:支持的采样格式(仅音频)
const uint64_t *channel_layouts:支持的声道数(仅音频)
int priv_data_size:私有数据的大小

  AVCodecContex作为编解码器上下文数据结构,是在解封装完毕后从AVStream中获取的(AVCodecContex的内容是动态获取的,而AVCodec是静态的(const),在编译时系统就已经确定了所有的编码器和解码器,这一点从AVCodecContext的AVCodec字段使用const修饰就能看出来)

typedef struct AVCodecContext {
    /**
     * information on struct for av_log
     * - set by avcodec_alloc_context3
     */
    const AVClass *av_class;
    int log_level_offset;

    enum AVMediaType codec_type; /* see AVMEDIA_TYPE_xxx */
    const struct AVCodec  *codec;//采用const修饰
    enum AVCodecID     codec_id; /* see AV_CODEC_ID_xxx */
    .
    .
    .
}

enum AVMediaType codec_type:编解码器的类型(视频,音频...)
struct AVCodec  *codec:采用的解码器AVCodec(H.264,MPEG2...)
int bit_rate:平均比特率
uint8_t *extradata; int extradata_size:针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等)
AVRational time_base:根据该参数,可以把PTS转化为实际的时间(单位为秒s)
int width, height:如果是视频的话,代表宽和高
int refs:运动估计参考帧的个数(H.264的话会有多帧,MPEG2这类的一般就没有了)
int sample_rate:采样率(音频)
int channels:声道数(音频)
enum AVSampleFormat sample_fmt:采样格式
int profile:型(H.264里面就有,其他编码标准应该也有)
int level:级(和profile差不太多)

 

d) 存数据

视频的话,每个结构一般是存一帧;音频可能有好几帧

解码前数据:AVPacket

      AVPacket是存储压缩编码数据相关信息的结构体

AVPacket结构体字段说明:

uint8_t *data:压缩编码的数据。
例如对于H.264来说。1个AVPacket的data通常对应一个NAL。

注意:在这里只是对应,而不是一模一样。他们之间有微小的差别:使用FFMPEG类库分离出多媒体文件中的H.264码流

因此在使用FFMPEG进行视音频处理的时候,常常可以将得到的AVPacket的data数据直接写成文件,从而得到视音频的码流文件。
int   size:data的大小
int64_t pts:显示时间戳
int64_t dts:解码时间戳
int   stream_index:标识该AVPacket所属的视频/音频流。

解码后数据:AVFrame

AVFrame是包含码流参数较多的结构体

AVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用FFMPEG进行码流分析的时候,AVFrame是一个很重要的结构体。

AVFrame结构体关键字段说明:

uint8_t *data[AV_NUM_DATA_POINTERS]:解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM)
int linesize[AV_NUM_DATA_POINTERS]:data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。
int width, height:视频帧宽和高(1920x1080,1280x720...)
int nb_samples:音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个
int format:解码后原始数据类型(YUV420,YUV422,RGB24...)
int key_frame:是否是关键帧
enum AVPictureType pict_type:帧类型(I,B,P...)
AVRational sample_aspect_ratio:宽高比(16:9,4:3...)
int64_t pts:显示时间戳
int coded_picture_number:编码帧序号
int display_picture_number:显示帧序号
int8_t *qscale_table:QP表
uint8_t *mbskip_table:跳过宏块表
int16_t (*motion_val[2])[2]:运动矢量表
uint32_t *mb_type:宏块类型表
short *dct_coeff:DCT系数,这个没有提取过
int8_t *ref_index[2]:运动估计参考帧列表(貌似H.264这种比较新的标准才会涉及到多参考帧)
int interlaced_frame:是否是隔行扫描
uint8_t motion_subsample_log2:一个宏块中的运动矢量采样个数,取log的

参考: http://blog.csdn.net/leixiaohua1020/article/details/14214577

对于packed格式的数据(eg:RGB24),会存到data[0]里面。

对于planar格式的数据(例如YUV420P),则会分开成data[0],data[1],data[2]...(YUV420P中data[0]存Y,data[1]存U,data[2]存V)

音视频开发---FFmpeg开发流程总结_第2张图片

3. FFmpeg编程

本节通过代码片段的形式对FFmpeg工作流程中涉及到的核心函数、数据结构进行说明,主要是理清各个数据结构之间的关联, 要参考完整代码,可参考我的其他文章。

3.1 解封装

     通常对于网络流媒体,我们需要先解协议,再进行解封装操作,而对于本地视频文件,则从解封装开始,就是 “打开码流”,然后再“ 解析码流信息”,在 ffmpeg 中,这两步任务主要通过 `avformat_open_input` 和 `avformat_find_stream_info`  函数来完成,前者负责服务器的连接和码流头部信息的拉取,后者则主要负责媒体信息的探测和分析工作,这两步的示例代码如下:

AVFormatContext *ic = avformat_alloc_context();
if (avformat_open_input(&ic, url, NULL, NULL) < 0) {//解封装
    LOGE("could not open source %s", url);
    return -1;
}
if (avformat_find_stream_info(ic, NULL) < 0) {
    LOGE("could not find stream information");
    return -1;
}

avformat_find_stream_info函数主要用于给每个媒体流(音频/视频)的AVStream结构体赋值,它其实已经实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作。换句话说,该函数实际上已经“走通”的解码的整个流程。

封装格式、总时长和总码率可以拿到了。另外,由于 AVStream **streams 还详细记录了每一路流的媒体信息,我们可以从中找到视频流的位置:


    int i = 0,videoindex = 0;
    for(i=0;inb_streams;i++){
		if( pInFmtContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
			videoindex = i;
			break;
		}
	}
    printf("vodeoindex2=%d\n",videoindex);
	if( videoindex == -1){
		printf("couldn't find a video stream\n");
		return -1;
	}

3.2 解码

拿到视频流AVStream之后,我们可以获取视频流编解码上下文结构AVCodecContext,进而可以获取视频流编解码结构AVCodec,代码如下:

	AVCodecContext *pInCodecCtx;
	AVCodec *pInCodec;
	pInCodecCtx = pInFmtContext->streams[videoindex]->codec;
   
    //根据编码器id,查找对应的解码器
	pInCodec = avcodec_find_decoder(pInCodecCtx->codec_id);
    
	if( avcodec_open2( pInCodecCtx, pInCodec,NULL) < 0){
		printf("avcodec_open2 failed\n");
		return -1;
	}

读取一包数据并解码:

        if( av_read_frame( pInFmtContext, in_packet) >= 0){//读取一包,存放在AVPacket中
        	if( in_packet->stream_index == videoindex){
        		ret = avcodec_decode_video2(pInCodecCtx, pInFrame, &got_picture, in_packet);//将AVPacket解码,将解码后的数据存放在AVFrame中
        		if( ret < 0){
        			printf("avcodec_decode_video2 failed:%d\n", ret);
        			return -1;
        		}
        }

到这里, pInFrame(属于AVFrame类型)就是最原始的视频数据,我们可以对原始数据进行处理(缩放,裁剪等)

3.3 原始数据处理(缩放、裁剪等)

AVFrame结构体存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),业务层更多的逻辑都集中在这一层进行处理。

我们需要对pInFrame进行操作,比如,缩放:

sws_scale( img_convert_ctx, (const uint8_t * const)pInFrame->data, pInFrame->linesize, 0, pOutCodecCtx->height, pOutFrame->data, pOutFrame->linesize);

(在调用sws_scale之前,需要调用sws_getContext进行初始化,这里不再详细介绍)

pOutFrame为处理后的结果,同样是原始数据形式。

3.4 编码、存储

对原始数据处理完毕后,还需要进行编码压缩,存储,代码片段如下:


avformat_write_header(pOutFmtContext, NULL);//写入封装格式头

while(1){
    ret = avcodec_encode_video2(pOutCodecCtx, &out_packet, pOutFrame, &got_picture);//对原始数据pOutFrame进行编码,结果保存在out_packet(AVPacket)中
                
    if( ret < 0){
        av_free_packet(in_packet);
        break;
    }
    if( got_picture == 1){
        av_packet_rescale_ts(&out_packet, pOutCodecCtx->time_base, out_stream->time_base);// pts设置
        ret = av_interleaved_write_frame(pOutFmtContext, &out_packet);//保存数据
    }
}

av_write_trailer(pOutFmtContext);//写入封装格式尾

这里需要注意的是pts的设置,可参考我的其他文章。

3.5 流程总结

结构体,函数操作关系流程:

音视频开发---FFmpeg开发流程总结_第3张图片

 

你可能感兴趣的:(音视频)