[音視頻] ffmpeg開發環境搭建&簡介

一、創建工程

在VS中新建一個Win32控制檯程序,然後做一下幾個操作:

1.拷貝FFmpeg的幾種開發文件到項目目錄下

其中包含include文件夾、lib文件夾和動態庫文件(.dll)。

2.在VS中進行以下配置:

1) 配置屬性-->C/C++-->常規-->附加包含目錄,輸入"include"(項目目錄下的include)。

2)配置屬性-->連接器-->常規-->附加庫目錄,輸入"lib"(項目目錄下的lib)

3)配置屬性-->連接器-->輸入-->附加依賴項,輸入lib目錄下所有"*.lib"文件,用分號隔開

4)動態庫不用配置。

3.測試是否成功

編寫代碼:

// testffmpeg.cpp : 定義控制檯應用程序的入口點。
//

#define __STDC_CONSTANT_MACROS

#include "stdafx.h"

extern "C"
{
    #include "libavcodec\avcodec.h"
}


int main()
{
    printf("%s", avcodec_configuration());
    getchar();
    return 0;
}

可以看到運行結果:

 

說明開發環境配置成功。

二、庫和流程介紹

FFmpeg一共包含8個庫:

  • avcodec:編解碼(最重要)。
  • avformat:封裝格式處理(重要)。
  • avfilter:濾鏡特效處理。
  • avdevice:各種設備的輸入輸出。
  • avutil:工具庫(大部分庫需要這個庫的支持)。
  • postproc:後加工。
  • swresample:音頻採樣數據格式轉換。
  • swscale:視頻像素數據格式轉換。

1.視頻讀取和解碼流程

流程描述:

1.ac_register_all():註冊所有組件。一般在使用ffmpeg的開始都會使用。

2.avformat_open_input():打開輸入。例如文件、網絡流等。

3.avformat_find_stream_info():查找流信息,例如文件中包含音頻、視頻等多條碼流。

4.avcodec_find_devoder():查找流對應的解碼器。

5.avcodec_open2():打開解碼器。

6.av_read_frame():讀取一幀壓縮後的數據。

7.AVPacket:一個結構體,用於存放一幀壓縮後的數據。這裏用於存放av_read_frame讀取到的數據。

8.avcodec_devode_video2():使用解碼器解碼AVPacket中的數據。

9.AVFrame:一個結構體,用於存放解碼後的YUV數據(原始圖像)。

10.顯示在輸出設備。

11.avcodec_close():關閉解碼器。

12.avformat_close_input():關閉打開的碼流。

其中6、7、8、9、10爲循環過程,即每次讀取一幀,解碼,顯示。如果沒讀到幀數據,說明整個碼流結束。

三.數據結構介紹

FFmpeg中包含以下一些基本的數據結構:

 

1.AVFormatConext:視頻格式的上下文,其中包含封裝信息,多個碼流的結構體。

2.AVInputFormat:保存封裝信息。

3.AVStream:這是一個數組,一般0表示視頻碼流,1表示音頻碼流。

4.AVCodecContext:解碼器上下文,其中包含解碼器和相關信息。

5.AVCodec:解碼器

6.AVPacket:用於存放壓縮後的視頻數據。

7.AVFrame:用於存放解碼後的數據。

 

1.AVFormatContext

該結構體包含以下幾個變量:

  • iformat:輸入視頻的AVInputFormat結構體指針。
  • nb_streams:輸入視頻的AVStream個數。
  • streams:輸入視頻的AVStream[]數組,其中包含視頻和音頻碼流等。
  • duration:輸入視頻的時長(以毫秒爲單位)。
  • bit_rate:輸入視頻的碼率。
  • 等等。

2.AVInputFormat

該結構體包含以下幾個變量:

  • name:封裝格式名稱,例如FLV等。
  • long_name:封裝格式長名稱,例如FLV<FLASH VIDEO>。
  • extensions:封裝格式的擴展名。
  • id:封裝格式ID。
  • 一些封裝格式處理的接口函數指針。

3.AVStream

  • id:序號
  • codec:該流對應的AVCodecContext結構體指針。
  • time_base:該流的時基。用來計算每一幀播放時間的時基(乘數)。
  • r_frame_rate:該流的幀率。一秒有多少幀畫面。
  • 等等。

4.AVCodecContext

  • codec:編解碼器的AVCodec結構體指針。
  • width,height:圖像的寬高(只針對視頻)。
  • pix_fmt:像素格式(只針對視頻)。
  • sample_rate:採樣率(只針對音頻)。
  • channels:聲道數(只針對音頻)。
  • sample_fmt:採樣格式(只針對音頻)。
  • 等等。

5.AVCodec

  • name:編解碼器名稱。
  • long_name:編解碼器長名稱。
  • type:編解碼器類型,音頻&視頻。
  • id:編解碼器ID。
  • 一些編解碼的接口函數指針。

6.AVPacket

  • pts:顯示時間戳,通過與時基一起計算出具體時間。(由於解碼順序和顯示順序是不同的,所以有pts和dts兩個時間戳,後面詳細解釋)
  • dts:解碼時間戳。
  • data:壓縮編碼數據。
  • size:壓縮編碼數據大小。
  • stream_index:所屬的AVStream。

7.AVFrame

  • data:解碼後的圖像像素數據(音頻採樣數據)。
  • linesize:對視頻來說就是圖像中一行像素的大小;對音頻來說就是整個音頻幀的大小。
  • width,height:圖像的高寬(只針對視頻)。
  • key_frame:是否爲關鍵幀(只針對視頻)。
  • pict_type:幀類型(只針對視頻)。例如I,P,B。

