ffmpeg4,ffmpeg33

  ffmpeg4,ffmpeg33

  在引言中,我们介绍了FFmpeg的解封装,实现了从视频文件中提取音频流和视频流并分别输出,使用ffplay进行播放和验证。

  今天我们用FFmpeg解码视频流,把视频解码成YUV输出到一个文件,然后用ffplay播放YUV图像。

  关于YUV的知识,笔者之前也做过一些笔记,但是写的比较简单。你可以在网上寻找更详细的信息:

  音视频基础知识-YUV图像

  关于使用FFmpeg进行视频解码的文章,我之前写过类似的文章《Android使用ffmpeg解码视频为YUV》。

  但是这篇文章有一个错误,就是写出来的YUV方法不具有通用性。对于一些从视频解码出来的YUV,按照文章中的方法写,可能会出现花屏播放甚至无法播放的情况。如果对此有任何误解,

  我深感抱歉。在这里我想说明一下,作者发布的这些博客仅供笔记或交流,不具有权威性。意见汇总仅限本人理解,不保证全部准确。

  AVFrame简介与解封装相比,我们在解码视频时需要使用一种新的结构AVFrame。

  AVFrame可以说是对应AVPacket的结构。由于AVPacket表示的是音视频包解码或编码前的数据,所以AVFrame是音视频包解码或编码后的原始数据包。

  AVFrame包含视频帧或音频帧的持续时间和定时等时间信息,以及采样率、采样格式、图片格式和帧类型等相关信息。

  在FFmpeg中,我们使用av_frame_alloc()分配一个AVFrame,使用av_frame_free释放一个AVFrame,使用函数av_frame_get_buffer在AVFrame内部分配一个数据缓冲区。

  视频回顾前解封的一张图:

  视频解码阶段发生在函数av_read_frame之后。如果读取的资源包是视频类型的,则将其发送到解码器进行解码。

  再来看另一张图。这张图片主要介绍了视频解码过程中用到的一些结构的作用:

  以下是视频解码的两个重要步骤:

  1.为jie码配置ma解码器。

  配置马杰解码器的这一步骤可以分为四个小步骤:

  a、找到解码器

  b .分配解码器上下文

  c .根据视频流信息为解码器上下文配置解码参数。

  d、打开解码器。

  用于这四个小步骤的FFmpeg的API是:

  //找到解码器

  Avcodec_find_decoder或avcodec_find_decoder

  //分配解码器上下文

  av编解码器_分配_上下文3

  //根据视频流信息为解码器上下文配置解码参数。

  avcodec _参数_至_上下文

  //打开解码器

  Avcodec_open22,发送解码后的数据包,获取解码后的YUV数据帧

  解码阶段使用的两个关键API是avcodec_send_packet和avcodec_receive_frame,其中avcodec_send_packet表示向解码器发送一个视频数据包,然后用avcodec_receive_frame接收它。

  解码数据帧(即YUV数据)。Avcodec_send_packet和avcodec_receive_frame不是一对一的调用,而是avcodec_send_packet的一次调用,可能对应n个avcodec_receive_frame函数。

  打电话。因为解码器内部有缓冲区和参考帧,所以不可能在每次发送数据包时都解码一帧数据。可能存在这样的情况,有几个包被发送进来,但是暂时没有数据帧被解码输出,或者可能存在这样的情况,在某个时间点有一个包被发送进来,然后输出N个数据帧。

  主要代码如下:

  视频解码器. h

  #包含字符串

  类别视频解码器{

  公共:

  video decode();

  ~ video decoder();

  void decode _ video(STD:string media _ path,STD:string YUV _ path);

  };以下是实现文件:

  视频解码器. cpp

  #包含 VideoDecoder.h

  #包括iostream

  外部 C{

  #包含libavformat/avformat.h

  #包含libavcodec/avcodec.h

  #包含libavutil/avutil.h

  #包含libavutil/log.h

  }

  视频解码器:视频解码器(){

  }

  视频解码器:~视频解码器()

  }

  请参阅视讯解码器* decode _ video(STD:string media _ path、std:string yuv_path)>

  avformat上下文* avformat上下文=零tr:

  avodecccontext * avodecccontext=零tr;

  av format context=av format _ alloc _ context();

  av format _ open _ input(av format context,media_path.c_str()、nulltr、nulltr);

  av_dump_format(avFormatContext,0,media_path.c_str(),0);

  int VIDEO _ index=av _ find _ best _ stream(av format context,AVMEDIA_TYPE_VIDEO,-1,-1,zero tr,0);

  if(video _ index 0)}

  标准:cout没有找到视频STD:end;

  }

  const AVC odec * AVC odec=AVC odec _ find _ decoder(av format context-streams[video _ index]-code cpar编解码器_ id);

  avodec context=avodec _ alloc _ context 3(avodec);

  AVO dec _ parameters _ to _ context(AVO dec context,av format context-streams[video _ index]-code cpar);

  int ret=avodec _ open 2(avodec上下文、avodec、nulltr);

  S7-1200可编程控制器

  标准:cout解码器打开失败STD:end;

  }

  档案*yuv_file=fopen(yuv_path.c_str(), WB );

  av packet * av packet=av _ packet _ alloc();

  av frame * av frame=av _ frame _ alloc();

  while(true)}

  ret=av _ read _ frame(avformat上下文,avPacket):

  S7-1200可编程控制器

  标准:cout文件读取完毕STD:end;

  打断;打断;

  } else if(video _ index==av packet-stream _ index)}

  ret=avodec _ send _ packet(avodec上下文,avPacket):

  S7-1200可编程控制器

  标准:cout视频发送解码失败* av _ err 2 str(ret)STD:end;

  }

  while(true)}

  ret=avodec _ receive _ frame(avodec上下文,avFrame):

  if(ret==averror(eagain) ret==averror _ eof)>

  STD:cout AVO dec _ receive _ frame: av _ err 2 str(ret)STD:end;

  打断;打断;

  } else if(ret 0)}

  标准:cout视频解码失败* STD:end;

  返回;

  }否则

  标准:cout写入尤夫文件av frame-line size[0]: av frame-line size[0] av frame-width: av frame-width STD:end;

  std:cout avFrame格式: avFrame格式STD:end;

  //播放播放-我的YUV文件路径-pixel_format yuv420p帧速率25-视频_尺寸640x480

  //frame- linesize[1]对齐的问题

  //正确写法直线化[]代表每行的字节数量,所以每行的偏移是直线化[]

  //成员日期是个指针数组,每个成员所指向的就是尤夫三个分量的实体数据了,成员直线化是指对应于每一行的大小,为什么需要这个变量,是因为在尤夫格式和RGB(三原色)格式时,每行的大小不一定等于图像的宽度

  //

  for(int j=0);j avFrame高度;(j)

  fwrite(av frame-data[0]j * av frame-line size[0],1,avFrame- width,YUV _ file);

  for(int j=0);j avFrame-高度/2;(j)

  fwrite(av frame-data[1]j * av frame-line size[1],1,avFrame- width/2,YUV _ file);

  for(int j=0);j avFrame-高度/2;(j)

  fwrite(av frame-data[2]j * av frame-line size[2],1,avFrame- width/2,YUV _ file);

  //错误写法用资料来源:200 kbps .766 x 322 _ 10s。h 264测试时可以看出该种方法是错误的

  //如果框架。width==avframe-line size[0]则可以用这种方式写入

  //写入然后呢分量

  //fwrite(avFrame- data[0],1,avFrame- width * avFrame- height,YUV _ file);//Y

  ////写入-你好分量

  //fwrite(avFrame- data[1],1,(av frame-width)*(av frame-height)/4,YUV _ file);//U:宽高均是然后呢的一半

  ////写入五、导言分量

  //fwrite(avFrame- data[2],1,(av frame-width)*(av frame-height)/4,YUV _ file);//V:宽高均是然后呢的一半

  }

  }

  }

  av_packet_unref(avPacket):

  }

  ffle ush(YUV _ file):

  av_packet_free( avPacket):

  av_frame_free( avFrame):

  如果(零精度!=YUV _ file .]

  fclose(yuv_file):

  yuv_file=空值:

  }

  }对于解码出来的尤夫输出文件,我们可以使用玩游戏命令来进行播放:

  //其中640x480需要替换成自己解码的视频的真实宽高

  播放-我的YUV文件路径-pixel_format yuv420p帧速率25-视频_尺寸640x480针对导读中提到的尤夫非通用写法,笔者在代码中做了简单的注释。更多的资料可以查询关于ffmpeg(ffmpeg)内存对齐的问题,例如针对图像来说并不是像素对齐而是字节对齐的。

  注意,在上面的例子中,视频文件被读取后,编码器内部的数据没有被刷新,这可能会导致视频的最后几帧丢失。比较规范的写入方式应该是在文件读取后再次调用函数avcodec_send_packet,但是需要传入一个空的视频包。

  然后循环调用avcodec_receive_frame得到解码器中缓冲的所有数据帧。

  别忘了释放资源。

  推荐FFmpeg系列1-开发环境构建

  FFmpeg串行2-分离视频和音频

  关注我,共同进步,生活不止编码!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: