1. 程式人生 > >雷神FFMpeg原始碼學習筆記

雷神FFMpeg原始碼學習筆記

雷神FFMpeg原始碼學習筆記

文章目錄

讀取編碼並依據編碼初始化內容結構

  1. 在開始編解碼視訊的時候首先第一步需要註冊一個編解碼器 :av_register_all();
  2. avformat_open_input來開啟這個檔案並給AVformartcontext賦值 ,在其中會去查詢當前快取檔案的格式 avformat_open_input 來開啟這個檔案並給AVformartcontext
    賦值 ,在其中會去查詢當前快取檔案的格式
  3. avformat_find_stream_info使用該方法給每個視訊/音訊流的AVStream 結構體進行賦值並得到,這個引數在裡面實現了一定的解碼過程
  4. AVstream 有值了以後我們需要拿到當前當前的avcodecContext和其對應的AVcodeC(使用avcodec_find_decoder)
  5. avcodec_open2,初始化一個視音訊編解碼器的AVCodecContext,,呼叫AVCodeC的初始化到具體的解碼器 AVCodeC init() 所以是在avcodec_open2在開始真正的初始化avcodecContext
  6. 在得到了初始化的AVcodecContext之後我們就可以開始為解碼之後的AVframe分配空間(使用(unsigned char *)av_mallocz(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecContex->width, pCodecContex->height, 1))) av_image_fill_arrays():為AVframe 的畫素點分配空間
    sws_getContext:使用源影象的高和寬得到目標影象的高和寬,flag 為設定影象拉伸的演算法

每一幀的視訊解碼處理

  1. av_read_frame
    在解碼之前來獲取一幀的視訊幀壓縮資料 或者是多幀的音訊幀壓縮資料 及我們得到的只是AVpacket
  2. avcodec_decode_video2使用該函式來解碼得到的AVpacket,輸出一個下一幀的AVframe 函式
  3. 使用sws_scale來對下一幀的AVframe 進行拉伸變化 ,輸出想要得到的AVframe
  4. 釋放上述的AVformartcontextAVstreamavcodecContext.

下面是程式碼的具體處理操作:

1.首先需要註冊所有的編解碼器
//1.註冊所有的編解碼器等等
   av_register_all();
//在需要網路的情況下初始化
   avformat_network_init();
//2.初始化AVformartcontext,AVformartcontext 是
   pContext=avformat_alloc_context();
//3.得到視訊流的URL 地址
   
//4.嘗試開啟檔案流,在AVformartcontext 中查詢當前的AVInputContext的格式
   if (avformat_open_input(&pContext, input_str_full, NULL, NULL)!=0) {
       NSLog(@"開啟檔案流失敗");
       return;
   }

avformat_open_input:內部實現就是去開啟當前檔案並將其賦值給AVformartcontext ,
然後查詢到當前AVformartcontext的AVinputcontext的格式,如:flv 等

//5.在AVStream 解碼一段視音流資訊 ,
   if (avformat_find_stream_info(pContext, NULL)<0){
       NSLog(@"解碼得到AVstream流資訊失敗");
       return;
   }
avformat_find_stream_info:該函式主要是給每個視訊/音訊流的AVStream 結構體進行賦值
其實在他內部本身實現瞭解碼的整個流程:查詢解碼器->開啟解碼器->讀取完整的一幀壓縮編碼資料->解碼壓縮編碼資料得到資訊
//6.拿到當前avformatcontext ->AVstream 的AVCodecContext 視訊流
   int videoIndex=-1;
   for (int i=0; i<pContext->nb_streams; i++) {
       if (pContext->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
           videoIndex=i;
       }  
   }
   if (videoIndex==-1) {
       NSLog(@"當前的資料是錯誤的");return;
   }
   //拿到視訊流的上下文
   AVCodecContext *pCodecContex=pContext->streams[videoIndex]->codec;

