将VP9编码的数据复用到.webm时,FPS不正确

时间:2017-10-04 18:14:28

标签: c ffmpeg

我正在尝试使用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的问题,还是我错过了一些初始化步骤?

2 个答案:

答案 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;