[SimplePlayer] 1. 從視頻文件中提取圖像


在開始之前,我們需要了解視頻文件的格式。視頻文件的格式眾多,無法三言兩語就能詳細分析其結構,盡管如此,ffmpeg卻很好地提取了各類視頻文件的共同特性,並對其進行了抽象描述。

image

視頻文件格式,統稱為container。它包含一個描述視頻信息的頭部,以及內含實際的音視頻編碼數據的packets。當然,這里的頭部以及packet部分只是個抽象描述,實際的視頻格式的描述信息可能不是存放在視頻文件的起始位置,可能是由分散於視頻文件的各個位置的多個部分組成;數據包有可能是由頭部以及尾部進行分割的傳統數據包形式,也有可能是一大塊數據區域,由索引進行各個數據包的分割。

視頻文件中的packets最主要的就是視頻以及音頻packets,demux的過程就是解析container的header來獲取視頻信息,所得到的視頻信息能幫助我們區分packet是音頻或者視頻。同樣屬性的packets會被稱為stream。

packet中存儲的數據就是音視頻編碼后的數據,通過解碼器進行decode后就能得到視頻圖像或者音頻幀。其中需要注意的一點是,一個packet不一定對應一幀,packet的順序也不一定是實際的播放順序,而通過ffmpeg解碼出來的frame的順序就是實際的播放順序。

 

Demux

首先需要一個用於存儲視頻文件信息的結構體。

pFormatCtx = avformat_alloc_context();

 

讀取視頻文件,並對該文件進行demux,所得到的視頻信息存儲於剛剛所構建的結構體當中

    if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0){
        fprintf(stderr, "open input failed\n");
        return -1;
    }

如果pFormatCtx=NULL,那么avformat_open_input也能自動為pFormatCtx分配存儲空間。

 

對於有些視頻格式,單單通過demux並不能獲得所有的視頻信息,為了獲得這些信息,還需要讀取並嘗試解碼該視頻幾個最前端packets(通常會解碼每個stream第一個packet)。所讀取的這幾個packets會被緩存以供后續處理。

if(avformat_find_stream_info(pFormatCtx, NULL)<0){
        fprintf(stderr, "find stream info failed\n");
        return -1;
    }

 

從所獲得的信息當中得到video stream序號,后續可以通過stream序號來對packet進行篩選。

videoStream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);

 

 

Decode

創建一個用於存儲以及維護解碼信息結構體。

pCodecCtx = avcodec_alloc_context3(NULL);

 

把demux時所獲得的視頻相關信息傳遞到解碼結構體中。

if(avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStream]->codecpar)<0){
        fprintf(stderr, "copy param from format context to codec context failed\n");
        return -1;
    }

 

根據解碼器id來尋找對應的解碼器

pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if(pCodec==NULL){
        fprintf(stderr, "Unsupported codec,codec id %d\n", pCodecCtx->codec_id);
        return -1;
    }else{
        fprintf(stdout, "codec id is %d\n", pCodecCtx->codec_id);
    }

 

打開該解碼器,主要目的是對解碼器進行初始化

    if(avcodec_open2(pCodecCtx, pCodec, NULL)<0){
        fprintf(stderr, "open codec failed\n");
        return -1;
    }

 

創建一個用於維護所讀取的packet的結構體,一個用於維護解碼所得的frame的結構體

    pPacket = av_packet_alloc();
    pFrame = av_frame_alloc();
    if(pFrame == NULL||pPacket == NULL){
        fprintf(stderr, "cannot get buffer of frame or packet\n");
        return -1;
    }

 

從視頻文件中讀取packet,如果所讀取的packet是video,則進行解碼,解碼所得的幀由pFrame進行維護。當然,並不是每次調用avcodec_decode_video2都會返回一幀,因為也可能會有需要多個packet才能解碼出一幀的情況,因此只有當指示一幀是否解碼完成的frameFinished為1才能對這一幀進行后續處理。

    while(av_read_frame(pFormatCtx, pPacket)>=0){
        //Only deal with the video stream of the type "videoStream"
        if(pPacket->stream_index==videoStream){
            //Decode video frame
            avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, pPacket);
            //fprintf(stdout, "Frame : %d ,pts=%lld, timebase=%lf\n", i, pFrame->pts, av_q2d(pFormatCtx->streams[videoStream]->time_base));
            if(frameFinished){
                if(i>=START_FRAME && i<=END_FRAME){
                    SaveFrame2YUV(pFrame, pCodecCtx->width, pCodecCtx->height, i);
                    i++;
                }else{
                    i++;
                    continue;
                }
            }
        }
        av_packet_unref(pPacket);
    }

當一個packet被解碼后就可以調用av_packet_unref來釋放該packet所占用的空間了。

 

 

Store

視頻文件解碼出來后通常都是YUV格式,Y、U、V三路分量分別存儲在AVFrame的data[0]、data[1]、data[2]所指向的內存區域。linesize[0]、linesize[1]、linesize[2]分別指示了Y、U、V一行所占用的字節數。下面把解碼所得的幀保存為YUV Planar格式。

void SaveFrame2YUV(AVFrame *pFrame, int width, int height, int iFrame){
    static FILE *pFile;
    char szFilename[32];
    int y;

    //Open file
    if(iFrame==START_FRAME){
         sprintf(szFilename, "Video.yuv");
        pFile = fopen(szFilename, "wb");
        if(pFile==NULL)
            return;
    }

    //Write YUV Data, Only support YUV420
    //Y
    for(y=0; y<height; y++){
        fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, pFrame->linesize[0], pFile);
    }
    //U
    for(y=0; y<(height+1)/2; y++){
        fwrite(pFrame->data[1]+y*pFrame->linesize[1], 1, pFrame->linesize[1], pFile);
    }
    //V
    for(y=0; y<(height+1)/2; y++){
        fwrite(pFrame->data[2]+y*pFrame->linesize[2], 1, pFrame->linesize[2], pFile);
    }

    //Close FIle
    if(iFrame==END_FRAME){
        fclose(pFile);
    }
}

 

 

最后就是釋放內存,關閉decoder,關閉demuxer

    av_free(pPacket);
    av_free(pFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM