[TOC]缓存
想来想去,感受有些关于FFmpeg的细节和复杂的地方我jio的仍是要单独给你们讲讲的,毕竟以前碰到的时候也是花了点功夫才理解,再次举几个栗子bash
FFmpeg
的Log机制FFmpeg
里的时间计算FFmpeg
的内存模型这章讲的也是在播放器开发中比较重要的知识,但愿你们能仔细看,若是有不懂的,能够加我微信或者微信群进行交流,要开始了微信
FFmpeg
中日志的话主要的方法就是av_log()
,在libavutil\log.h
里,咱们来看一下他的方法框架
/**
* @param avcl 包含一个AVClass的结构体
* @param level 错误等级
* @param fmt 抛出带有format信息的日志信息
* @param ... fmt中所须要的数据
**/
void av_log(void *avcl, int level, const char *fmt, ...)
复制代码
Log的等级有如下几个ide
//不打印输出
#define AV_LOG_QUIET -8
//崩溃性的错误
#define AV_LOG_PANIC 0
//出现没法恢复的问题,好比找不到相应格式的header,或者是传入了非法的参数
#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
//输出libav*里面的debug信息 对开发者有用
#define AV_LOG_DEBUG 48
//很是冗长的调试,对libav*开发很是有用。
#define AV_LOG_TRACE 56
复制代码
对于Log的输出,咱们能够用函数
int av_log_get_level(void);
void av_log_set_level(int level);
复制代码
进行set和get操做来控制和获取日志的等级测试
输出log的方法,就是利用void av_log_set_callback(void (*callback)(void*, int, const char*, va_list));
这个方法,设置一个函数指针,进行回调。ui
咱们在ffmpeg中时间主要是计算的PTS的时间 经常使用的时间分为三种:this
这三种时间单位都有不一样的用处,可是均可以进行互相换算。spa
怎么肯定ffmpeg中使用的是以什么时间为基准的呢?
ffmpeg内部有个定义的宏#define AV_TIME_BASE 1000000
,这个宏定义了它的时间基 (1s = AV_TIME_BASE),像这里就是用的微秒(us)作为时间基
还有另外一个AV_TIME_BASE_Q
,这个宏的定义完整的是这样的
#define AV_TIME_BASE_Q (AVRational){1,AV_TIME_BASE}
复制代码
这个宏是AV_TIME_BASE的分数表示 也就是 1/AV_TIME_BASE
他们之间的转换关系是
timestamp(时间戳) = AV_TIME_BASE * time(秒)
time(秒) = AV_TIME_BASE_Q * timestamp(时间戳)
复制代码
在这里,细心的同窗会发现 AVRational
这个结构体在不少地方都用到了,这个结构体的全貌是这样的
/**
* Rational number (pair of numerator and denominator).
*/
typedef struct AVRational{
int num; ///< Numerator
int den; ///< Denominator
} AVRational
复制代码
它就是简单的记录了一下分子和分母,咱们在ffmpeg中能够直接用方法
/**
* Convert an AVRational to a `double`.
* @param a AVRational to convert
* @return `a` in floating-point form
* @see av_d2q()
*/
static inline double av_q2d(AVRational a){
return a.num / (double) a.den;
}
复制代码
直接进行计算,就像这样
timestamp(秒 = pts * av_q2d(stream->time_base);
复制代码
就能计算出如今这帧的真实pts是多少秒
在FFmpeg中存在的多个时间基(time_base) (没有错,就是这么坑),它们对应着不一样的阶段,每一个time_base的具体值不同,常见的有
注意 在使用解码中AVFrame的pts的时候,能够作个时间矫正 frame->pts = frame->best_effort_timestamp
这个值通常状况下个pts是同样的,可是在某些状况下,好比丢帧,会进行一些纠正
内存模型的话咱们这小节主要讲AVPacket
和AVFrame
这两个结构体,由于在播放器的开发中,咱们操做的最多的就是这两个结构体,一旦处理很差,发生内存泄漏,显示不正常什么的。。很刺激的。。。
进入正题
首先,稍微来看一些AVPacket
这个结构体
typedef struct AVPacket {
/**
* 带有引用计数buffer,多是空的
*/
AVBufferRef *buf;
/**
* 当前Packet的pts
*/
int64_t pts;
/**
* 当前Packet的dts
*/
int64_t dts;
uint8_t *data;
int size;
/**
* 当前Packet的流下标
**/
int stream_index;
/**
* A combination of AV_PKT_FLAG values
*/
int flags;
/**
* Additional packet data that can be provided by the container.
* Packet can contain several types of side information.
*/
AVPacketSideData *side_data;
int side_data_elems;
/**
* 当前的packet的持续时间
*/
int64_t duration;
int64_t pos; ///< 流中的字节位置,若是未知,则为-1
#if FF_API_CONVERGENCE_DURATION
/**
* @deprecated Same as the duration field, but as int64_t. This was required
* for Matroska subtitles, whose duration values could overflow when the
* duration field was still an int.
*/
attribute_deprecated
int64_t convergence_duration;
#endif
} AVPacket;
复制代码
AVPacet
是一个解封装以后的数据(H264,AAC),假如咱们要对这个AVPacket
进行拷贝的操做,那么这个时候就须要注意了
AVPacket
引用的是统一数据的缓存空间,假如释放一个,另外一个也会被释放AVPacket
的buf
引用不一样的数据缓存空间,每一个AVPacket
都有数据缓存的拷贝分配的时候是不会分配buf的
咱们来简单的测试一下 ,新建chapter_08/AVPacketMemoryModel
(为何又采用C++的写法了?没办法,路子就是这么野) 头文件里
运行以后咱们发现
至于他的缘由你们能够去看一下AVPacket *av_packet_alloc(void)
方法,一看就知道为何了
那么何时才回去把数据放进去呢?
没有错就是调用av_read_fram
的方法的时候,才会去对这个数据赋值
AVPacket
的数据共享模型,能够用下面这个图
对于多个AVPacket
共享同一个缓存空间,ffmpeg采用引用计数机制(reference-count)
(AVFrame表示我也是这么作的)
闭上眼,仔细感觉 有没有点智能指针的味道
基于上面的引用机制,咱们在调用AVPacket
相关的方法时,就须要注意引用问题了,下面是有关的方法,以及引用的状况
AVPacket *av_packet_alloc(void); //分配AVPacket
void av_packet_free(AVPacket **pkt); //释放AVPacket
void av_init_packet(AVPacket *pkt); //初始化AVPacket
int av_new_packet(AVPacket *pkt, int size); //给AVPacket的buf分配内存,引用计数初始化为1
int av_packet_ref(AVPacket *dst, const AVPacket *src); //增长引用计数
void av_packet_unref(AVPacket *pkt); //减小引用计数
void av_packet_move_ref(AVPacket *dst, AVPacket *src); //转移引用计数
AVPacket *av_packet_clone(const AVPacket *src); //等于av_packet_alloc()+av_packet_ref()
复制代码
那么 咱们要怎么知道他的引用数呢?有个av_buffer_get_ref_count
,能够获取Buffer的引用数
在原来的程序基础上咱们进行修改
void AVPacketMemoryModel::testAVPacketAlloc() {
AVPacket *packet = av_packet_alloc();
std::string log = (packet->buf == nullptr) ? "null" : "not null";
std::cout << log << std::endl;
av_new_packet(packet, 20 * 1024 * 1024);
memccpy(packet->data, this, 1, 20 * 1024 * 1024);
if (packet->buf) {
int ret = av_buffer_get_ref_count(packet->buf);
std::cout<<"当前引用值 :"<<ret<<std::endl;
}
AVPacket* packet1 = av_packet_alloc();
av_packet_ref(packet,packet);
if (packet->buf) {
int ret = av_buffer_get_ref_count(packet->buf);
std::cout<<"当前引用值 1 :"<<ret<<std::endl;
}
}
复制代码
执行的结果是
AVFrame
和AVPacket
的操做差很少,这里就不费篇幅叙述了
** 未完持续 。。。**