我正在尝试使用FFmpeg将一些视频数据复用到WebM文件中。我通过time_base
(详细如下)指定了AVDictionary
,但似乎多路复用器忽略了我指定的time_base
值。相反,它始终使用time_base
1/1000
,因此使用1000
的FPS。我的初始化代码如下:
HRESULT WINAPI InitializeVideoEncoding(Encoder* encoder,
LPCSTR codec, LPCSTR outputContainer, LPCSTR* options, UINT optCount)
{
// Fill the options
Log("Loading options.");
for (UINT i = 0; i < optCount; ++i)
{
int opt = i * 2;
const char* key = options[opt];
const char* value = options[opt + 1];
Log("Adding option %s: %s", key, value);
if (av_dict_set(&encoder->options, key, value, 0) < 0)
{
Log("Failed to add item to dictionary: %s %s", key, value);
}
}
// Make sure the encoder options aren't null when they should have
// been filled.
if (!encoder->options && optCount > 0)
{
Log("Failed to initialize encoder options.");
return E_FAIL;
}
// Grab the buffer size early and remove it from the dict so we don't
// get complaints from FFmpeg
{
const char* frameBufKey = "frame_buf_size";
encoder->ioBufSize = 131072;
AVDictionaryEntry* e = av_dict_get(encoder->options,
frameBufKey,
NULL, 0);
if (e)
{
// Set the value and remove from the list.
encoder->ioBufSize = strtol(e->value, NULL, 10);
av_dict_set(&encoder->options, frameBufKey, NULL, 0);
}
}
// Create the output context
avformat_alloc_output_context2(&encoder->outputFormatContext, NULL, outputContainer, NULL);
if (!encoder->outputFormatContext)
{
Log("Couldn't create output format context.");
return E_FAIL;
}
encoder->outputFormat = encoder->outputFormatContext->oformat;
// Create the output stream
encoder->outputStream = avformat_new_stream(encoder->outputFormatContext, NULL);
if (!encoder->outputStream)
{
Log("Couldn't create output stream.");
return E_FAIL;
}
encoder->outputStream->id = encoder->outputFormatContext->nb_streams - 1;
// Find the codec
encoder->codec = avcodec_find_encoder_by_name(codec);
if (!encoder->codec)
{
Log("Couldn't find encoder.");
return E_FAIL;
}
// Create the encoding context
encoder->encodingContext = avcodec_alloc_context3(encoder->codec);
if (!encoder->encodingContext)
{
Log("Couldn't create encoding context.");
return E_FAIL;
}
// Set the basics
encoder->encodingContext->width = encoder->width;
encoder->encodingContext->height = encoder->height;
// Open the codec
int result = avcodec_open2(encoder->encodingContext, encoder->codec, &encoder->options);
if (result < 0)
{
LogFFmpegError(result, "Couldn't open codec.");
return E_FAIL;
}
if (av_dict_count(encoder->options) > 0)
{
// Dump the fields we didn't fill
char* dictEntryBuf;
av_dict_get_string(encoder->options, &dictEntryBuf, ':', ',');
Log("The following provided options were unused:\n%s", dictEntryBuf);
av_freep(&dictEntryBuf);
}
// Set some params afterwards
encoder->outputStream->time_base = encoder->encodingContext->time_base;
if (encoder->outputFormat->flags & AVFMT_GLOBALHEADER)
encoder->encodingContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
// Copy necessary information to the stream
result = avcodec_parameters_from_context(encoder->outputStream->codecpar,
encoder->encodingContext);
if (result < 0)
{
LogFFmpegError(result, "Couldn't copy stream parameters.");
return E_FAIL;
}
av_dump_format(encoder->outputFormatContext, 0, NULL, 1);
// Initialize IO callbacks
encoder->ioBuf = (LPBYTE)av_malloc(encoder->ioBufSize);
Log("Encoder IO buffer size: %d", encoder->ioBufSize);
AVIOContext* ioContext = avio_alloc_context(encoder->ioBuf,
(int)encoder->ioBufSize,
1,
encoder,
NULL,
WriteStreamCallback,
NULL);
encoder->outputFormatContext->pb = ioContext;
result = avformat_write_header(encoder->outputFormatContext, NULL);
if (result < 0)
{
LogFFmpegError(result, "Couldn't write header.");
return E_FAIL;
}
return S_OK;
}
您会注意到time_base
未手动指定。相反,我正在使用内置字典功能,因此我可以控制这些参数,而无需重新编译程序。我传入的值如下:
const char* params[] =
{
"frame_buf_size", "131072",
"b", "2000000",
"time_base", "1:15",
"pixel_format", "yuv420p",
"speed", "6",
"tile-columns", "4",
"frame-parallel", "1",
"threads", "8",
"static-thresh", "0",
"deadline", "realtime",
"lag-in-frames", "0",
"error-resilient", "1"
};
我做了一些调查,我的输出流的time_base
一直是1/15
,直到avformat_write_header
被调用。似乎这个函数调用中的某些东西正在改变时基。
现在,我在FFmpeg邮件列表中读到WebM需要time_base
1/1000
,我相信这就是为什么WebM avformat_write_header实现为流重写time_base
的值的原因。我可能会弄错,但帧速率与时基有关,1000 fps的帧速率对于我用来测试它的视频播放器来说太大了(具体来说,是网络浏览器中的媒体扩展播放器) )。
我知道数据包的时间戳很重要,所以下面是我用来为每个数据包提供时间戳的代码:
// somewhere I create a frame
encoder->yuvFrame->pts = encoder->nextPts++;
// somewhere I actually write to the muxer:
av_packet_rescale_ts(packet, encoder->encodingContext->time_base,
encoder->outputStream->time_base);
简而言之,无论我指定的是什么time_base
,多路复用器都会使用1/1000
覆盖它。这是FFmpeg的问题,还是我错过了一些初始化步骤?
答案 0 :(得分:1)
我们并不完全清楚你在问什么,但我会尝试提供一些有用的信息。
支持的时基是您正在使用的格式和/或编解码器的属性。如果格式仅支持1/1000,那就是你必须使用的。
1/1000的时基并不意味着您的视频必须为1000 fps。这意味着您输入的时间戳需要以1/1000秒为单位(即毫秒)。
只需计算15fps帧的毫秒时间戳,然后再将它们输入。这个计算应该很简单:
timestamp = frame_number * 1000 / 15;
libav *中可能有一个函数可以帮你完成这个。
答案 1 :(得分:1)
事实证明,我的应用程序输出的视频文件实际上是有效的。根据R ..的答案,1/1000
数字是WebM复用器所必需和强制执行的,并不是我应该改变的。相反,我必须手动设置AVCodecContext
的帧速率和AVStream
的平均帧速率。这在WebM标题中写下了必要的信息,并允许我的视频在测试播放器中播放。
encoder->outputStream->time_base = encoder->encodingContext->time_base;
// Manually set the frame rate on the codec context
encoder->encodingContext->framerate = av_inv_q(encoder->encodingContext->time_base);
//Manually set the frame rate of the stream
encoder->outputStream->avg_frame_rate = encoder->encodingContext->framerate;