使用FFMPEG解码视频之保存成图片

使用FFMPEG打开视频文件,并解码保存成一张张的图片。

具体的步骤如下所示:

1.初始化FFMPEG 调用了这个才能正常使用编码器和解码器。使用这个函数完成编码器和解码器的初始化,只有初始化了编码器和解码器才能正常使用,否则会在打开编解码器的时候失败。

av_register_all();

2.接着需要分配一个AVFormatContext,FFMPEG所有的操作都要通过这个AVFormatContext来进行 它是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体。AVFormatContext就是对容器或者媒体文件层次的抽象, 容器/文件(Container/File):即特定格式的多媒体文件,比如MP4,flv,mov等

AVFormatContext *pFormatCtx; 
pFormatCtx = avformat_alloc_context();

3.调用avformat_open_input打开视频文件

  • 第一个参数是AVFormatContext结构体的指针,
  • 第二个参数为文件路径;
  • 第三个参数用来设定输入文件的格式,如果设为null,将自动检测文件格式;   
  • 第四个参数用来填充AVFormatContext一些字段以及Demuxer的private选项。    AVFormatContext包含有较多的码流信息参数,通常由avformat_open_input创建并填充关键字段
const char *file_path = "C:/Users/Administrator/Desktop/qt/92/1.mp4";//自己根据路径定义
if (avformat_open_input(&pFormatCtx, file_path, nullptr, nullptr) != 0)
{
    printf("can't open the file.");
    return -1;
}

    4.文件打开成功后就是查找文件中的视频流 avformat_find_stream_info

if (avformat_find_stream_info(pFormatCtx, nullptr) < 0)
{
    printf("Could't find stream infomation.");
    return -1;
}

  循环查找视频中包含的流信息,直到找到视频类型的流。便将其记录下来 保存到videoStream变量中。要解码视频,首先要在AVFormatContext包含的多个流中找到CODEC类型为 AVMEDIA_TYPE_VIDEO。结构体 AVFormatContext 中的 streams 字段是一个 AVStream 指针的数组,包含了文件所有流的描述,下述代码在该数组中查找CODEC类型为AVMEDIA_TYPE_VIDEO的流的下标

videoStream = -1;    
for (i = 0; i < pFormatCtx->nb_streams; i++)
{
    if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
    {
        videoStream = i;
    }
}

    如果videoStream为-1 说明没有找到视频流

if (videoStream == -1)
{
    printf("Didn't find a video stream.");
    return -1;
}

5.在每一路流中都会描述这路流的编码格式,对编解码器格式以及编解码器的抽象就是AVCodecContextAVCodec。根据视频流打开一个解码器来解码;根据 codec_id 找到相应的 CODEC打开。结构体 AVCodecContext 描述了 CODEC 上下文,包含了众多CODEC所需要的参数信息。avcodec_find_decoder 可以通过codec_id或者名称来找到相应的解码器,返回值是一个AVCodec指针。

AVCodecContext *pCodecCtx;   
AVCodec *pCodec;

pCodecCtx = pFormatCtx->streams[videoStream]->codec;//编解码器格式
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);//编解码器的抽象

if (pCodec == nullptr)
{
   printf("Codec not found.");
   return -1;
}

    打开解码器 首先通过codec_id找到相应的CODEC,然后调用avcodec_open2打开相应的CODEC

    if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0)
    {
        printf("Could not open codec.");
        return -1;
    }
av_frame_alloc()分配一个AVFrame并将其字段设置为默认值
pFrame用来存放从AVPacket中解码出来的原始数据,这个AVFrame的数据缓存空间通过调avcodec_decode_video2分配和填充。
pFrameRGB用来存放将 解码出来的原始数据 变换为 需要的数据格式(例如RGB,RGBA)的 数据,这个AVFrame需要手动的分配数据缓存空间。
AVFrame *pFrame, *pFrameRGB;
pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();

首先计算需要缓存空间大小,调用av_malloc分配缓存空间,最后调用avpicture_fill将分配的缓存空间和AVFrame关联起来。

//初始化一个SwsContext 成功后返回SwsContext 类型的结构体
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
            pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
            PIX_FMT_RGB24, SWS_BICUBIC, nullptr, nullptr, nullptr);
//计算给定宽度和高度的图片的字节大小
numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height);
//内存分配
out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
//将out_buffer指向的数据填充到picture内,但并没有拷贝,只是将picture结构内的data指针指向了out_buffer的数据。
avpicture_fill((AVPicture *) pFrameRGB, out_buffer, PIX_FMT_RGB24,
            pCodecCtx->width, pCodecCtx->height);

    6.现在开始读取视频

int y_size = pCodecCtx->width * pCodecCtx->height;

AVPacket *packet;
packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
av_new_packet(packet, y_size); //分配packet的数据 分配数据包的有效负载并用默认值初始化其字段
    int index = 0;

    while (1)
    {
        if (av_read_frame(pFormatCtx, packet) < 0)//从流中读取读取数据到Packet中
        {
            break; //这里认为视频读取完了
        }

        //7.视频里面的数据是经过编码压缩的,因此这里我们需要将其解码:
        if (packet->stream_index == videoStream)
        {
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);
            //对Packet读取的数据进行解码 结果保存在pFrame 
            //如果没有帧可以被解压,得到的图像指针got_picture为零,否则它是非零的。

            if (ret < 0)
            {
                printf("decode error.");
                return -1;
            }
           //8.基本上所有解码器解码之后得到的图像数据都是YUV420的格式,而这里我们需要将其保存成图片文件。
           //因此需要将得到的YUV420数据转换成RGB格式,转换格式也是直接使用FFMPEG来完成:
            if (got_picture)
            {
                sws_scale(img_convert_ctx,
                        (uint8_t const * const *) pFrame->data,
                        pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
                        pFrameRGB->linesize);

                SaveFrame(pFrameRGB, pCodecCtx->width,pCodecCtx->height,index++); //保存图片
                if (index > 50) return 0; //这里我们就保存50张图片
            }
        }
        av_free_packet(packet);
    }

9.得到RGB数据之后就是直接写入文件了,现在我们需要做的是让SaveFrame函数能把RGB信息定稿到一个PPM格式的文件中。我们将生成一个简单的PPM格式文件。

void SaveFrame(AVFrame *pFrame, int width, int height,int index)
{

  FILE *pFile;
  char szFilename[32];
  int  y;

  // Open file
  sprintf(szFilename, "frame%d.ppm", index);//文件名
  pFile=fopen(szFilename, "wb");

  if(pFile==nullptr)
    return;

  // Write header
  fprintf(pFile, "P6 %d %d 255", width, height);

  // Write pixel data
  for(y=0; y<height; y++)
  {
    fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
  }

  // Close file
  fclose(pFile);

}

总结:

调用 avformat_open_input 打开流,将信息填充到 AVFormatContext 中;调用 av_read_frame 从流中读取数据帧到 AVPacket,AVPacket 保存仍然是未解码的数据。调用 avcodec_decode_video2 将 AVPacket 的数据解码,并将解码后的数据填充到 AVFrame 中,AVFrame中保存的是解码后的原始数据。

结构体的存储空间的分配和释放
AVFormatContext 在FFmpeg中有很重要的作用,描述一个多媒体文件的构成及其基本信息,存放了视频编解码过程中的大部分信息。
通常该结构体由avformat_open_input分配存储空间,在最后调用avformat_close_input关闭。

AVStream 描述一个媒体流,在解码的过程中,作为AVFormatContext的一个字段存在,不需要单独的处理
AVpacket 用来存放解码之前的数据,它只是一个容器,其data成员指向实际的数据缓冲区,在解码的过程中可由av_read_frame创建和填充AVPacket中的数据缓冲区,当数据缓冲区不再使用的时候可以调用av_free_apcket释放这块缓冲区

AVFrame 存放从AVPacket中解码出来的原始数据,其必须通过av_frame_alloc来创建,通过av_frame_free来释放。和AVPacket类似,AVFrame中也有一块数据缓存空间,在调用av_frame_alloc的时候并不会为这块缓存区域分配空间,需要使用其他的方法。在解码的过程使用了两个AVFrame,这两个AVFrame分配缓存空间的方法也不相同:一个AVFrame用来存放从AVPacket中解码出来的原始数据,这个AVFrame的数据缓存空间通过调avcodec_decode_video分配和填充。另一个AVFrame用来存放 将解码出来的原始数据 变换为 需要的数据格式(例如RGB,RGBA)的数据,这个AVFrame需要手动的分配数据缓存空间。
完整工程链接​​​​​​​
 
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值