采样格式是AVSampleFormat:
enum AVSampleFormat {
AV_SAMPLE_FMT_NONE = -1,
AV_SAMPLE_FMT_U8, ///< unsigned 8 bits
AV_SAMPLE_FMT_S16, ///< signed 16 bits
AV_SAMPLE_FMT_S32, ///< signed 32 bits
AV_SAMPLE_FMT_FLT, ///< float
AV_SAMPLE_FMT_DBL, ///< double
AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar
AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar
AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar
AV_SAMPLE_FMT_FLTP, ///< float, planar
AV_SAMPLE_FMT_DBLP, ///< double, planar
AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking dynamically
};
位数越大,声音表达越准确,除此之外还有采用频率,人耳可识别的范围在20Hz~20KHz,采样频率是指每秒对连续音频信号采集
多少次数据,每次数据的位数就是AVSampleFormat,常用的采样频率有22050、44100和44800,再高的话人耳也听不出差别了
多一个通道存储多占一倍数据,多声道的存储方式有两种:planner和packed,第一种是平面存储,每个通道占用一个平面存
储,packed是交叉存储,每个通道数据依次交叉存储,依次进行;但是如何存储呢?如planner哪个平面和哪个通道对应呢,
packed的各个通道是按何种排序方式存储的呢?这就需要channel_layout布局说明?如何存储可参考 FFmpeg中音频的通道channels和channel_layout等概念
音视频时钟概念
各种时钟
声音中的采样率、通道等概念
每帧音频可能有多个采样点,AAC格式数据一帧有1024个采样点,每帧耗时1024*1000/sample_rate,如果sample_rate为44.10Khz,大约耗时22ms
每帧视频帧包含一个画面,耗时等于1/fps,fps为码率,一帧约40ms
上面计算的耗时可以理解为PTS,所以在播放时根据时间轴,依次播放耗时短的在长的视频帧,如在22ms播放第一个音频帧,40ms播放第一个视频帧;但是这会有一个问题: 如声卡在播放一个22ms的音频帧的耗时和正常时间轴走完可能不同,这样可能会出现这样的情况,22ms播放第一个音频帧,到40ms播放视频帧时,音频帧理论应该播完,但是实际情况可能声卡的一些机制可能音频未播完,依次播放累加,会导致音视频不同步, 如何解决?
回调反馈机制,给声卡固定的buffer,这个buffer装载固定耗时的音频数据,当buffer播完,就知道这个固定耗时,依次为基准来播放视频,就会解决该问题;更多请点击下面的原理
音视频时钟同步原理
-可变量的尺寸转换(从4×4 到32×32)
-四叉树结构的预测区域(从64×64到4×4)
-基于候选清单的运动向量预测。
-多种帧内预测模式。
-更精准的运动补偿滤波器。
-优化的去块、采样点自适应偏移滤波器等。
视频编码历史
HEVC和H264对比
SLES播放简介
SLES代码简介
openGL EGL
腾讯资料openGL EGL
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:元数据
AVCodecParameters *codecpar; 解码器参数
int64_t skip_initial_bytes; 解码时跳过开头的字节数,偏移;编码时设置无效
struct AVOutputFormat *oformat; 编码时有效
gop_size : the number of pictures in a group of pictures, or 0 for intra_only,即帧率
解码之前的数据和关于这些数据的一些附加信息,如显示时间戳(pts)、解码时间戳(dts)、数据时长,所在媒体流的索引等。对于视频来说,AVPacket通常包含一个压缩的Frame,而音频(Audio)则有可能包含多个压缩的Frame。并且,一个Packet有可能是空的,不包含任何压缩数据,只含有side data(side data>,容器提供的关于Packet的一些附加信息。例如,在编码结束的时候更新一些流的参数)。AVPacket的大小是公共的ABI(public ABI)一部分,这样的结构体在FFmpeg很少,由此也可见AVPacket的重要性。它可以被分配在栈空间上(可以使用语句AVPacket packet; 在栈空间定义一个Packet ),并且除非libavcodec >和 libavformat有很大的改动,不然不会在AVPacket中添加新的字段。
av_packet_unref使AVPacket变得无效
// 用来管理data指针引用的数据缓存的
// 为NULL时,那么数据包是不计数的
AVBufferRef *buf;
// 显示时间戳, 对应时间戳AVStream->time_base单元; 这个时间点, 解压缩的数据包将被提交给用户
// 如果时间不被存储在文件里, 则可以写成AV_NOPTS_VALUE
// pts必须大于或等于dts, 因为显示不能在解压缩之前被发生, 除非有人想查看十六进制存储。
// 某些格式误用了这个名词dts或者是pts/cts那是意味着别的意思, 所以时间戳必须在被存储到AVPacket之前转换成真正的PTS/DTS。
int64_t pts;
// 解码时间戳, 对应时间戳AVStream->time_base单元; 这个时间点, 数据包被解码
// 如果时间不被存储在文件里, 则可以写成AV_NOPTS_VALUE
int64_t dts;
// 存储的数据,指向一个缓存,这是AVPacket实际的数据
uint8_t *data;
// 数据的大小
int size;
// 标识该AVPacket所属的音频/视频流的索引
int stream_index;
// 一个AV_PKT_FLAG标识值, 最低为置1表示关键帧
int flags;
// 容器可以提供的附加数据
// 包可以包含几种AVPacketSideDataType类型的侧信息
AVPacketSideData *side_data;
// 附加信息元素
int side_data_elems;
// 数据的时长,以所属媒体流的时间基准为单位
int64_t duration;
// 该数据在媒体流中的字节偏移量
int64_t pos;
// 该字段不再使用
#if FF_API_CONVERGENCE_DURATION
attribute_deprecated
int64_t convergence_duration;
#endif
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音频文件附带的专辑封面
AVCodecParameters *codecpar; 编码器的名字和id,视频宽高等
int disposition; /**< AV_DISPOSITION_* bit field 以字节码来显示Packet的标志量*/
enum AVDiscard discard; //丢弃策略,哪些stream可以直接丢弃,无须demux,AVDiscard有许多丢弃策略,直接丢弃,0大小packet丢弃,根据需要去选择
判断流是否是一个只包含封面的图片流
pStream->disposition & AV_DISPOSITION_ATTACHED_PIC,了解更多点击这里
编解码器上下文,点我详情
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差不太多)
解码后的一帧数据
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的
typedef struct AVRational{
int num; ///< Numerator 分子
int den; ///< Denominator 分明
} AVRational; ///* 就是一个分数
AVClass与AVOption常用语FFmpeg内部结构体的赋值,可以使用统一的变量名查找并赋值,而不必了解结构体;一般AVClass是ffmpeg结构体的第一个成员,AVClass描述结构体的基本信息,AVClass的成员AVoption指向一个全局AVoption数组,这个数组保存当前结构体的所有成员以及类型帮助等,用于设置结构体成员
为何要进行重采样?因为解码器解码后的音频流在格式上,比如采样率、采样格式、通道、通道layout等存在不一致,音频播放器无法进行播放,这个时候就需要用到重采样进行音频格式转换,转换流程如下:
int out_count = av_rescale_rnd(swr_get_delay(swrCtx, in->sample_rate) + frame->nb_samples, out->freq, in->sample_rate,AV_ROUND_UP);
int out_size = av_samples_get_buffer_size(NULL, out->channels, out_count, out->fmt, 0);
//快速分配内存
av_fast_malloc(&resampleBuffer, &resampleSize, out_size);
//更新时钟
//解码时间戳加这么 nb_samples个采样点的时间
audioClock = frame->pts * av_q2d((AVRational){1, frame->sample_rate}) +
(double)frame->nb_samples / frame->sample_rate;
//再次更新
//乘2是因为有两个通道 audioClock在audioFrameResample更新了播放解码后数据的时间,这里要减去这些数据,还没有播放的数据
//audio_hw_buf_size硬件缓冲区缓存数据,bytes_per_sec是一秒播放的字节数
sync->updateAudioColock(audioClock - (double)(2 * audioState->audio_hw_buf_size + audioState->writeBufferSize) / audioState->audioParamsTarget.bytes_per_sec,
audioState->audio_callback_time / 1000000.0);
前提,音频播放器注册Mediplayer的回调
avformat_alloc_output_context2()
avfomat_write_header() //写入文件头
avcodec_send_frame()/avcodec_receive_packet() //编码
av_write_frame()/av_interleaved_write_frame() //写入帧
av_write_trailer() //写入文件尾
pts:显示时间戳,编解码时需要自行处理以及转换
dts:解码时间戳,无须自己处理,有解码器自行处理
time_base:ffmpeg中时间的最小单位,任何时间戳乘上time_base才转换位具体的时间刻度;ffmpeg有三种时间基;
AVStream的时间基,目前不知道用什么用,应该是在该协议层会使用到;
AVCodec编码层的时间基,一般为帧率的倒数,编码是一帧帧的去编码,这个是最小的度量单位,应该是这么决定的
FFmpeg的内部时间基,供FFmpeg的内部api使用;
上面说到pts在编解码中需要我们自行转换,转换也是通过FFmpeg的内部api来转换:
为了保证时间的精度,时基一般用分数表示:AVRational结构体内部由分子,分母构成,以下av_q2d就是把分数形式转换为double类型
把a从bg时间基转换为cq时间基的值
将AVPacket中各种时间值从一种时间基转换为另一种时间基
AVrational赋值操作
将具体的时间值从一种时间基转换为另一种时间基
// 编码
avcodec_send_frame(enc_ctx, frame);
avcodec_receive_packet(enc_ctx, packet);
// 时间基转换
packet.stream_index = out_stream_idx;
enc_ctx->time_base = av_inv_q(dec_ctx->framerate);
av_packet_rescale_ts(&opacket, enc_ctx->time_base, out_stream->time_base);
// 将编码帧写入输出媒体文件
av_interleaved_write_frame(o_fmt_ctx, &packet);
对于解码来说:
// 从输入文件中读取编码帧
av_read_frame(ifmt_ctx, &packet);
// 时间基转换
int raw_video_time_base = av_inv_q(dec_ctx->framerate);
av_packet_rescale_ts(packet, in_stream->time_base, raw_video_time_base);
// 解码
avcodec_send_packet(dec_ctx, packet)
avcodec_receive_frame(dec_ctx, frame);
码率控制就是对编码输出的速率控制,单位时间内输出的字节数kbps(kb per second);码率越大,视频越接近原始大小,图像越清晰;码率越低,压缩程度越大,图像失真越厉害;码率控制的目的在于 尽可能用最低的码率来达到图像最小的可以接受的失真
动态码率,根据图像内容,当图像包含场景运动时,码率变大,保证图像质量;图像静止时,码率变低,节省带宽
VBR适合于媒体存储,而不是网络传输
媒体传输时,建议选择ABR,压缩比VBR快,更适合传输
通过设置AVCodecContext中的变量可以达到控制码率的目的:
//qp控制模式
av_opt_set(pCodecCtx->priv_data,"",crf,AV_OPT_SEARCH_CHILDREN);
同时,如果编码采用H264、H265编码标准,还可以设置如下:
AVDictionary *dictParam = 0;
av_dict_set(&dictParam,"preset","medium",0);
av_dict_set(&dictParam,"tune","zerolatency",0);
av_dict_set(&dictParam,"profile","main",0);
avcodec_open2(pCodecCtx, pCodec,&dictParam);
preset的参数主要调节编码速度和质量的平衡,有ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、veryslow、placebo这10个选项,从快到慢。
tune的参数主要配合视频类型和视觉优化的参数,或特别的情况。如果视频的内容符合其中一个可用的调整值又或者有其中需要,则可以使用此选项,否则建议不使用
tune的值有: film: 电影、真人类型;
animation: 动画;
grain: 需要保留大量的grain时用;
stillimage: 静态图像编码时使用;
psnr: 为提高psnr做了优化的参数;
ssim: 为提高ssim做了优化的参数;
fastdecode: 可以快速解码的参数;
zerolatency:零延迟,用在需要非常低的延迟的情况下,比如电视电话会议的编码。
profile,对应四种H264画质级别。每个profile支持一组特定的编码功能
1. BP-Baseline Profile
基本画质,支持I/P帧,无B帧。只支持无交错(Progressive)和CAVLC
主要用于可视电话、会议电视、无线通信等实时视频通信。
2. EP-Extended Profile
进阶画质,支持I/B/P/SP/SI帧。只支持无交错(Progressive)和CAVLC
主要用于流媒体服务。
3. MP-Main Profile
主流画质,支持I/B/P。只是无交错(Progressive)和交错(Interlaced),支持CAVLC和CABAC。
主要用于电视广播和视频存储。
4. HP-High profile
高级画质, 在mainProfile的基础上增加了8x8内部预测、自定义量化、 无损视频编码和更多的YUV格式。
H.264 FRExt(即:FidelityRange Extensions)扩展部分(Amendment),包括High profile(HP)、High10 profile(Hi10P)、High 4:2:2 profile(Hi422P)、High4:4:4 profile(Hi444P)4个profile。H.264 Baseline profile、Extended profile和Main profile都是针对8位样本数据、4:2:0格式的视频序列,FRExt将其扩展到8~12位样本数据,视频格式可以为4:2:0、4:2:2、4:4:4,设立了High profile(HP)、High 10profile(Hi10P)、High 4:2:2 profile(Hi422P)、High 4:4:4 profile(Hi444P) 4个profile,这4个profile都以Main profile为基础。在相同配置情况下,Highprofile(HP)可以比Mainprofile(MP)节省10%的码流量,比MPEG-2 MP节省60%的码流量,具有更好的编码性能。
在视频编解码的时候你可能还会用到libyuv库,一款可以高效的图像格式转换、裁剪、旋转镜像的库,Google开发的,用法自己去看,这里讲我们需要用到的;后置摄像头摄像时一般不需要自己对图像进行处理,但是前置摄像头拍摄时,图像处于上下颠倒,左右镜像的情况,我们可以使用mirror对图像数据镜像处理,也可以给图像设置一个旋转角度,播放时对读取旋转角度处理即可,以下是我处理前置摄像头的操作:
av_dict_set(&stream->metadata, "rotate", "180", 0);
// I420 mirror.
LIBYUV_API
int I420Mirror(const uint8* src_y, int src_stride_y,
const uint8* src_u, int src_stride_u,
const uint8* src_v, int src_stride_v,
uint8* dst_y, int dst_stride_y,
uint8* dst_u, int dst_stride_u,
uint8* dst_v, int dst_stride_v,
int width, int height);
I420Mirror就是libyuv库的方法,该方法支持水平竖直镜像,width和height图像原始宽高时,水平镜像;height传递负height,竖直镜像,并且水平也给你弄镜像 了,这是个败笔;不能同时水平竖直镜像,你不得不掉两遍该方法
除此之外,libyuv的scale处理裁剪之外,width和height传递负值,也能实现镜像功能
以上是我的编码操作;当然你也可以同时设置旋转和镜像操作,在播放器端,在读取出来,opengl实现镜像即可
double av_gettime_relative()
获取当前时间微秒
avcodec_flush_buffers(AVCodecContext *avctx)
刷线编解码上下文中的缓冲区,在重新打开编解码器、定位视频时,必须要重新刷线缓冲区,否则会收到之间的编解码数据,错误的数据帧
void av_format_inject_global_side_data(AVFormatContext *s) 将s下的每个AVStream流的side_data置为1
int av_get_channel_layout_nb_channels(uint64_t channel_layout) 根据通道的layout返回通道的个数
int64_t av_get_default_channel_layout(int nb_channels) 根据通道的个数返回默认的layout
int av_get_channel_layout_channel_index(uint64_t channel_layout,uint64_t channel) 返回通道在layout中的index,也就是某一通道在layout的存储位置
int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples,
enum AVSampleFormat sample_fmt, int align)
用于音频计算,把一系列的音频数据保存起来,需要花费多少空间
首先,音频数据可以是多个通道的,如左右声道,存储方式和YUV类似,分平面存储planner和打包packed存储
linesize,即一个平面的长度,packed存储方式看成一个平面,注意参数linesize是指针,执行后他是一个通道的长度;而函数返回值是所有通道的长度和
nb_channels:通道数
nb_samples:采样点数
sample_fmt:采样点存储方式
align:0,提供冗余数据,各通道数据长度相同;1-不冗余,音频数据真实大小
int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd);
不同时间基转换,计算转换后的值大小,内部大致原理为a*b/c,是由c时基转到b时基下,最后取整依靠rnd来取,rnd取值有
enum AVRounding {
AV_ROUND_ZERO = 0, ///< Round toward zero. 趋近于0
AV_ROUND_INF = 1, ///< Round away from zero. 趋远于0
AV_ROUND_DOWN = 2, ///< Round toward -infinity. 趋于更小的整数
AV_ROUND_UP = 3, ///< Round toward +infinity. 趋于更大的整数
AV_ROUND_NEAR_INF = 5, ///< Round to nearest and halfway cases away from zero. 四舍五入,小于0.5取值趋向0,大于0.5取值趋远于0
AV_ROUND_PASS_MINMAX = 8192, ///< Flag to pass INT64_MIN/MAX through instead of rescaling, this avoids special cases for AV_NOPTS_VALUE
}
av_malloc和av_mallocz的区别在于后者分配内存后,将内存区域初始化为0
void av_fast_malloc(void *ptr, unsigned int *size, size_t min_size);
min_size为我们需要分配的内存大小,实际内部分配的大小会比min_size大,执行完成后实际内存大小将赋值给size
AVFilter *avfilter_get_by_name(const char *name);
根据名字查找过滤器
int avfilter_graph_create_filter(AVFilterContext **filt_ctx, const AVFilter *filt,
const char *name, const char *args, void *opaque,
AVFilterGraph *graph_ctx);
创建过滤器上下文AVFilterContext并用args初始化,args一般采用以下格式:
snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
mWidth, mHeight, mInputPixelFormat, timeBase.num, timeBase.den,
ratio.num, ratio.den);
-i : 输入音视频文件