ffmpeg的導入和視頻解碼,YUV保存(ffmpeg4.2)


1.分配一個AVFormatContext,FFMPEG所有的操作都要通過這個AVFormatContext來進行

2.接着調用打開視頻文件

AVFormatContext * pFormatContext = avformat_alloc_context();
int ret = avformat_open_input(&pFormatContext,filepath,NULL,NULL);

3.文件打開成功后就是查找文件中的視頻流了:

  // 第三步:查找視頻流
    // 如果是視頻解碼,那么查找視頻流,如果是音頻解碼,那么就查找音頻流
    // avformat_find_stream_info();
    // AVProgram 視頻的相關信息
    ret = avformat_find_stream_info(pFormatContext, NULL);
    if(ret < 0)
    {
        cout << "find video stream failed "<<endl;
    }
    /*
     * 1.查找視頻流索引位置
     *
    */
    int streamIndex =0,i;
    for(i=0; i< streamIndex; i++)
    {
        // 判斷流的類型
        // 舊的接口 formatContext->streams[i]->codec->codec_type
        // 4.0.0以后新加入的類型用於替代codec
        // codec -> codecpar
        enum AVMediaType mediaType = pFormatContext->streams[i]->codecpar->codec_type;
        if(mediaType == AVMEDIA_TYPE_VIDEO)  //視頻流
        {
            streamIndex =i;
            break;
        }
        else if(mediaType == AVMEDIA_TYPE_AUDIO)
        {
            //音頻流
        }
        else
        {
            //其他流
        }
    }

4.查找視頻解碼器
  /*
     * 2.根據視頻流索引,獲取解碼器上下文
     * 舊的接口,拿到上下文,pFormatContext->streams[i]->codec
     * 4.0.0以后新加入的類型代替codec
     * codec-codecpar 此處的新接口不需要上下文
    */
    AVCodecParameters * avcodecParameters = pFormatContext->streams[streamIndex]->codecpar;
    enum AVCodecID codecId = avcodecParameters->codec_id;

    /*
     * 3.根據解碼器上下文,獲得解碼器ID,然后查找解碼器
     * avvodec_find_encoder(enum AVCodecId id) 編碼器
    */
    AVCodec * codec = avcodec_find_decoder(codecId);
5.打開解碼器
    /*
     * 第五步:打開解碼器
     * avcodec_open2()
     * 舊接口直接使用codec作為上下文傳入
     * pFormatContext->streams[avformat_stream_index]->codec被遺棄
     * 新接口如下
    */
    AVCodecContext * avCodecContext = avcodec_alloc_context3(NULL);
    if(avCodecContext == NULL)
    {
        //創建解碼器上下文失敗
        cout<<"create condectext failed "<<endl;
        return;
    }
    // avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *par)
    // 將新的API中的 codecpar 轉成 AVCodecContext
    avcodec_parameters_to_context(avCodecContext, avcodecParameters);
    ret = avcodec_open2(avCodecContext,codec,NULL);
    if(ret < 0)
    {
        cout << "open decoder failed "<< endl;
        return;
    }
    cout << "decodec name: "<< codec->name<< endl;

