ffmpeg解封裝
需要呼叫ffmpeg的API首先需要引入對應的標頭檔案:
extern "C"{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
複製程式碼
1.初始化解封裝
//初始化解封裝
av_register_all();
//初始化網路,可以直接從伺服器拉流
avformat_network_init();
複製程式碼
av_register_all()用於註冊所有複用器,編碼器和協議處理器。如果要指定註冊某種編碼器可以使用:av_register_input_format() ,av_register_output_format(),ffurl_register_protocol()。av_register_all()呼叫了avcodec_register_all()。avcodec_register_all()註冊了和編解碼器有關的元件:硬體加速器,解碼器,編碼器,Parser,Bitstream Filter。av_register_all()除了呼叫avcodec_register_all()之外,還註冊了複用器,解複用器,協議處理器。
avformat_network_init()用於網路元件的全域性初始化。這是可選的,但建議使用,因為它避免了隱式地為每個會話進行安裝的開銷。如果在某些主要版本中使用網路協議,呼叫此函式將成為強制性要求。載入socket庫以及網路加密協議相關的庫,為後續使用網路相關提供支援。2.開啟媒體檔案
//開啟檔案
AVFormatContext *ic = NULL;
char path[] = "/sdcard/1080.mp4";
int re = avformat_open_input(&ic,path,0,0);
if(re == 0)
{
LOGW("avformat_open_input %s success!",path);
}
else
{
LOGW("avformat_open_input failed!:%s",av_err2str(re));
}
複製程式碼
ffmpeg開啟媒體的的過程開始於avformat_open_input()。在該方法呼叫之前確保av_register_all(),avformat_network_init()已經被呼叫。該函式用於開啟多媒體資料(輸入流)並且獲得一些相關的資訊(頭資料)。對應的關閉流的函式為avformat_close_input()。
該方法中主要完成了:
-
輸入輸出結構體AVIOContext的初始化;
-
輸入資料的協議(例如RTMP,或者file)的識別(通過一套評分機制):1.判斷檔名的字尾 2.讀取檔案頭的資料進行比對;
-
使用獲得最高分的檔案協議對應的URLProtocol,通過函式指標的方式,與FFMPEG連線(非專業用詞);
-
剩下的就是呼叫該URLProtocol的函式進行open,read等操作了。
URLProtocol結構如下,是一大堆函式指標的集合(avio.h檔案)
typedef struct URLProtocol {
const char *name;
int (*url_open)(URLContext *h, const char *url, int flags);
int (*url_read)(URLContext *h, unsigned char *buf, int size);
int (*url_write)(URLContext *h, const unsigned char *buf, int size);
int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);
int (*url_close)(URLContext *h);
struct URLProtocol *next;
int (*url_read_pause)(URLContext *h, int pause);
int64_t (*url_read_seek)(URLContext *h, int stream_index,
int64_t timestamp, int flags);
int (*url_get_file_handle)(URLContext *h);
int priv_data_size;
const AVClass *priv_data_class;
int flags;
int (*url_check)(URLContext *h, int mask);
} URLProtocol;
複製程式碼
URLProtocol功能就是完成各種輸入協議的讀寫等操作。
該方法的簽名為:
int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options);
複製程式碼
- ps:函式呼叫成功之後處理過的AVFormatContext結構體。
- file:開啟的視音訊流的URL。
- fmt:強制指定AVFormatContext中AVInputFormat的。這個引數一般情況下可以設定為NULL,這樣ffmpeg可以自動檢測AVInputFormat。
- dictionay:附加的一些選項,一般情況下可以設定為NULL。
函式執行成功的話,其返回值大於等於0。
AVFormatContext:輸入資料的封裝格式
-
AVIOContext *pb:輸入資料的快取
-
unsigned int nb_streams:視音訊流的個數
-
AVStream **streams:視音訊流
-
char filename[1024]:檔名
-
int64_t duration:時長(單位:微秒us,轉換為秒需要除以1000000)
-
int bit_rate:位元率(單位bps,轉換為kbps需要除以1000)
-
AVDictionary *metadata:後設資料
-
char filename[1024]:輸入或輸出檔名
-
void avformat_close_input(AVFormatContext **s);:該函式用於關閉一個AVFormatContext,一般情況下是和avformat_open_input()成對使用的。
3.獲取流資訊
avformat_find_stream_info()。該函式可以讀取一部分視音訊資料並且獲得一些相關的資訊(適用於沒有頭部資訊的檔案):
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
複製程式碼
- ic:輸入的AVFormatContext。
- options:額外的選項。
函式正常執行後返回值大於等於0。
//獲取流資訊
re = avformat_find_stream_info(ic,0);
if(re != 0)
{
LOGW("avformat_find_stream_info failed!");
}
LOGW("duration = %lld nb_streams = %d",ic->duration,ic->nb_streams);
複製程式碼
獲取音視訊資訊:
static double r2d(AVRational r)
{
return r.num==0||r.den == 0 ? 0 :(double)r.num/(double)r.den;
}
int fps = 0;
int videoStream = 0;
int audioStream = 1;
for(int i = 0; i < ic->nb_streams; i++)
{
AVStream *as = ic->streams[i];
if(as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
LOGW("視訊資料");
videoStream = i;
fps = r2d(as->avg_frame_rate);
LOGW("fps = %d,width=%d height=%d codeid=%d pixformat=%d",fps,
as->codecpar->width,
as->codecpar->height,
as->codecpar->codec_id,
as->codecpar->format
);
}
else if(as->codecpar->codec_type ==AVMEDIA_TYPE_AUDIO )
{
LOGW("音訊資料");
audioStream = i;
LOGW("sample_rate=%d channels=%d sample_format=%d",
as->codecpar->sample_rate,
as->codecpar->channels,
as->codecpar->format
);
}
}
複製程式碼
AVStream:儲存每一個視訊/音訊流資訊的結構體。
int index:標識該視訊/音訊流
AVCodecContext *codec:指向該視訊/音訊流的AVCodecContext(它們是一一對應的關係)。codec引數在58版本及之後就不會支援了,需要由codecpar引數所替代。
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:codec引數在58版本及之後就不會支援了,需要由codecpar引數所替代。
獲取音訊流索引:
//獲取音訊流資訊
audioStream = av_find_best_stream(ic,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
複製程式碼
int av_find_best_stream (
AVFormatContext * ic,
enum AVMediaType type,
int wanted_stream_nb,
int related_stream,
AVCodec ** decoder_ret,
int flags
)
複製程式碼
在檔案中找到“最佳”流。
ic:媒體檔案控制程式碼。
type:流型別:視訊,音訊,字幕等。
wanted_stream_nb:使用者請求的流號碼,或-1用於自動選擇。
related_stream:嘗試查詢與此相關的流(例如,在相同的程式中),如果沒有,則返回-1。
decoder_ret:如果非NULL,則返回所選流的解碼器。
flags:目前沒有定義。
4.讀取音視訊幀資料
//讀取幀資料
AVPacket *pkt = av_packet_alloc();
for(;;)
{
int re = av_read_frame(ic,pkt);
if(re != 0)
{
LOGW("讀取到結尾處!");
int pos = 20 * r2d(ic->streams[videoStream]->time_base);
av_seek_frame(ic,videoStream,pos,AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME );
continue;
}
LOGW("stream = %d size =%d pts=%lld flag=%d",
pkt->stream_index,pkt->size,pkt->pts,pkt->flags
);
av_packet_unref(pkt);
}
複製程式碼
AVPacket *av_packet_alloc(void):分配一個AVPacket結構體大小的記憶體。
void av_packet_unref(AVPacket *pkt):釋放對應的AVPacket結構體。
AVPacket是儲存壓縮編碼資料相關資訊的結構體。
uint8_t *data:壓縮編碼的資料。
例如對於H.264來說。1個AVPacket的data通常對應一個NAL。
注意:在這裡只是對應,而不是一模一樣。他們之間有微小的差別:使用FFMPEG類庫分離出多媒體檔案中的H.264碼流
因此在使用FFMPEG進行視音訊處理的時候,常常可以將得到的AVPacket的data資料直接寫成檔案,從而得到視音訊的碼流檔案。
int size:data的大小
int64_t pts:顯示時間戳(num/den)
int64_t dts:解碼時間戳
int stream_index:標識該AVPacket所屬的視訊/音訊流。
讀取幀資料:
int av_read_frame(AVFormatContext * s,AVPacket * pkt)
複製程式碼
返回流的下一幀。
此函式返回儲存在檔案中的內容,並且不驗證解碼器的有效幀是什麼。它會將儲存在檔案中的內容拆分為幀,併為每個呼叫返回一個。它不會忽略有效幀之間的無效資料,從而為解碼器提供解碼所需的最大資訊。
如果pkt-> buf為NULL,那麼資料包在下一個av_read_frame()或avformat_close_input()之前是有效的。否則資料包無限期地有效。在這兩種情況下,資料包必須在不再需要時使用av_free_packet釋放。對於視訊,資料包恰好包含一幀。對於音訊,如果每個幀具有已知的固定大小(例如PCM或ADPCM資料),則它包含整數個幀。如果音訊幀具有可變大小(例如MPEG音訊),則它包含一幀。
pkt-> pts,pkt-> dts和pkt->duration始終設定為以AVStream.time_base單位的正確值。如果視訊格式具有B幀,則pkt-> pts可以是AV_NOPTS_VALUE,所以如果不解壓縮有效載荷,則最好依賴pkt-> dts。
返回
如果成功返回為0,錯誤或檔案結束時為 < 0。
設定ffmpeg將流偏移到正確的起始位置:
int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags);
複製程式碼
s:為容器內容;
stream_index:流索引
timestamp:將要定位處的時間戳
flags:功能flag
#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward
#define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes
#define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non-keyframes
#define AVSEEK_FLAG_FRAME 8 ///< seeking based on frame number
複製程式碼