忽然發現又有很久沒有寫技術blog了,主要緣由是最近時間都用來研究libav去了(由於api極相似ffmpeg,雖然出自同一份代碼的另一個分支,因項目選用libav,故下文均用libav代替),其實要從知道這個庫的時候已經好久了,早在加入avplayer開源社區的已經略有耳聞,看着他們討論我卻一直不知這個庫能具體幫我作到哪些功能,插不上嘴呢,更強迫了我學習它的熱情,下面就來一一解惑,但願就能幫到相似幾個月前的我那樣的同行。api
一、提供API解碼、編碼市面上主流幾乎所有的視頻、音頻格式文件。ide
二、通用視頻轉換命令行工具ffmpeg、avconv,能夠幫咱們快速將媒體文件格式進行轉換,且作一些簡單的resize或者resample,其工具提供了很是強大的filter,各類變幻根據參數都能實現,沒有你想不到的,只有你找不到的。函數
三、簡單的播放器avplay命令,這個播放器支持libav全部可以支持的video codec,算個簡單的萬能播放器了,雖然seek功能弱爆了,而且尚未pause、stop、顯示時間等等功能,不過有些應急時候絕對首選它了。工具
四、libav還提供avprobe命令,可讓你瞬間瞭解這個媒體文件其中真實的video/audio編碼(不會受到文件擴展名的誤導)、擁有哪些stream(通常MP4分爲視頻、音頻、字幕),一目瞭然。學習
讀到此你必定會很感激我,不像大多數技術博客那樣直接貼上不少大段大段的代碼一下嚇走好多初學者....我不能保證接下來必定不貼代碼上來,可是我會盡可能剋制本身的....ui
本文主要將以第1點API解碼編碼的介紹爲主。由於libav是基於C實現的,調用習慣全是基於函數式的,這樣的優勢就是跨平臺好吧,缺點就是會使client代碼比較臃腫,處處充斥着free、alloc等等。若是你是一個純面向對象發燒支持者,請不要往下看,以避免傷身且藥還不能停。編碼
libav提供一個函數avformat_open_input,即打開一個媒體文件,用AVFormatContext指針接受返回結果,代碼看起來就是這樣:spa
AVFormatContext* pformat_context = avformat_alloc_context(); if(avformat_open_input(&pformat_context, file.c_str(), nullptr, 0) != 0) { printf("can't open the file %s\n", file.c_str()); return false; }
而後你要作的是將所打開的FormatContext讀取其中的stream,其中會有各類各樣的stream類型,你須要作的事情就是將這個stream的index記錄下來。命令行
shared_ptr<AVFormatContext> format_context(pformat_context, [](AVFormatContext*& p){ avformat_close_input(&p); }); if(avformat_find_stream_info(format_context.get(), nullptr) < 0) { printf("can't find suitable codec parameters\n"); return false; } // find out the audio and video stream int video_stream_index = -1, audio_stream_index = -1; for(unsigned int i = 0; i < format_context->nb_streams && (video_stream_index == -1 || audio_stream_index == -1); i++) { if(format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; } else if (format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { audio_stream_index = i; } } if(video_stream_index == -1 && audio_stream_index == -1) { printf("input file contains no video stream or audio stream.\n"); return false; }
對照stream可使用avprobe命令查看視頻文件自己的內容。
獲取到stream信息以後,你就須要建立decoder來解碼視頻啦~ libav提供一個函數avcodec_find_decoder根據你本身找到的video index和audio index去尋找codec_id做爲參數獲得AVCodec指針,再使用函數avcodec_open2傳入這個指針便可。指針
// open the video decoder AVCodecContext* video_codec_context = nullptr; if (video_stream_index != -1) { video_codec_context = format_context->streams[video_stream_index]->codec; AVCodec* video_codec = avcodec_find_decoder(video_codec_context->codec_id); if(video_codec == nullptr) { printf("can't find suitable video decoder\n"); return false; } if(avcodec_open2(video_codec_context, video_codec, nullptr) < 0) { printf("can't open the video decoder\n"); return false; } } // open the audio decoder AVCodecContext* audio_codec_context = nullptr; if (audio_stream_index != -1) { audio_codec_context = format_context->streams[audio_stream_index]->codec; AVCodec* audio_codec = avcodec_find_decoder(audio_codec_context->codec_id); if (audio_codec == nullptr) { printf("can't find suitable audio decoder\n"); return false; } if (avcodec_open2(audio_codec_context, audio_codec, nullptr) < 0) { printf("can't open the audio decoder\n"); return false; } }
注意,open以後必定要調用對應的api close,比方avformat_close_input這些都是必備的,就不全貼出來了。
接下來說這課程最重要的部分——decode video,先建立用於接收av_read_frame讀出來的數據包,
AVPacket packet = {0}; av_init_packet(&packet);
而後使用一個循環調用av_read_frame,查註釋你會知道return>=0爲成功,而後判斷packet的stream_index是video_stream_index仍是audio_stream_index,從而使用不一樣的decode函數(avcodec_decode_video2 / avcodec_decode_audio4)作解碼,視頻若是是MP4將獲得AV_PIX_FMT_YUV420P數據,音頻將獲得原始音頻AV_SAMPLE_FMT_FLTP採樣數據。
可是咱們通常不會使用YUV420P進行圖像、視頻處理,而是使用bitmap來進行處理,因此須要在這裏藉助另一個函數sws_scale,第一個參數查看源碼瞭解到是一個結構體struct,並不須要手動填充它,並且你也沒辦法手動填充它,libav並不但願你這麼作(沒有將細節寫在include中),所以有一個sws_getContext函數是專門作這件事情的。
struct SwsContext *sws_getContext( int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param);
看到參數你就能很容易的猜到,你須要提供原視頻的尺寸和格式,能夠在已打開的視頻的codec中得到,目標視頻尺寸你本身隨便設置均可以,dstFormat能夠設置爲:AV_PIX_FMT_BGRA,更多能夠參見:pixfmt.h 中的 enum AVPixelFormat,若是是BGRA,圖片則爲32位,包含透明通道,方便以後疊加圖層處理。若是讀者跟着個人步驟走,應該就能達到連續輸出圖片的功能了,再加入圖像識別的更多功能:臉譜識別、手勢識別、車牌識別,就直接能夠用了,是否是很激動?