简体   繁体   English

C++ ffmpeg 视频丢失帧并且无法在 Quicktime 中播放

[英]C++ ffmpeg video missing frames and won't play in Quicktime

I wrote some C++ code that uses ffmpeg to encode a video.我编写了一些使用 ffmpeg 对视频进行编码的 C++ 代码。 I'm having two strange issues:我有两个奇怪的问题:

  1. The final video is always missing 1 frame.最终的视频总是缺少 1 帧。 That is, if I have it encode 10 frames the final video only has 9 (at least that's what ffprobe -show_frames -pretty $VIDEO | grep -F '[FRAME]' | wc -l tells me.也就是说,如果我让它编码 10 帧,最终视频只有 9(至少ffprobe -show_frames -pretty $VIDEO | grep -F '[FRAME]' | wc -l告诉我。
  2. The final video plays fine in some players (mpv and vlc) but not in Quicktime.最终视频在某些播放器(mpv 和 vlc)中播放良好,但在 Quicktime 中则不然。 Quicktime just shows a completely black screen. Quicktime 只显示一个完全黑屏。

My code is roughly this (modified a bit to remove types that are unique to our code base):我的代码大致是这样(稍微修改以删除我们代码库独有的类型):

First, I open the video file, write the headers and initialize things:首先,我打开视频文件,编写标题并初始化:

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)));
}

Then, every time I get a new frame to encode I pass it to this function (the frames are being decoded via ffmpeg from another mp4 file which Quicktime plays just fine):然后,每次我得到一个新的帧进行编码时,我都会将它传递给这个函数(帧是通过 ffmpeg 从另一个 Quicktime 播放的 mp4 文件中解码的):

// 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);
}

Finally, in my destructor I close everything up like so:最后,在我的析构函数中,我像这样关闭所有内容:

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

If I run this passing n frames to EncodeAndWriteFrame line LOG(INFO) << "Writing packet to stream.";如果我运行这个将n帧传递给EncodeAndWriteFrameLOG(INFO) << "Writing packet to stream."; gets run n times indicating the n packets were written to the stream. get 运行n次,表明n数据包已写入流。 But ffprobe always shows only n - 1 frames int he video.但是ffprobe在视频中始终只显示n - 1帧。 And the final video doesn't play on quicktime.最后的视频不能在 quicktime 上播放。

What am I doing wrong??我做错了什么??

Sorry for the delay but as i just had the same problem and noticed that this question deserves an answer, here how i solved this.抱歉耽搁了,但因为我刚刚遇到了同样的问题,并注意到这个问题值得回答,这里我是如何解决这个问题的。 Up in front, the Problem only occured for me when using mov, mp4, 3gp as format.在前面,问题只发生在我使用 mov、mp4、3gp 作为格式时。 It worked frame accurate when using eg avi format.使用例如 avi 格式时,它的工作帧准确。 When i wrote uncompressed video frames to the container, i saw that the avi and mov had the same count of frames stored but the mov obviously had some problem in it's header.当我将未压缩的视频帧写入容器时,我看到 avi 和 mov 存储的帧数相同,但 mov 的标题显然存在一些问题。

Counting the number of frames in the mov using header metadata showed one frame is missing:使用标头元数据计算 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

While ignoring the index showed the correct number of frames:忽略索引显示正确的帧数:

-ignore_editlist 1

The solution for me was, set the timebase to the AVStream->CodeContext of the video stream .我的解决方案是,将时基设置为视频流的 AVStream->CodeContext

The code above attempts to do this in this line:上面的代码试图在这一行中做到这一点:

out_stream_video->time_base = codec_context->time_base;

But the problem is that the posted code above does not expose the function GetCodecContext so we do not know if the time_base is correctly set for "codec_context".但问题是上面发布的代码没有公开 GetCodecContext 函数,所以我们不知道 time_base 是否为“codec_context”正确设置。 So it is my believe that the author's problem was that his function GetCodecContext did not set the time_base correctly.所以我认为作者的问题是他的函数 GetCodecContext 没有正确设置 time_base。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM