我写了一些使用ffmpeg编码视频的C ++代码。我有两个奇怪的问题:
ffprobe -show_frames -pretty $VIDEO | grep -F '[FRAME]' | wc -l
告诉我的那些。我的代码大致是这样的(修改了一下以删除我们的代码库所特有的类型):
首先,我打开视频文件,编写标题并初始化:
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
帧传递给EncodeAndWriteFrame
行LOG(INFO) << "Writing packet to stream.";
,则运行n
次,表示n
数据包已写入流。但ffprobe
始终只在视频中显示n - 1
帧。最后的视频不会在quicktime上播放。
我做错了什么?
答案 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。