四、實例

打開一個視頻文件,將其內容讀取出來,寫入h264文件,yuv文件:

#include <stdio.h>

#define __STDC_CONSTANT_MACROS

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
};


int main(int argc, char* argv[])
{
    AVFormatContext    *pFormatCtx;
    int                i, videoindex;
    AVCodecContext    *pCodecCtx;
    AVCodec            *pCodec;
    AVFrame    *pFrame,*pFrameYUV;
    uint8_t *out_buffer;
    AVPacket *packet;
    int y_size;
    int ret, got_picture;
    struct SwsContext *img_convert_ctx;
    //輸入文件路徑
    char filepath[]="Titanic.ts";  // 需要處理的視頻文件名,在當前目錄下

    int frame_cnt;

    av_register_all();  // 註冊所有組件
    avformat_network_init();  // 初始化網絡,例如可以讀取rtsp的視頻流
    pFormatCtx = avformat_alloc_context();  // 分配一個AVFormatContext空間

    if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){  // 打開視頻文件
        printf("Couldn't open input stream.\n");
        return -1;
    }
    if(avformat_find_stream_info(pFormatCtx,NULL)<0){  // 查找流信息
        printf("Couldn't find stream information.\n");
        return -1;
    }
    videoindex=-1;
    for(i=0; i<pFormatCtx->nb_streams; i++)   // 找到視頻碼流,並記錄其編號,默認一般爲0(在不確定的情況下,要進行判斷)
        if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){  // AVMEDIA_TYPE_VIDEO 是枚舉值,代表視頻碼流
            videoindex=i;  // 獲取到視頻碼流的編號
            break;
        }
    if(videoindex==-1){
        printf("Didn't find a video stream.\n");
        return -1;
    }

    pCodecCtx=pFormatCtx->streams[videoindex]->codec;  // 獲取視頻碼流的編解碼器上下文結構體
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);  // 獲取編解碼器
    if(pCodec==NULL){
        printf("Codec not found.\n");
        return -1;
    }
    if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){  // 打開編解碼器
        printf("Could not open codec.\n");
        return -1;
    }
    /*
     * 在此處添加輸出視頻信息的代碼
     * 取自於pFormatCtx,使用fprintf()
     */
    pFrame=av_frame_alloc();  // 分配一個AVFrame內存空間,用於存放解碼後的YUV數據
    pFrameYUV=av_frame_alloc();  // 另外再分配一個AVFrame空間,用於存放sws_scale處理後的數據(剪裁掉無數據部分)
    out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
    avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
    packet=(AVPacket *)av_malloc(sizeof(AVPacket));  // 分配AVPacket內存空間,用於存放解碼前的幀數據
    //Output Info-----------------------------
    printf("--------------- File Information ----------------\n");
    av_dump_format(pFormatCtx,0,filepath,0);
    printf("-------------------------------------------------\n");
    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, 
        pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); 

    // 打開一個用於存放h264數據的文件
    FILE * fp_264 = fopen("test264.h264","wb+");

    // 打開一個用於存放YUV數據的文件
    FILE * fp_yuv = fopen("testyuv.yuv","wb+");

    frame_cnt=0;  // 幀計數
    while(av_read_frame(pFormatCtx, packet)>=0){  // 循環讀取幀數據
        if(packet->stream_index==videoindex){  // 判斷讀取到的幀是否爲視頻幀
            /*
             * 在此處添加輸出H264碼流的代碼
             * 取自於packet,使用fwrite()
             */
            // 將每一幀的未解碼數據(h264數據)寫入文件中,第一個參數是需要寫入文件的數據,第二個參數是每次寫入的字節,第三個參數是寫入次數,第四個參數是文件句柄
            fwrite(packet->data,1,packet->size,fp_264);

            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);  // 使用解碼器進行解碼
            if(ret < 0){
                printf("Decode Error.\n");
                return -1;
            }
            if(got_picture){
                sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, 
                    pFrameYUV->data, pFrameYUV->linesize);   // 對解碼後的YUV數據進行剪裁(直接解碼得到的YUV,可能存在空白部分)
                printf("Decoded frame index: %d\n",frame_cnt);  // 打印當前是第多少幀

                /*
                 * 在此處添加輸出YUV的代碼
                 * 取自於pFrameYUV,使用fwrite()
                 */ 
                fwrite(pFrameYUV->data[0],1,pCodecCtx->width*pCodecCtx->height,fp_yuv);   // 寫入Y數據,大小是寬*高
                fwrite(pFrameYUV->data[1],1,pCodecCtx->width*pCodecCtx->height,fp_yuv);   // 寫入U數據
                fwrite(pFrameYUV->data[2],1,pCodecCtx->width*pCodecCtx->height,fp_yuv);   // 寫入V數據

                frame_cnt++;

            }
        }
        av_free_packet(packet);  // 釋放AVPacket空間?還是清除其中的數據?調用位置可能有問題
    }

    fclose(fp_264);
    fclose(fp_yuv);

    sws_freeContext(img_convert_ctx);  // 釋放sws context對象

    av_frame_free(&pFrameYUV);  // 釋放AVFrame空間
    av_frame_free(&pFrame);  // 釋放AVFrame空間
    avcodec_close(pCodecCtx);  // 關閉解編碼器
    avformat_close_input(&pFormatCtx);  // 關閉AVFormatContext

    return 0;
}

 

 

 

= =!

 

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