Android 基於ffmpeg開發簡易播放器 - ffmpeg解封裝

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()以外,還註冊了複用器,解複用器,協議處理器。 bash

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的初始化;socket

  • 輸入數據的協議(例如RTMP,或者file)的識別(經過一套評分機制):1.判斷文件名的後綴 2.讀取文件頭的數據進行比對;ide

  • 使用得到最高分的文件協議對應的URLProtocol,經過函數指針的方式,與FFMPEG鏈接(非專業用詞);函數

  • 剩下的就是調用該URLProtocol的函數進行open,read等操做了。ui

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
複製代碼
相關文章
相關標籤/搜索