C ++ ffmpeg视频缺少帧并且不能在Quicktime中播放

时间:2018-04-22 00:15:16

标签: c++ ffmpeg

我写了一些使用ffmpeg编码视频的C ++代码。我有两个奇怪的问题:

  1. 最终视频总是丢失1帧。也就是说,如果我编码10帧,则最终视频只有9帧(至少是ffprobe -show_frames -pretty $VIDEO | grep -F '[FRAME]' | wc -l告诉我的那些。
  2. 最终视频在某些播放器(mpv和vlc)中播放正常但在Quicktime中播放不正常。 Quicktime只显示一个完全黑屏。
  3. 我的代码大致是这样的(修改了一下以删除我们的代码库所特有的类型):

    首先,我打开视频文件,编写标题并初始化:

    template <class PtrT>
    using UniquePtrWithDeleteFunction = std::unique_ptr<PtrT, std::function<void (PtrT*)>>;
    
    
    std::unique_ptr<FfmpegEncodingFrameSink> FfmpegEncodingFrameSink::Create(
        const std::string& dest_url) {
      AVFormatContext* tmp_format_ctxt;
      auto alloc_format_res = avformat_alloc_output_context2(&tmp_format_ctxt, nullptr, "mp4", dest_url.c_str());
      if (alloc_format_res < 0) {
        throw FfmpegException("Error opening output file.");
      }
      auto format_ctxt = UniquePtrWithDeleteFunction<AVFormatContext>(
          tmp_format_ctxt, CloseAvFormatContext);
    
      AVStream* out_stream_video = avformat_new_stream(format_ctxt.get(), nullptr);
      if (out_stream_video == nullptr) {
        throw FfmpegException("Could not create outputstream");
      }
    
      auto codec_context = GetCodecContext(options);
      out_stream_video->time_base = codec_context->time_base;
    
      auto ret = avcodec_parameters_from_context(out_stream_video->codecpar, codec_context.get());
      if (ret < 0) {
        throw FfmpegException("Failed to copy encoder parameters to outputstream");
      }
    
      if (!(format_ctxt->oformat->flags & AVFMT_NOFILE)) {
        ret = avio_open(&format_ctxt->pb, dest_url.c_str(), AVIO_FLAG_WRITE);
        if (ret < 0) {
          throw VideoDecodeException("Could not open output file: " + dest_url);
        }
      }
    
      ret = avformat_init_output(format_ctxt.get(), nullptr);
      if (ret < 0) {
        throw FfmpegException("Unable to initialize the codec.");
      }
    
      ret = avformat_write_header(format_ctxt.get(), nullptr);
      if (ret < 0) {
        throw FfmpegException("Error occurred writing format header");
      }
    
      return std::unique_ptr<FfmpegEncodingFrameSink>(
          new FfmpegEncodingFrameSink(std::move(format_ctxt), std::move(codec_context)));
    }
    

    然后,每次我得到一个新的帧进行编码我都会将它传递给这个函数(这些帧是通过ffmpeg从另一个mp4文件解码的,Quicktime播放得很好):

    // If frame == nullptr then we're done and we're just flushing the encoder
    // otherwise encode an actual frame
    void FfmpegEncodingFrameSink::EncodeAndWriteFrame(
        const AVFrame* frame) {
      auto ret = avcodec_send_frame(codec_ctxt_.get(), frame);
      if (ret < 0) {
        throw FfmpegException("Error encoding the frame.");
      }
    
      AVPacket enc_packet;
      enc_packet.data = nullptr;
      enc_packet.size = 0;
      av_init_packet(&enc_packet);
    
      do {
        ret = avcodec_receive_packet(codec_ctxt_.get(), &enc_packet);
        if (ret ==  AVERROR(EAGAIN)) {
          CHECK(frame != nullptr);
          break;
        } else if (ret ==  AVERROR_EOF) {
          CHECK(frame == nullptr);
          break;
        } else if (ret < 0) {
          throw FfmpegException("Error putting the encoded frame into the packet.");
        }
    
        assert(ret == 0);
        enc_packet.stream_index = 0;
    
        LOG(INFO) << "Writing packet to stream.";
        av_interleaved_write_frame(format_ctxt_.get(), &enc_packet);
        av_packet_unref(&enc_packet);
      } while (ret == 0);
    }
    

    最后,在我的析构函数中,我将所有内容都关闭起来:

    FfmpegEncodingFrameSink::~FfmpegEncodingFrameSink() {
      // Pass a nullptr to EncodeAndWriteFrame so it flushes the encoder
      EncodeAndWriteFrame(nullptr);
      // write mp4 trailer
      av_write_trailer(format_ctxt_.get());
    }
    

    如果我将n帧传递给EncodeAndWriteFrameLOG(INFO) << "Writing packet to stream.";,则运行n次,表示n数据包已写入流。但ffprobe始终只在视频中显示n - 1帧。最后的视频不会在quicktime上播放。

    我做错了什么?

1 个答案:

答案 0 :(得分:0)

很抱歉延迟,但是由于我只是遇到了同样的问题,并注意到该问题值得回答,在这里我如何解决了这个问题。 在前面,问题仅在使用mov,mp4、3gp作为格式时才出现。当使用例如avi格式。当我将未压缩的视频帧写入容器时,我发现avi和mov存储的帧数相同,但是mov的标头显然存在一些问题。

使用标头元数据计算mov中的帧数表明缺少一帧:

ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 c:\temp\myinput.mov

忽略索引显示正确的帧数:

-ignore_editlist 1

对我来说,解决方案是将时基设置为视频流的AVStream-> CodeContext

上面的代码尝试在此行中执行此操作:

out_stream_video->time_base = codec_context->time_base;

但是问题是上面发布的代码没有公开函数GetCodecContext,因此我们不知道time_base是否为“ codec_context”正确设置。因此,我相信作者的问题在于他的函数GetCodecContext没有正确设置time_base。