[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
的操做差很少,這裏就不費篇幅敘述了
** 未完持續 。。。**