6.現在開始讀取視頻了

 /*
     * 第六步:讀取視頻壓縮數據->循環讀取
     * av_read_frame(AVFoematContext *s, AVPacket *packet)
     * s: 封裝格式上下文
     * packet:一幀的壓縮數據
    */
    AVPacket *avPacket = (AVPacket *)av_mallocz(sizeof(AVPacket));
    AVFrame *avFrameIn = av_frame_alloc();  //用於存放解碼之后的像素數據
    // sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param)
    // 原始數據
    // scrW: 原始格式寬度
    // scrH: 原始格式高度
    // scrFormat: 原始數據格式
    // 目標數據
    // dstW: 目標格式寬度
    // dstH: 目標格式高度
    // dstFormat: 目標數據格式
    // 當遇到Assertion desc failed at src/libswscale/swscale_internal.h:668
    // 這個問題就是獲取元數據的高度有問題
    struct SwsContext *swsContext = sws_getContext(avcodecParameters->width,
                                                   avcodecParameters->height,
                                                   avCodecContext->pix_fmt,
                                                   avcodecParameters->width,
                                                   avcodecParameters->height,
                                                   AV_PIX_FMT_YUV420P,
                                                   SWS_BITEXACT, NULL, NULL, NULL);
    //創建緩沖區
    //創建一個YUV420視頻像素數據格式緩沖區(一幀數據)
    AVFrame *pAVFrameYUV420P = av_frame_alloc();
    //給緩沖區設置類型->yuv420類型
     //得到YUV420P緩沖區大小
     // av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align)
     //pix_fmt: 視頻像素數據格式類型->YUV420P格式
     //width: 一幀視頻像素數據寬 = 視頻寬
     //height: 一幀視頻像素數據高 = 視頻高
     //align: 字節對齊方式->默認是1
     int bufferSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                                                avCodecContext->width,
                                                avCodecContext->height,
                                                1);
     cout << "size:"<<bufferSize<<endl;
     //開辟一塊內存空間
     uint8_t *outBuffer = (uint8_t *)av_malloc(bufferSize);
     //向pAVFrameYUV420P->填充數據
     // av_image_fill_arrays(uint8_t **dst_data, int *dst_linesize, const uint8_t *src, enum AVPixelFormat pix_fmt, int width, int height, int align)
     //dst_data: 目標->填充數據(pAVFrameYUV420P)
     //dst_linesize: 目標->每一行大小
     //src: 原始數據
     //pix_fmt: 目標->格式類型
     //width: 寬
     //height: 高
     //align: 字節對齊方式
     av_image_fill_arrays(pAVFrameYUV420P->data,
                          pAVFrameYUV420P->linesize,
                          outBuffer,
                          AV_PIX_FMT_YUV420P,
                          avCodecContext->width,
                          avCodecContext->height,
                          1);
     FILE * fileYUV420P = fopen("out.yuv","wb+");
     int currentIndex=0,ySize,uSize,vSize;

     while(av_read_frame(pFormatContext,avPacket) >=0)
     {
        //判斷是不是視頻
         if(avPacket->stream_index == streamIndex)
         {
             //讀取每一幀數據,立馬解碼一幀數據
             //解碼之后得到視頻的像素數據->YUV
             // avcodec_send_packet(AVCodecContext *avctx, AVPacket *pkt)
             // avctx: 解碼器上下文
             // pkt: 獲取到數據包
             // 獲取一幀數據
             avcodec_send_packet(avCodecContext,avPacket);

             //解碼
             ret = avcodec_receive_frame(avCodecContext,avFrameIn);
             if(ret == 0)  //解碼成功
             {
                //此處無法保證視頻的像素格式一定是YUV格式
                 //將解碼出來的這一幀數據,統一轉類型為YUV
                 // sws_scale(struct SwsContext *c, const uint8_t *const *srcSlice, const int *srcStride, int srcSliceY, int srcSliceH, uint8_t *const *dst, const int *dstStride)
                  // SwsContext *c: 視頻像素格式的上下文
                  // srcSlice: 原始視頻輸入數據
                  // srcStride: 原數據每一行的大小
                  // srcSliceY: 輸入畫面的開始位置,一般從0開始
                  // srcSliceH: 原始數據的長度
                  // dst: 輸出的視頻格式
                  // dstStride: 輸出的畫面大小
                 sws_scale(swsContext,(const uint8_t * const *)avFrameIn->data,
                           avFrameIn->linesize,
                           0,
                           avCodecContext->height,
                           pAVFrameYUV420P->data,
                           pAVFrameYUV420P->linesize);
                 //方式一:直接顯示視頻上面去
                 //方式二:寫入yuv文件格式
                 //5、將yuv420p數據寫入.yuv文件中
                 //5.1 計算YUV大小
                 //分析一下原理?
                 //Y表示:亮度
                 //UV表示:色度
                 //有規律
                 //YUV420P格式規范一:Y結構表示一個像素(一個像素對應一個Y)
                 //YUV420P格式規范二:4個像素點對應一個(U和V: 4Y = U = V)
                 ySize = avCodecContext->width * avCodecContext->height;
                 uSize = ySize/4;
                 vSize = ySize/4;
                 fwrite(pAVFrameYUV420P->data[0],1,ySize,fileYUV420P);
                 fwrite(pAVFrameYUV420P->data[1],1,uSize,fileYUV420P);
                 fwrite(pAVFrameYUV420P->data[2],1,vSize,fileYUV420P);
                 currentIndex++;
                 cout <<"當前解碼 "<<currentIndex<<""<<endl;
             }
         }
     }

7.關閉解碼器

     /*
      * 關閉解碼器
      *
     */
     av_packet_free(&avPacket);
     fclose(fileYUV420P);
     av_frame_free(&avFrameIn);
     av_frame_free(&pAVFrameYUV420P);
     free(outBuffer);
     avcodec_close(avCodecContext);
     avformat_free_context(pFormatContext);

 代碼地址:https://github.com/hgstudy/FFmpegStudy

 













免責聲明!

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



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