//7.通過avcodec_find——decoder 去找尋當前id 下的AVcodeC 解碼器
   AVCodec *pCodec=avcodec_find_decoder(pCodecContex->codec_id);
   if (pCodec==NULL) {
       NSLog(@"找不到AVCodeC");
       return;
   }
 //8.初始化一個視音訊編解碼器的AVCodecContext,,呼叫AVCodeC的初始化到具體的解碼器 AVCodeC init() 所以是在
   avcodec_open2在開始真正的初始化avcodecContext
   if (avcodec_open2(pCodecContex, pCodec, NULL)!=0 ) {
       NSLog(@"開啟codeC錯誤");
       return;
   }
 avcodec_open2:引數為初始化一個codecContext 目標解碼器  AVdictionary
   //建立一個AVframe 就是用來預測下一幀的視訊幀,使用avcodec_decode_video2 預測下一幀
    AVFrame *pFrame;
    pFrame=av_frame_alloc();
    AVFrame *pFrameYUV;
    pFrameYUV=av_frame_alloc();
    uint8_t *outBuffer;

   //9 pFrameYUV分配空間,該函式並沒有為AVFrame的畫素資料分配空間,需要使用av_image_fill_arrays分配
   outBuffer=(unsigned char *)av_mallocz(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecContex->width,   
   pCodecContex->height, 1));

   //給目標的AVframe 來分配空間
   av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuffer,  AV_PIX_FMT_YUV420P, pCodecContex->width,             
   pCodecContex->height, 1);
    //10.給AVFrame 分配完了地址空間並給 畫素點新增進去之後 需要是用libswscale 包&來進行影象的格式轉換和影象的拉伸
   AVPacket *packet=(AVPacket *)av_malloc(sizeof(AVPacket));
   struct SwsContext *img_conver_ctx;


   /**
* Allocate and return an SwsContext. You need it to perform
* scaling/conversion operations using sws_scale().
*
* @param srcW the width of the source image
* @param srcH the height of the source image
* @param srcFormat the source image format
* @param dstW the width of the destination image
* @param dstH the height of the destination image
* @param dstFormat the destination image format
* @param flags specify which algorithm and options to use for rescaling
* @return a pointer to an allocated context, or NULL in case of error
* @note this function is to be removed after a saner alternative is
*       written
*/
   img_conver_ctx= sws_getContext(pCodecContex->width, pCodecContex->height, pCodecContex->pix_fmt, pCodecContex->width, pCodecContex->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

sws_getContext:從sws_getContext()的定義中可以看出,它首先呼叫了一個函式sws_alloc_context()用於給SwsContext分配記憶體。然後將傳入的源影象,目標影象的寬高,畫素格式,以及標誌位分別賦值給該SwsContext相應的欄位。最後呼叫一個函式sws_init_context()完成初始化工作

   int frame_cnt;
   //用來計算處理事件用了多久的時間
   clock_t time_start ,timeFinsh;
   time_start=clock();
   int y_size;
   int got_picture, ret;
   
   FILE *fp_yuv;
   fp_yuv=fopen(output_str_full, "wb+");
   if (fp_yuv==NULL) {
       NSLog(@"不能開啟該地址下的檔案");
       return;
   }
   //11.解碼之前都要先用 av_read_frame()獲得一幀視訊的壓縮資料,或者是若干幀的音訊資料,
   while (av_read_frame(pContext, packet)>=0) {
       if (packet->stream_index==videoIndex) {
           //avcodec_decode_video2()的作用是解碼一幀視訊資料。輸入一個壓縮編碼的結構體AVPacket,輸出一個解碼後的結構體AVFrame
           ret=avcodec_decode_video2(pCodecContex, pFrame, &got_picture, packet);
           if (ret<0) {
               NSLog(@"解碼下一幀失敗");
               return;
           }
       if (got_picture) {
           //對影象進行拉伸等處理,這裡是不進行拉伸變換
           sws_scale(img_conver_ctx, (const uint8_t* const* )pFrame->data, pFrame->linesize, 0, pCodecContex->height,                        pFrameYUV->data, pFrameYUV->linesize);
           y_size=pCodecContex->width*pCodecContex->height;
           fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);
           fwrite(pFrameYUV->data[1], 1, y_size/4, fp_yuv);
           fwrite(pFrameYUV->data[2], 1, y_size/4, fp_yuv);
           char picture_type_str[10]={0};
           switch (pFrame->pict_type) {
               case AV_PICTURE_TYPE_I:sprintf(picture_type_str,"I");break;
               case AV_PICTURE_TYPE_P:sprintf(picture_type_str,"P");break;
               case AV_PICTURE_TYPE_B:sprintf(picture_type_str,"B");break;
               default:sprintf(picture_type_str,"Other");break;
           }
           NSLog(@"frame index:%.5d type:%s",frame_cnt,picture_type_str);
           frame_cnt++;
       }
       }
       av_free_packet(packet);
   }
while (1) {
       ret=avcodec_decode_video2(pCodecContex, pFrame, &got_picture, packet);
       if (ret<0) {
           return;
       }
       if (!got_picture) {
           break;
       }
       sws_scale(img_conver_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecContex->height,          pFrameYUV->data, pFrameYUV->linesize);
       int y_size=pCodecContex->width*pCodecContex->height;
       fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);    //Y
       fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);  //U
       fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);  //V
       //Output info
       char pictype_str[10]={0};
       switch(pFrame->pict_type){
           case AV_PICTURE_TYPE_I:sprintf(pictype_str,"I");break;
           case AV_PICTURE_TYPE_P:sprintf(pictype_str,"P");break;
           case AV_PICTURE_TYPE_B:sprintf(pictype_str,"B");break;
           default:sprintf(pictype_str,"Other");break;
       }
       printf("Frame Index: %5d. Type:%s\n",frame_cnt,pictype_str);
       frame_cnt++;
   
   }
   timeFinsh=clock();
   double time_duration=timeFinsh-time_start;
   NSLog(@"這個過程持續的時間是%f",time_duration);
   sws_freeContext(img_conver_ctx);
   fclose(fp_yuv);
   av_frame_free(&pFrameYUV);
   av_frame_free(&pFrame);
   avcodec_close(pCodecContex);
   avformat_close_input(&pContext);