FFmpeg開源框架及重要結構體簡紹

1. FFmpeg介紹與裁剪

1.1 FFmpeg簡介

 FFmpeg(Fast forword mpeg,音視頻轉換器)是一個開源免費跨平臺的視頻和音頻流方案,它提供了錄製/音視頻編解碼、轉換以及流化音視頻的完整解決方案。ffmpeg4.0.2源碼目錄結構如下:

目錄說明:
FFmpeg
 |—compat     該目錄存放的是兼容文件,以便兼容早期版本
 |—doc      說明文檔
 |—ffbuild
 |—libavcodec   音視頻編解碼核心庫
 |—libavdevice  各種設備的輸入輸出,比如Video4Linux2, VfW, DShow以及 ALSA
 |—libavfilter  濾鏡特效處理
 |—libavformat  I/O操作和封裝格式(muxer/demuxer)處理
 |—libavswresample 音頻重採樣,格式轉換和混音
   |—      (1) 重採樣:改變音頻的採樣率,比如從44100HZ降低到8000HZ
   |—      (2)重新矩陣化:改變音頻通道數量,比如從立體聲道(stereo )變爲單身道(mono)
   |—      (3)格式轉換:改變音頻採樣大小,比如將每個樣本大小從16bits降低到8bits
 |—libavutil   工具庫,比如算數運算、字符操作等
 |—libpostproc  後期效果處理,如圖像的去塊效應
 |—libswscale   視頻像素處理,包括縮放圖像尺寸、色彩映射轉換、像素顏色空間轉換等
 |—presets
 |—tests      測試實例
 |—configure    配置文件,編譯ffmpeg時用到

1.2 命令行工具

 FFmpeg框架中還提供了幾個用於執行命令行完成音視頻數據處理工具,包括ffplay、ffprobe、ffserver,具體解釋如下:

ffplay
Fast forword play,用ffmpeg實現的播放器

ffserver
Fast forword server,用ffmpeg實現的rtsp服務器

ffprobe
Fat forword probe,用來輸入分析輸入流

2. FFmpeg架構分析

 在1.1小節中,我們對FFmpeg整體架構進行了簡單介紹,闡述了框架中各個模塊的功能。本節將在此基礎上,重點闡述在利用FFmpeg進行音視頻開發中牽涉到的重要步驟,數據結構體以及相關函數。

2.1 FFmpeg處理要點

 總體來說,FFmpeg框架主要的作用在於對多媒體數據進行解協議、解封裝、解碼以及轉碼等操作,爲了對FFmpeg在視音頻中的應用有個更直觀理解,下面給出解析rtsp網絡流的流程圖,該圖演示了從打開rtsp流,到最終提取出解碼數據或轉碼的大概過程,如下所示:

術語解釋:

muxer:視音頻複用器(封裝器),即將視頻文件、音頻文件和字幕文件(如果有的話)合併爲某一個視頻格式,比如講a.avi、a.mp3、a.srt合併爲mkv格式的視頻文件;
demuxer:視音頻分離器(解封裝器),即muxer的逆過程;
transcode:轉碼,即將視音頻數據從某一種格式轉換成另一種格式;
RTP包:Real-time Transport Protocol,實時傳輸協議,是一種基於UDP的網絡傳輸協議,它介於應用層和傳輸層之間,負責對流媒體數據進行封包並實現媒體流的實時傳輸;
ES流:Elementary Streams,即原始流,也稱視/音頻裸流,是直接從編碼器輸出的數據流,可爲視頻數據流(如H.264、MJPEG等)或音頻數據流(如AAC等);
PES流:Packetized Elementary Streams,分組ES流,PES流是ES流經過PES打包器將ES分組、打包、加入包頭信息等處理後形成的數據流,是用來傳遞ES的一種數據結構。
解協議:取出網絡數據流無關報文信息,以獲取真正的視音頻數據,常見的協議有rtsp、rtmp、http和mms等;
解封裝:即demuxer,封裝格式可以爲.mp4/.avi/.flv/.mkv等;
解碼:將編碼數據還原成原始內容,比如將H.264解碼爲YUV、AAC解碼爲PCM等;
2.1 FFmpeg重要的結構體

 FFmpeg中有很多比較重要的結構體,比如與輸入輸出(I/O)有關的結構體AVIOContext、URLContext、URLProtocol ,與封裝格式有關的結構體AVFormatContext、AVInputFormat、AVOutputFormat,與編解碼有關的結構體AVCodec、AVCodecContext,以及與音視頻數據有關的結構體AVStream、AVPacket、AVFrame等等。剛開始接觸FFmpeg時,個人感覺一時間要理解區分這些結構體還是有點困難的,好在這些結構體當中有個“老大哥”-AVFormatContext,AVFormatContext可以說是貫穿整個FFmpeg開發,“猶如神一般的存在”。下面我們就在分析AVFormatContext結構體的基礎上,闡述上述結構體的作用與區別。

