FFmpeg的幀

以前FFmpeg頻頻出場,都是它的應用,但FFmpeg自己的結構或流程卻尚未介紹過。就「能用便可」的角度,能把FFmpeg這個黑盒子用好,就已是很好的成績了。數組

但追求理解甚至想修改FFmpeg的你,應該會關心FFmpeg自己的結構與處理流程。ide

因而,小程準備用若干篇文章來介紹FFmpeg的結構與流程。在介紹過程當中,小程儘可能引用具體的數值,讓你對結構有個直觀的感知。爲了拿到具體的數據,須要調試FFmpeg的代碼,這部分的內容(包括gdb的使用)小程已經在前面的章節介紹過了。函數

本文介紹FFmpeg的幀的結構。編碼

這裏的幀並非咱們說的圖像幀,它只是一個數據載體或一個結構體而已(能夠是圖像或音頻數據)。指針

FFmpeg的「幀」涉及到兩個結構,即AVPacket,以及AVFrame。調試

(一)AVPacket

AVPacket,是壓縮數據的結構體,也就是解碼前或編碼後的數據的載體。code

爲了查看AVPacket結構體的變量的值,小程寫了一段調用FFmpeg的代碼:orm

#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"

void show_frame(const char* filepath) {
    av_register_all();
    av_log_set_level(AV_LOG_DEBUG);
    AVFormatContext* formatContext = avformat_alloc_context();
    int status = 0;
    int success = 0;
    int videostreamidx = -1;
    AVCodecContext* codecContext = NULL;
    status = avformat_open_input(&formatContext, filepath, NULL, NULL);
    if (status == 0) {
        status = avformat_find_stream_info(formatContext, NULL);
        if (status >= 0) {
            for (int i = 0; i < formatContext->nb_streams; i ++) {
                if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
                    videostreamidx = i;
                    break;
                }
            }
            if (videostreamidx > -1) {
                AVStream* avstream = formatContext->streams[videostreamidx];
                codecContext = avstream->codec;
                AVCodec* codec = avcodec_find_decoder(codecContext->codec_id);
                if (codec) {
                    status = avcodec_open2(codecContext, codec, NULL);
                    if (status == 0) {
                        success = 1;
                    }
                }
            }
        }
        else {
            av_log(NULL, AV_LOG_DEBUG, "avformat_find_stream_info error\n");
        }

        if (success) {
            av_dump_format(formatContext, 0, filepath, 0);
            int gotframe = 0;
            AVFrame* frame = av_frame_alloc();
            int decodelen = 0;
            int limitcount = 10;
            int pcindex = 0;
            while (pcindex < limitcount) {
                AVPacket packet;
                av_init_packet( &packet );
                status = av_read_frame(formatContext, &packet);
                if (status < 0) {
                    if (status == AVERROR_EOF) {
                        av_log(NULL, AV_LOG_DEBUG, "read end for file\n");
                    }
                    else {
                        av_log(NULL, AV_LOG_DEBUG, "av_read_frame error\n");
                    }
                    av_packet_unref(&packet);
                    break;  
                }
                else {
                    if (packet.stream_index == videostreamidx) {
                        decodelen = avcodec_decode_video2(codecContext, frame, &gotframe, &packet);
                        if (decodelen > 0 && gotframe) {
                            av_log(NULL, AV_LOG_DEBUG, "got one avframe, pcindex=%d\n", pcindex);
                        }   
                    }
                } 
                av_packet_unref(&packet);
                pcindex ++;
            }
            av_frame_free(&frame);
        }
        avformat_close_input(&formatContext);
    }
    avformat_free_context(formatContext);
}

int main(int argc, char *argv[])
{
    show_frame("moments.mp4");
    return 0;
}

從上面的代碼能夠看到,調用av_read_frame能夠取得一個AVPacket,因此在調用這個函數的地方下個斷點,看一下AVPacet長什麼樣子。視頻

先說一下怎麼編譯上面這段代碼,我使用的是mac電腦。blog

把上面的代碼保存成show_frame.c文件,而後寫一個makefile編譯腳本(保存成makefile文件,與show_frame.c在同一目錄),內容以下:

exe=showframe
srcs=show_frame.c 
$(exe):$(srcs)
    gcc -o $(exe) $(srcs) -Iffmpeg/include/ -Lffmpeg -lffmpeg -liconv -lz -g
clean:
    rm -f $(exe) *.o

整個項目的代碼目錄結構是這樣的:
項目目錄結構

注意,上圖的show_avcodec.c要換成show_frame.c,小程沿用了另外一個例子的截圖,而且沒有更改:)。

另外,FFmpeg是事先就編譯了的,對於FFmpeg的編譯,前文已說。

準備好環境後,就能夠編譯這段代碼了:

make

編譯代碼後,使用gdb啓動調試:

gdb showframe
b 38
r

單步調試時,能夠看到,在沒有調用av_read_frame前,AVPacket中的變量值:
沒有調用av_read_frame的avpacket

調用av_read_frame後,AVPacket中的變量:
avpacket1

再一次av_read_frame後:
avpacket2

av_read_frame後,AVPacket也可能沒有數據:
avpacket沒有數據的狀況

AVPacket是壓縮數據,一個AVPacket,最多包含一幀視頻數據,但能夠包括多幀音頻數據。AVPacket中的變量含義:

pts/dts,顯示/解碼時間戵,以packet所在的流的time_base爲單位。
stream_index,所在流的索引。
data,avpacket擁有的數據。
size,avpacket的數據長度。
duration,avpacket的時長,一樣以time_base爲單位。

AVPacket結構,在libavcodec/avcodec.h中定義,你能夠詳細看下這個頭文件的說明。

(二)AVFrame

AVFrame,是原始數據的結構體,也就是解碼後或編碼前的數據的載體。

能夠簡單理解爲,AVFrame就是原始的音頻或視頻數據。有了它,能夠作一些處理,好比音效或圖效處理、特徵提取、特徵圖繪製,等等。

爲了看AVFrame的數據,使用調試AVPacket的代碼便可,部分代碼如截圖:
調試AVFrame的代碼片斷

而後在avcodec_decode_video2的調用處下個斷點,使用gdb進行單步調試。

能夠看到,在解碼前,avframe是這樣的:
解碼前avframe的內容

解碼後,而且保證有解碼到一幀數據時,avframe是這樣的:
解碼後avframe的內容

如下對AVFrame的一些變量做一些解釋:

data,指針數組(最多8個指針),每一個指針指向不一樣維度的byte數據。
對於視頻來講,若是是planar的,則data[0]可能指向Y維度的數據,data[1]可能指向U維度的數據。
對於音頻來講,若是聲道是平面組織的(planar),則data[0]指向一個聲道,data[1]指向另外一個聲道...;若是聲音是打包形式的(packed,即左右不分開),則只有data[0]。

linesize,長度的數組。
對於視頻,若是是planar數據,則linesize[i]是某個維度的一行的長度;若是是packet數據,則只有linesize[0],並且表示全部數據的長度。
對於音頻,只有linesize[0]可用;若是是planar數據,則linesize[0]對應data[i]的長度(每一個data[i]是同樣的長度);若是是packed數據,則linesize[0]表示data[0]的長度。對於音頻,沒有「一行」的概念,linesize[0]表示的是整個長度。
對於視頻,注意linesize[i]表示一行的長度時,可能比實際的數據的長度(寬)要大。

extern_data,對於視頻,等同於data。對於音頻,常常用於packed數據。
width/height,視頻寬高。
nb_samples,一個聲道的樣本數。
format,視頻的顏色空間,或音頻的樣本格式。
key_frame,是否爲關鍵幀。
pict_type,視頻幀的類型(ipb幀等)。
sample_aspect_ratio,寬高比例。
pts,表現時間戵。
pkt_pts,對應的AVPacket的pts。
quality,質量係數。
sample_rate,音頻採樣率。
channels,聲道數。

AVFrame結構,在libavutil/frame.h中定義,你能夠詳細閱讀裏面的說明。

至此,FFmpeg的幀結構就介紹完畢了。

總結一下,本文介紹了FFmpeg的幀的結構(其實是數據載體),包括AVFrame與AVPacket,而且經過調試查看告終構中變量的值的變化。


我思故我在

相關文章
相關標籤/搜索