一、創建工程
在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; }
= =!