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

 












posted @ 2019-08-08 11:12  hu9977  阅读(12172)  评论(0编辑  收藏  举报