AVFormatContext

 AVFormatContext結構體描述了一個多媒體文件或流的構成和基本信息,是FFmpeg中最爲基本的一個結構體,也是其他所有結構的根。其中,成員變量iformat和oformat爲指向對應的demuxing(解封裝)和muxing(封裝)指針,變量類型分別爲AVInputFormat、AVOutputFormat;pb爲指向控制底層數據讀寫的指針,變量類型爲AVIOContext;nb_streams表示多媒體文件或多媒體流中數據流的個數;streams爲指向所有流存儲的二級指針,變量類型AVStream;video_codec和audio_codec分別表示視頻和音頻編解碼器,變量類型爲AVCodec等等。AVFormatContext結構體(位於libavformat/avformat.h中)部分源碼如下:

typedef struct AVFormatContext {
    const AVClass *av_class;
    // 輸入容器格式
    // 只在調用avformat_open_input()時被設置,且僅限Demuxing
    struct AVInputFormat *iformat;
    // 輸出容器格式
    // 只在調用avformat_alloc_output_context2()函數時被設置,且僅限封裝(Muxing)
    struct AVOutputFormat *oformat;

    /**
     * Format private data. This is an AVOptions-enabled struct
     * if and only if iformat/oformat.priv_class is not NULL.
     *
     * - muxing: set by avformat_write_header()
     * - demuxing: set by avformat_open_input()
     */
    void *priv_data;
    // 輸/入輸出(I/O)的緩存
    // 說明:解封裝(demuxing):值由avformat_open_input()設置
    //          封裝(muxing):  值由avio_open2設置,需在avformat_write_header()之前
    AVIOContext *pb;
    // stream info
    int ctx_flags;
    // AVFormatContext.streams中數據流的個數
    // 說明:值由avformat_new_stream()設置
    unsigned int nb_streams;
    // 文件中所有流stream列表。創建一個新stream,調用avformat_new_stream()函數實現
    // 當調用avformat_free_context()後,streams所佔資源被釋放
    // 說明:解封裝(demuxing):當調用avformat_open_input()時,streams值被填充
    //        封裝(muxing):streams在調用avformat_write_header()之前被用戶創建
    // 
    AVStream **streams;
    // 輸入或輸出文件名,如輸入:rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov
    // 說明:demuxing:當調用avformat_open_input()後被設置
    //         muxing: 當調用avformat_alloc_output_context2()後被設置,且需要調用avformat_write_header()之前
    char filename[1024];
    // component的第一幀位置,僅限Demuxing時由libavformat設置
    int64_t start_time;
    // stream的時長,僅限Demuxing時由libavformat設置
    int64_t duration;
    // 總比特率(bit/s),包括音頻、音頻
    int64_t bit_rate;

    ...

    // 視頻編解碼器ID
    // 說明:Demuxing時由用戶設置
    enum AVCodecID video_codec_id;

    // 音頻編解碼器ID
    // 說明:Demuxing時由用戶設置
    enum AVCodecID audio_codec_id;

    // 字幕(subtitle)編解碼器ID
    // 說明:Demuxing時由用戶設置
    enum AVCodecID subtitle_codec_id;
    
    ...

    // 文件元數據,即Metadata
    // 說明:demuxing:當調用avformat_open_input時被設置
    //         muxing:在調用avformat_write_header()之前被設置
    // 注:當調用avformat_free_context()時metadata的資源被libavformat釋放
    AVDictionary *metadata;

    // 實時流啓動的真實時間
    int64_t start_time_realtime;
    
    ...

    // 視頻編解碼器,Demuxing時由用戶指定
    AVCodec *video_codec;
    // 音頻編解碼器,Demuxing時由用戶指定
    AVCodec *audio_codec;
    // 字幕編解碼器,Demuxing時由用戶指定
    AVCodec *subtitle_codec;

    // 數據編解碼器,Demuxing時由用戶指定
    AVCodec *data_codec;

    ...
    
    // 數據編解碼器ID
    enum AVCodecID data_codec_id;
    
    ...
    
} AVFormatContext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
1. 複用(muxing)/解複用(demuxing)

(1) AVInputFormat結構體

 AVInputFormat爲解複用/解封裝(demuxing)器對象,它包含了解複用器的相關信息和操作函數,比如name成員變量爲指定封裝格式的名稱,如"aac"、"mov"等;read_header成員函數爲讀取封裝頭部數據;read_packet成員函數爲讀取一個AVPacket等等。AVInputFormat結構體(位於libavformat/avformat.h中)部分源碼如下:

typedef struct AVInputFormat {
    // 封裝格式名稱,如"mp4"、"mov"等
    const char *name;
    // 封裝格式別稱
    const char *long_name;
    int flags;
    const char *extensions;
    const struct AVCodecTag * const *codec_tag;
    const AVClass *priv_class; 
    const char *mime_type;
    // 
    struct AVInputFormat *next;
    int raw_codec_id;
    // 具體format對應Context的size,如MovContext
    int priv_data_size;
    int (*read_probe)(AVProbeData *);
     // 讀取format header,同時初始化AVFormatContext結構
     // 若成功,返回0
    int (*read_header)(struct AVFormatContext *);
    // 讀取packet大小數據,並將其存放到pkt指向的內存中
    // 若成功,返回0;若失敗,返回負數且pkt不會被分配內存
    int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);
     // 關閉流,但不釋放AVFormatContext和AVStreams所佔內存 
    int (*read_close)(struct AVFormatContext *);
    /**
     * seek相對於流索引中幀的時間戳
     * @param stream_index 流index,不能爲-1
     * @param flags 用於方向,如果麼有精確的匹配
     * @return >= 0 操作成功
     */
    int (*read_seek)(struct AVFormatContext *,
                     int stream_index, int64_t timestamp, int flags);
    /**
     * 獲取流[stream_index]的下一個時間戳
     * @return 時間戳或AV_NOPTS_VALUE(當發生錯誤時)
     */
    int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index,
                              int64_t *pos, int64_t pos_limit);
     // Start/resume playing -只適用於RTSP
    int (*read_play)(struct AVFormatContext *);
    // Pause playing - 只適用於RTSP
    int (*read_pause)(struct AVFormatContext *);
    // 獲取設備列表,詳解avdevice_list_devices() 
    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
     // 初始化設備功能子模塊,詳見avdevice_capabilities_create()函數
    int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
     // 釋放設備功能子模塊,詳見avdevice_capabilities_free()函數
    int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
} AVInputFormat;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
 通過調用av_register_all()函數,FFmpeg所有的解複用器保存在以first_iformat爲頭部指針、last_iformat爲尾部指針的鏈表中。這裏以AAC(音頻壓縮編碼格式)解複用器爲例,來分析AVInputFormat結構體的初始化流程,相關源碼詳見libavformat/Aacdec.c:

AVInputFormat ff_aac_demuxer = {
    .name         = "aac",  // 指定解複用器名稱
    .long_name    = NULL_IF_CONFIG_SMALL("raw ADTS AAC (Advanced Audio Coding)"),  // 指定AAC對應的文件格式
    .read_probe   = adts_aac_probe, // 探測函數
    .read_header  = adts_aac_read_header, // 讀取頭部數據函數
    .read_packet  = adts_aac_read_packet, // 讀取數據包函數
    .flags        = AVFMT_GENERIC_INDEX,    
    .extensions   = "aac",    // 後綴
    .mime_type    = "audio/aac,audio/aacp,audio/x-aac",
    .raw_codec_id = AV_CODEC_ID_AAC,// AAC解碼器ID
};
1
2
3
4
5
6
7
8
9
10
11
(2) AVOutputFormat結構體

 與AVInputFormat相反,AVOtputFormat爲複用/封裝(muxing)器對象,它包含了複用器的相關信息和操作函數,比如name成員變量爲指定封裝格式的名稱,如"mp4"、"3gp"等;write_header成員函數爲讀取封裝頭部數據;write_packet成員函數爲寫入一個AVPacket等等。AVOutputFormat結構體(位於libavformat/avformat.h中)部分源碼如下:

typedef struct AVOutputFormat {
    // 封裝格式名稱,如"mp4"
    const char *name;
    // 文件格式
    const char *long_name;
    // mime類型
    const char *mime_type;
    const char *extensions; /**< 逗號分隔的文件擴展名 */
    /* output support */
    enum AVCodecID audio_codec;    /**< 默認音頻codec(編解碼器) */
    enum AVCodecID video_codec;    /**< 默認視頻codec */
    enum AVCodecID subtitle_codec; /**< 默認subtitle codec */
    /**
     * flags可取值:AVFMT_NOFILE, AVFMT_NEEDNUMBER,
     * AVFMT_GLOBALHEADER, AVFMT_NOTIMESTAMPS, AVFMT_VARIABLE_FPS,
     * AVFMT_NODIMENSIONS, AVFMT_NOSTREAMS, AVFMT_ALLOW_FLUSH,
     * AVFMT_TS_NONSTRICT, AVFMT_TS_NEGATIVE
     */
    int flags;
    const struct AVCodecTag * const *codec_tag;
    const AVClass *priv_class; ///< AVClass for the private context
    struct AVOutputFormat *next;
    // private data的大小
    int priv_data_size;
    // 寫header
    int (*write_header)(struct AVFormatContext *);
    // 寫一個packet。如果flags=AVFMT_ALLOW_FLUSH,pkt可爲NULL,以便flush muxer中的緩衝數據
    // 返回0,表示緩衝區仍還有數據可flush;返回1,表示緩衝區無可flush得數據 
    int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);
    int (*write_trailer)(struct AVFormatContext *);
    // 如果不是YUV420P,目前只支持設置像素格式
    int (*interleave_packet)(struct AVFormatContext *, AVPacket *out,
                             AVPacket *in, int flush);
    // 測試給定的編解碼器是否可以存儲在這個容器中                      
    int (*query_codec)(enum AVCodecID id, int std_compliance);

    void (*get_output_timestamp)(struct AVFormatContext *s, int stream,
                                 int64_t *dts, int64_t *wall);
    int (*control_message)(struct AVFormatContext *s, int type,
                           void *data, size_t data_size);
    // 寫未編碼的AVFrame幀數據,詳見av_write_uncoded_frame()
    int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index,
                               AVFrame **frame, unsigned flags);
    /**
     * Returns device list with it properties.
     * @see avdevice_list_devices() for more details.
     */
    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
    /**
     * Initialize device capabilities submodule.
     * @see avdevice_capabilities_create() for more details.
     */
    int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
     // 釋放設備功能子模塊,詳見avdevice_capabilities_free()
    int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);
    enum AVCodecID data_codec; /**< default data codec */
    /**
     * 初始化format. 分配數據內存,設置AVFormatContext或    AVStream參數,與deinit()配合使用,釋放分配的內存資源
     * 返回0,配置成功;返回1,配置失敗。
     */
    int (*init)(struct AVFormatContext *);
    /** 釋放init分配的內存資源,無論調用init()是否成功
     */
    void (*deinit)(struct AVFormatContext *);
    /** 檢測比特流
     * 如果返回0,表示需要檢測流的更多packets;返回-1,則不需要
     */
    int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);
} AVOutputFormat;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
 同樣,通過調用av_register_all()函數,FFmpeg所有的複用器保存在以first_oformat爲頭部指針、last_oformat爲尾部指針的鏈表中。這裏以mp4(視頻壓縮編碼格式)複用器爲例,來分析AVOutputFormat結構體的初始化流程,相關源碼詳見libavformat/Movenc.c:

AVOutputFormat ff_mp4_muxer = {
    .name              = "mp4", //複用器名稱
    .long_name         = NULL_IF_CONFIG_SMALL("MP4 (MPEG-4 Part 14)"),                //mp4對應的文件格式
    .mime_type         = "video/mp4",// MIME類型
    .extensions        = "mp4",         // 文件擴展名
    .priv_data_size    = sizeof(MOVMuxContext),
    .audio_codec       = AV_CODEC_ID_AAC,// 音頻編碼器ID
    .video_codec       = CONFIG_LIBX264_ENCODER ?
                         AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4,// 視頻編碼器ID
    .init              = mov_init,    // 初始化函數
    .write_header      = mov_write_header, // 寫入頭部
    .write_packet      = mov_write_packet, // 寫入Packet
    .write_trailer     = mov_write_trailer,
    .deinit            = mov_free, // 釋放資源
    .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE,
    .codec_tag         = (const AVCodecTag* const []){ codec_mp4_tags, 0 },
    .check_bitstream   = mov_check_bitstream,
    .priv_class        = &mp4_muxer_class,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2. 輸入/輸出(I/O)

(1) AVIOContext結構體

 AVIOContext是FFmpeg管理輸入輸出(I/O)數據的結構體,它是協議(文件)操作的頂層結構,提供帶緩衝的讀寫操作。有關讀寫操作和成員變量的含義,可見如下源碼中給出的註釋示意圖:

讀取數據:

寫入數據:

AVIOContext結構體位於libavformat/avio.h中,部分源碼如下:
typedef struct AVIOContext {
    const AVClass *av_class;
    unsigned char *buffer;  // 數據緩衝區
    int buffer_size;        // 緩存的大小
    unsigned char *buf_ptr; // 指針指向緩存區的當前位置,可小於buffer+buffer.size
    unsigned char *buf_end; // 讀取/寫入緩存區數據的末尾位置 
    // 私有指針,關聯URLContext結構,作爲read/write/seek/...函數參數
    // 用於完成對廣義輸入文件的讀寫等操作,指向一個URLContext對象
    void *opaque;           
    // 讀packet數據
    int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);
    // 寫數據到packet
    int (*write_packet)(void *opaque, uint8_t *buf, int buf_size);
    // 定位
    int64_t (*seek)(void *opaque, int64_t offset, int whence);
    int64_t pos;      // 當前緩存區域在文件中的位置
    int eof_reached;  // 是否到達文件末尾,true表示已經到末尾
    int write_flag;   // 是否可寫標誌,true表示open可寫
    int max_packet_size; // packet最大尺寸
    unsigned long checksum;
    unsigned char *checksum_ptr;
    unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);
    // 錯誤代碼,0表示沒有錯誤出現
    int error;      
    //網絡流媒體協議暫停或恢復播放
    int (*read_pause)(void *opaque, int pause); 
    int64_t (*read_seek)(void *opaque, int stream_index,
                         int64_t timestamp, int flags);
    // 0表示網絡流不可seek
    int seekable;
    // 寫入緩衝區中向後查找之前的最大到達位置,用於跟蹤已寫入的數據,以便稍後刷新
    unsigned char *buf_ptr_max;
    // packet最小尺寸
    int min_packet_size;
    // 以下字段大部分僅限libavformat內部使用或用的不多,這裏不作解釋
    int64_t maxsize;
    int direct;
    int64_t bytes_read;
    int seek_count;
    int writeout_count;
    int orig_buffer_size;
    int short_seek_threshold;
    const char *protocol_whitelist;
    const char *protocol_blacklist;
    int (*write_data_type)(void *opaque, uint8_t *buf, int buf_size,
                           enum AVIODataMarkerType type, int64_t time);
    int ignore_boundary_point;
    enum AVIODataMarkerType current_type;
    int64_t last_time;
    int (*short_seek_get)(void *opaque);
    int64_t written;
} AVIOContext;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
 其中,AVIOContext的成員變量opaque指向一個URLContext對象,URLContext中是對具體資源文件進行操作的上下文,它包括一個URLProtocol結構體類型的指針變量prot。URLProtocol則是在將資源進行分類的基礎上,對某一類資源操作的函數集。URLContext結構體源碼如下:

typedef struct URLContext {
    const AVClass *av_class;   
    // 關聯/指向相應的廣義輸入文件
    const struct URLProtocol *prot;  
    // 關聯具體廣義輸入文件的句柄,如fd爲文件句柄,socket爲網絡句柄
    void *priv_data;             
    char *filename;             // 指定的URL
    int flags;
    int max_packet_size;        
    int is_streamed;            // true爲流,默認爲false
    int is_connected;
    AVIOInterruptCB interrupt_callback;
    int64_t rw_timeout;        // read/write操作超時時間
    const char *protocol_whitelist;
    const char *protocol_blacklist;
    int min_packet_size;        
} URLContext;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(2) URLProtocol結構體

 URLProtocol結構體表示廣義的輸入文件,是FFmpeg操作I/O的結構,包括文件(file)、網絡數據流(tcp、rtp、… )等等,每種協議都對應着一個URLProtocol結構。該結構位於libavformat/url.h文件中,包括open、close、read、write、seek等操作,部分源碼如下:

typedef struct URLProtocol {
    // 協議名稱
    const char *name;
    int     (*url_open)( URLContext *h, const char *url, int flags);
    int     (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);
    int     (*url_accept)(URLContext *s, URLContext **c);
    int     (*url_handshake)(URLContext *c);

    /**
     * Read data from the protocol.
     */
    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);
    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 (*url_get_multi_file_handle)(URLContext *h, int **handles,
                                     int *numhandles);
    int (*url_get_short_seek)(URLContext *h);
    int (*url_shutdown)(URLContext *h, int flags);
    int priv_data_size;
    const AVClass *priv_data_class;
    int flags;
    int (*url_check)(URLContext *h, int mask);
    int (*url_open_dir)(URLContext *h);
    int (*url_read_dir)(URLContext *h, AVIODirEntry **next);
    int (*url_close_dir)(URLContext *h);
    int (*url_delete)(URLContext *h);
    int (*url_move)(URLContext *h_src, URLContext *h_dst);
    const char *default_whitelist;
} URLProtocol;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 接下來,這裏以HTTP協議爲例,闡述URLProtocol結構體的初始化流程,同時也證明了每一種協議(包括文件)相對應一個URLProtocol對象。具體源碼如下,位於libavformat/Http.c:

const URLProtocol ff_http_protocol = {
    .name                = "http",    //協議名稱
    .url_open2           = http_open, // open操作
    .url_accept          = http_accept,//accept操作
    .url_handshake       = http_handshake,// 握手操作
    .url_read            = http_read,    // 讀取數據操作
    .url_write           = http_write,    // 寫入數據操作
    .url_seek            = http_seek,    // seek操作
    .url_close           = http_close,    // close操作
    .url_get_file_handle = http_get_file_handle,
    .url_get_short_seek  = http_get_short_seek,
    .url_shutdown        = http_shutdown,
    .priv_data_size      = sizeof(HTTPContext),
    .priv_data_class     = &http_context_class,
    .flags               = URL_PROTOCOL_FLAG_NETWORK,
    .default_whitelist   = "http,https,tls,rtp,tcp,udp,crypto,httpproxy"
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
3.編/解碼

(1) AVCodec結構體

 AVCodec是與編解碼器(codec)息息相關的數據結構體,它包含了與codec相關的屬性參數以及編解碼操作函數等,比如name爲codec的名稱、pix_fmts爲codec的視頻幀像素格式等等,每一個codec都對應着一個AVCodec結構體。
AVCodec結構體源碼如下:

typedef struct AVCodec {
    // 編解碼器名稱
    const char *name;
    // 描述編解碼器的名稱
    const char *long_name;
    // media type
    enum AVMediaType type;
    // 該codec的ID
    enum AVCodecID id;
    int capabilities;
    // 該codec相關的參數
    const AVRational *supported_framerates; 
    // 該codec支持的像素格式,針對視頻幀/圖像而言
    const enum AVPixelFormat *pix_fmts;    
    // 該codec支持的採樣率,針對音頻而言
    const int *supported_samplerates;      
    // 該codec支持的採樣格式,針對音頻而言
    const enum AVSampleFormat *sample_fmts; 
    // 該codec的通道佈局
    const uint64_t *channel_layouts;      
    // 解碼器支持的低分辨率的最大值
    uint8_t max_lowres;                     
    const AVClass *priv_class;             
    const AVProfile *profiles;             
    const char *wrapper_name;
    int priv_data_size;
    struct AVCodec *next;
    int (*init_thread_copy)(AVCodecContext *);

    int (*update_thread_context)(AVCodecContext *dst, const AVCodecContext *src);
    const AVCodecDefault *defaults;
    // 執行avcodec_register()函數被調用,
    // 用於初始化codec的靜態數據
    void (*init_static_data)(struct AVCodec *codec);

    // 初始化
    int (*init)(AVCodecContext *);
    int (*encode_sub)(AVCodecContext *, uint8_t *buf, int buf_size,
                      const struct AVSubtitle *sub);
    /**
     * 編碼操作:將編碼後的數據保存到AVPacket
     *
     * @param      avctx          codec上下文(context)
     * @param      avpkt          輸出的AVPacket
     * @param[in]  frame          AVFrame存儲的是要被壓縮編碼的裸數據
     * @param[out] got_packet_ptr 編碼器設置爲0或1,以指示avpkt中返回的非空包
     * @return 0 操作成功
     */
    int (*encode2)(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame,
                   int *got_packet_ptr);
    // 解碼操作
    int (*decode)(AVCodecContext *, void *outdata, int *outdata_size, AVPacket *avpkt);
    // 關閉codec
    int (*close)(AVCodecContext *);
    // Encode API with decoupled packet/frame dataflow. 
    int (*send_frame)(AVCodecContext *avctx, const AVFrame *frame);
    int (*receive_packet)(AVCodecContext *avctx, AVPacket *avpkt);

    // Decode API with decoupled packet/frame dataflow. 
    int (*receive_frame)(AVCodecContext *avctx, AVFrame *frame);
    // flush緩衝區,執行seeking操作是被調用
    void (*flush)(AVCodecContext *);
      ...
} AVCodec;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
(2) AVCodecContext結構體

 也許你會發現,對於編解碼而言,除了AVCodec這個非常重要的結構體,在AVCodec的成員函數中還有一個出現頻率非常高的結構體,可以這麼說大部分與編解碼有關的函數都需要傳入一個結構體參數,這個結構體就是AVCodecContext。AVCodecContext結構體存儲視頻流或音頻流使用的編解碼相關信息,比如codec_type表示編解碼器的類型、codec表示採用的編解碼器等等。AVCodecContext結構體源碼如下:

typedef struct AVCodecContext {
    enum AVMediaType codec_type; /* 編解碼器的類型(視頻,音頻...) */
    const struct AVCodec  *codec;// 採用的解碼器AVCodec(H.264,MPEG2...)
    enum AVCodecID     codec_id; /* see AV_CODEC_ID_xxx */
    // 比特率(音頻和視頻的平均比特率)
    int64_t bit_rate;
    // 壓縮編碼的等級
    int compression_level;
     // 針對特定編碼器包含的附加信息(例如對於H.264解碼器來說,存儲SPS,PPS等)
    uint8_t *extradata; 
    int extradata_size;
    // 時基
    // 根據該參數,可以把PTS轉化爲實際的時間(單位爲秒s)
    AVRational time_base;
    // 圖像寬、高,針對視頻而言
    int width, height;
    // 像素格式,針對視頻而言
    enum AVPixelFormat pix_fmt;
    // 獲取像素格式
    enum AVPixelFormat (*get_format)(struct AVCodecContext *s, const enum AVPixelFormat * fmt);
    // 非B幀之間的最大B幀數
    int max_b_frames;
     // I/P幀和B幀之間的qscale因子
    float b_quant_factor;
     // 採樣縱橫比
    AVRational sample_aspect_ratio;
    // 音頻一幀採樣樣本個數
    int frame_size;
    // 音頻通道佈局
    uint64_t channel_layout;
    // 幀率
    AVRational framerate;
    ...
} AVCodecContext;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
4.數據相關結構體

(1) AVStream結構體

 AVStream結構體用於存儲一個視頻或音頻流信息,其中,字段nb_frames表示該流包含多少幀數據、字段duration表示該流的長度、字段index標誌是音頻流還是視頻流等等。

typedef struct AVStream {
    // 標誌視頻流或音頻流,存儲在AVFormatContext中
    int index;    /**< stream index in AVFormatContext */
    
    // 指向該視頻/音頻流的AVCodecContext
    // @deprecated use the codecpar struct instead
    AVCodecContext *codec;
    // 時基。通過該值可以把PTS,DTS轉化爲真正的時間
    AVRational time_base;

    // 該視頻/音頻流的長度
    int64_t duration;
    // 該視頻/音頻流的幀數
    int64_t nb_frames;              
    // 元數據信息
    AVDictionary *metadata;
    // 幀率(對視頻來說很重要)
    AVRational avg_frame_rate;
    // 附帶的圖片。比如說一些MP3,AAC音頻文件附帶的專輯封面
    AVPacket attached_pic;
    
    ...

    // 與該視頻流或音頻流相關的Codec參數
    // 由avformat_new_stream()分配、avformat_free_context()釋放
    AVCodecParameters *codecpar;
} AVStream;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(2) AVPacket結構體

 AVPacket結構體用於存儲壓縮編碼的視頻或音頻數據相關信息,其中,字段stream_index標誌AVPacket所屬的是音頻流還是視頻流。比如對於H.264來說,通常一個AVPacket的data對應着一個NAL,而一個NAL存儲着一幀圖像。
AVPacket結構體源碼如下:

typedef struct AVPacket {
    
    AVBufferRef *buf;
    /**
     * Presentation timestamp in AVStream->time_base units; the time at which
     * the decompressed packet will be presented to the user.
     */
    // Presentation timestamp,即顯示時間戳
    int64_t pts;
    /**
     * Decompression timestamp in AVStream->time_base units; the time at which
     * the packet is decompressed.
     */
    // Decompression timestamp,即解碼時間戳
    int64_t dts;
    // 壓縮編碼的視頻或音頻數據
    uint8_t *data;
    // data的大小
    int   size;
    // 標誌該AVPacket所屬的是音頻流還是視頻流
    int   stream_index;

    int   flags;
    AVPacketSideData *side_data;
    int side_data_elems;

    /**
     * Duration of this packet in AVStream->time_base units, 0 if unknown.
     * Equals next_pts - this_pts in presentation order.
     */
    // 該AVPacket的長度
    int64_t duration;
    // 該AVPacket在流中的字節位置,-1表示未知
    int64_t pos;                           
} AVPacket;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(3) AVFrame結構體

 AVFrame結構體用於存儲解碼後的視/音頻數據相關信息,表示一幀數據。如果AVFrame爲視頻幀數據結構體,字段data數組存儲的是一幀圖像、字段width、height爲圖像的寬、高、key_frame爲是否爲關鍵幀標誌等等;如果AVFrame爲音頻數據結構體,字段data數組存儲的是音頻數據,可包含多幀音頻、字段sample_rate爲音頻的採樣率、字段channels爲音頻通道數量等等。
AVFrame結構體源碼如下:

typedef struct AVFrame {
    // 解碼後的原始數據(視頻-YUV或RGB;音頻-PCM)
    // 對於packed格式的數據(如RGB24),會存儲在data[0]
    // 對於plannar格式的數據(如YUV420P),Y分量存儲在data[0]、U分量存儲在data[1]、V分量存儲在data[2]
    uint8_t *data[AV_NUM_DATA_POINTERS];
    // data一行數據的長度
    // 注意:如果是圖像不一定等於圖像的寬度,往往大於圖像的寬
    int linesize[AV_NUM_DATA_POINTERS];
    // 視頻幀的寬、高
    int width, height;
    // 該AVFrame包含幾個音頻幀
    int nb_samples;
    // 解碼後原始數據類型,比如YUV420、RGB..
    // 音頻,詳見AVSampleFormat
    // 視頻,詳見AVPixelFormat
    int format;
    // 是否爲關鍵幀,對視頻來說非常重要
    // 1 -> keyframe, 0-> not
    int key_frame;
    // 幀類型,比如I幀、B幀、P幀...
    enum AVPictureType pict_type;
    // 視頻幀寬高比,如16:9、4:3...
    AVRational sample_aspect_ratio;
    // 顯示時間戳
    int64_t pts;
    // 編碼圖像幀序號
    int coded_picture_number;
    // 顯示圖像幀序號
    int display_picture_number;
    // 音頻採樣率
    int sample_rate;
    // 音頻通道layout
    uint64_t channel_layout;
    // YUV顏色空間類型
    enum AVColorSpace colorspace;
    // 元數據
    AVDictionary *metadata;
    // 音頻通道數量
    int channels;
    ...
} AVFrame;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 至此,FFmpeg框架中最爲重要的結構體,我們基本講解梳理完畢。最後,再借用雷神的FFmpeg關鍵結構體關係圖作爲結尾,一是使得本文能夠前後呼應,二是向大神致敬!


Github實戰項目:https://github.com/jiangdongguo/FFMPEG4Android
————————————————
版權聲明:本文爲CSDN博主「無名之輩FTER」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/AndrExpert/article/details/83268563

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章