下面的MVE是一个简单的测试程序,应该编码6秒(360帧)的视频。当我将导出的视频(output.mkv
)导入到视频编辑器中时,我看到它只有几分之一秒。这是为什么?我如何修复它?
MVE
#include <cstddef>
#include <cstdio>
#include <iostream>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
namespace av {
// ERROR HANDLING
// ==============
class av_category_t final : public std::error_category {
public:
inline const char* name() const noexcept override { return "av_category"; }
inline std::string message(int code) const noexcept override {
thread_local static char msg_buffer[AV_ERROR_MAX_STRING_SIZE];
av_strerror(code, msg_buffer, sizeof(msg_buffer));
return std::string(msg_buffer);
}
};
inline const std::error_category& av_category() {
static av_category_t result;
return result;
}
// helper function to create a std::system_error.
inline std::system_error av_error(int code) {
return std::system_error(code, av_category());
}
// Allocate or reallocate buffers in a video frame.
inline void alloc_video_frame(
AVFrame* frame, int width, int height, AVPixelFormat pix_fmt
) {
int err;
if (frame->width != width || frame->height != height ||
frame->format != pix_fmt || !av_frame_is_writable(frame)) {
// unref the old data if any
if (frame->buf[0] != nullptr) {
av_frame_unref(frame);
}
// reset parameters
frame->width = width;
frame->height = height;
frame->format = pix_fmt;
// reallocate
if ((err = av_frame_get_buffer(frame, 0)) < 0)
throw av_error(err);
}
}
inline void alloc_video_frame(AVFrame* frame, const AVCodecContext* ctx) {
alloc_video_frame(frame, ctx->width, ctx->height, ctx->pix_fmt);
}
} // namespace av
void fill_rgb(AVFrame* tmp, AVFrame* dst, uint32_t col) {
static SwsContext* sws = nullptr;
int err;
av::alloc_video_frame(tmp, dst->width, dst->height, AV_PIX_FMT_0RGB32);
for (int i = 0; i < tmp->height; i++) {
for (int j = 0; j < tmp->width; j++) {
void* p = tmp->data[0] + (i * tmp->linesize[0]) + (j * 4);
*((uint32_t*) p) = col;
}
}
sws = sws_getCachedContext(sws,
// src params
tmp->width, tmp->height, (AVPixelFormat) tmp->format,
// dst params
dst->width, dst->height, (AVPixelFormat) dst->format,
// stuff
0, nullptr, nullptr, nullptr
);
if ((err = sws_scale_frame(sws, dst, tmp)) < 0)
throw av::av_error(err);
}
int32_t hue_c(int deg) {
deg %= 360;
int rem = deg % 60;
switch (deg / 60) {
case 0: return 0xFF0000 | ((deg * 256 / 60) << 8);
case 1: return 0x00FF00 | (((60 - deg) * 256 / 60) << 16);
case 2: return 0x00FF00 | ((deg * 256 / 60) << 0);
case 3: return 0x0000FF | (((60 - deg) * 256 / 60) << 8);
case 4: return 0x00FF00 | ((deg * 256 / 60) << 16);
case 5: return 0xFF0000 | (((60 - deg) * 256 / 60) << 0);
}
return 0xFFFFFF;
}
int main() {
int res;
AVFormatContext* fmt_ctx;
AVStream* vstream;
const AVCodec* vcodec;
AVCodecContext* vcodec_ctx;
AVFrame* vframe;
AVFrame* vframe2;
AVPacket* vpacket;
int64_t pts = 0;
// init frame
res = avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, "output.mkv");
if (res < 0)
return 1;
// get encoder
vcodec = avcodec_find_encoder(AV_CODEC_ID_VP8);
// allocate everything else
vstream = avformat_new_stream(fmt_ctx, vcodec);
vcodec_ctx = avcodec_alloc_context3(vcodec);
vframe = av_frame_alloc();
vframe2 = av_frame_alloc();
vpacket = av_packet_alloc();
if (!vstream || !vcodec_ctx || !vframe || !vpacket) {
return 1;
}
// set PTS counter
pts = 0;
// codec: size parameters
vcodec_ctx->width = 640;
vcodec_ctx->height = 480;
vcodec_ctx->sample_aspect_ratio = {1, 1};
// codec: pixel formats
vcodec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
// codec: bit rate et al.
vcodec_ctx->bit_rate = 2e6;
vcodec_ctx->rc_buffer_size = 4e6;
vcodec_ctx->rc_max_rate = 2e6;
vcodec_ctx->rc_min_rate = 2.5e6;
// codec: frame rate
vcodec_ctx->time_base = {1, 60};
vstream->time_base = {1, 60};
vstream->avg_frame_rate = {60, 1};
// open codec
res = avcodec_open2(vcodec_ctx, vcodec, NULL);
if (res < 0)
throw av::av_error(res);
res = avcodec_parameters_from_context(vstream->codecpar, vcodec_ctx);
if (res < 0)
throw av::av_error(res);
// open file
if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
res = avio_open(&fmt_ctx->pb, "output.mkv", AVIO_FLAG_WRITE);
if (res < 0)
throw av::av_error(res);
}
// write format header
res = avformat_write_header(fmt_ctx, NULL);
if (res < 0)
throw av::av_error(res);
// encode loop
for (int i = 0; i < 360; i++) {
av::alloc_video_frame(vframe, vcodec_ctx);
// gen data and store to vframe
fill_rgb(vframe2, vframe, hue_c(i));
// set timing info
vframe->time_base = vcodec_ctx->time_base;
vframe->pts = i;
// send to encoder
avcodec_send_frame(vcodec_ctx, vframe);
while ((res = avcodec_receive_packet(vcodec_ctx, vpacket)) == 0) {
printf("DTS: %ld, PTS: %ld\n", vpacket->dts, vpacket->pts);
if ((res = av_interleaved_write_frame(fmt_ctx, vpacket)) < 0)
throw av::av_error(res);
}
if (res != AVERROR_EOF && res != AVERROR(EAGAIN))
return -1;
}
if ((res = av_write_trailer(fmt_ctx)) < 0)
throw av::av_error(res);
av_frame_free(&vframe);
av_frame_free(&vframe2);
av_packet_free(&vpacket);
avcodec_free_context(&vcodec_ctx);
avformat_free_context(fmt_ctx);
}
2条答案
按热度按时间hsgswve41#
看起来带有VP8视频流的MKV强制时基为
1/1000
。根据following answer,
1/1000
是WebM多路复用器所必需和强制的,不应该进行更改。(It很难找到那个限制的参考资料-它可能是FFMPEG特定的限制)。
根据定义,MKV容器中的VP8视频编解码器等效于具有VP8视频编解码器的WebM容器。
我假设上述配置使用"WebM muxer",并且具有与WebM容器中的VP9编解码器相同的
1/1000
时基限制。注:
我不知道是否有一种方法可以在上述配置中强制使用不同的时基。
获得60fps的一个简单解决方案是将
pts
设置为i*16
。执行
ffprobe -show_packets output.mkv
始终输出:time_base=1/1000
将
pts
设置为i*16
时,FFprobe报告:Stream #0:0: Video: vp8, yuv420p(progressive), 640x480, 60 fps, 60 tbr, 1k tbn, 1k tbc
MediaInfo工具也报告
Frame rate : 60.000 FPS
。我不知道这是不是最好的解决办法...
vuktfyat2#
我自己的备注:Rotem的答案是正确的。根据FFmpeg的文档(强调我):
encoding:可由调用方在avformat_write_header()之前设置,以向多路复用器提供有关所需时基的提示。在avformat_write_header()中,多路复用器将使用实际用于写入文件的时间戳的时基覆盖此字段(根据格式,可能与用户提供的时间戳相关,也可能不相关)。
简而言之,我确实给FFmpeg设置了一个提示,使用
1/60
作为流时基,但是,MKV格式将时基限制为1/1000
,因此忽略了我的提示。要解决定时问题,需要使用
av_packet_rescale_ts
将时间戳从 codec 的时基转换为 muxer 的时基。