Скорость воспроизведения видео, закодированного с помощью IMFSinkWriter, изменяется в зависимости от ширины - PullRequest
0 голосов
/ 19 января 2020

Я делаю устройство записи экрана (без звука), используя Win32s Sink Writer для кодирования серии растровых изображений в файл MP4.

По какой-то причине скорость воспроизведения видео увеличивается (по-видимому) пропорционально ширине видео.

Из этого поста я понял, что, скорее всего, потому, что я неправильно вычисляю размер буфера. Разница здесь в том, что проблема с воспроизведением видео была исправлена ​​после правильного расчета размера аудио буфера, но, поскольку я вообще не кодирую звук, я не уверен, что из него извлечь.

Я также пытался прочитать , как работает буфер , но я действительно в растерянности относительно того, как размер буфера вызывает разные скорости воспроизведения.

Вот пастбина для всего кода, я действительно не могу отследить проблему больше, чем размер буфера и / или индекс / продолжительность кадра.

т.е.: В зависимости от ширины переменной-члена m_width (измеряется в пикселях), скорость воспроизведения изменяется. Это; чем выше ширина, тем быстрее воспроизводится видео, и наоборот.

Вот два примера видео: 3840x1080 и 640x1080 , обратите внимание системные часы. Imugr не сохраняет исходное разрешение файлов, но перед загрузкой я дважды проверил, и программа действительно создает файлы заявленных разрешений.

rtStart и rtDuration определены как таковые и являются частными членами класс MP4File.

LONGLONG rtStart = 0;
UINT64   rtDuration;
MFFrameRateToAverageTimePerFrame(m_FPS, 1, &rtDuration);

Здесь обновляется rtStart, а отдельные биты растрового изображения передаются в программу записи кадров.

Перемещал объект LPVOID в закрытые члены для надеюсь увеличить производительность. Теперь нет необходимости в выделении кучи каждый раз, когда добавляется кадр.

HRESULT MP4File::AppendFrame(HBITMAP frame)
{
    HRESULT hr = NULL;

    if (m_isInitialFrame) 
    {
        hr = InitializeMovieCreation();

        if (FAILED(hr))
            return hr;

        m_isInitialFrame = false;
    }

    if (m_hHeap && m_lpBitsBuffer) // Makes sure buffer is initialized
    {
        BITMAPINFO bmpInfo;
        bmpInfo.bmiHeader.biBitCount = 0;
        bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

        // Get individual bits from bitmap and loads it into the buffer used by `WriteFrame`
        GetDIBits(m_hDC, frame, 0, 0, NULL, &bmpInfo, DIB_RGB_COLORS);
        bmpInfo.bmiHeader.biCompression = BI_RGB;
        GetDIBits(m_hDC, frame, 0, bmpInfo.bmiHeader.biHeight, m_lpBitsBuffer, &bmpInfo, DIB_RGB_COLORS);

        hr = WriteFrame();

        if (SUCCEEDED(hr))
        {
            rtStart += rtDuration;
        }
    }

    return m_writeFrameResult = hr;
}

И, наконец, средство записи кадров, которое фактически загружает биты в буфер, а затем записывает данные в Sink Writer.

HRESULT MP4File::WriteFrame()
{
    IMFSample *pSample = NULL;
    IMFMediaBuffer *pBuffer = NULL;

    const LONG cbWidth = 4 * m_width;
    const DWORD cbBufferSize = cbWidth * m_height;

    BYTE *pData = NULL;

    // Create a new memory buffer.
    HRESULT hr = MFCreateMemoryBuffer(cbBufferSize, &pBuffer);

    // Lock the buffer and copy the video frame to the buffer.
    if (SUCCEEDED(hr))
    {
        hr = pBuffer->Lock(&pData, NULL, NULL);
    }
    if (SUCCEEDED(hr))
    {
        hr = MFCopyImage(
            pData,                      // Destination buffer.
            cbWidth,                    // Destination stride.
            (BYTE*)m_lpBitsBuffer,      // First row in source image.
            cbWidth,                    // Source stride.
            cbWidth,                    // Image width in bytes.
            m_height                    // Image height in pixels.
        );
    }
    if (pBuffer)
    {
        pBuffer->Unlock();
    }

    // Set the data length of the buffer.
    if (SUCCEEDED(hr))
    {
        hr = pBuffer->SetCurrentLength(cbBufferSize);
    }

    // Create a media sample and add the buffer to the sample.
    if (SUCCEEDED(hr))
    {
        hr = MFCreateSample(&pSample);
    }
    if (SUCCEEDED(hr))
    {
        hr = pSample->AddBuffer(pBuffer);
    }

    // Set the time stamp and the duration.
    if (SUCCEEDED(hr))
    {
        hr = pSample->SetSampleTime(rtStart);
    }
    if (SUCCEEDED(hr))
    {
        hr = pSample->SetSampleDuration(rtDuration);
    }

    // Send the sample to the Sink Writer and update the timestamp
    if (SUCCEEDED(hr))
    {
        hr = m_pSinkWriter->WriteSample(m_streamIndex, pSample);
    }

    SafeRelease(&pSample);
    SafeRelease(&pBuffer);

    return hr;
}

Несколько подробностей о кодировке:

  • Частота кадров: 30FPS
  • Битрейт: 15 000 000
  • Формат выходной кодировки: H264 (MP4)

1 Ответ

1 голос
/ 21 января 2020

Для меня такое поведение имеет смысл.

См. https://github.com/mofo7777/Stackoverflow/tree/master/ScreenCaptureEncode

Моя программа использует DirectX9 вместо GetDIBits, но поведение такое же. Попробуйте эту программу с разными разрешениями экрана, чтобы подтвердить это поведение.

И я подтверждаю, что с моей программой скорость воспроизведения видео увеличивается пропорционально ширине видео (а также высоте видео).

Почему?

Больше данных для копирования, больше времени для передачи. И неправильное время выборки / длительность выборки.

Использование 30 кадров в секунду означает один кадр каждые 33,3333333 мс:

  • Do GetDIBits, MFCopyImage, WriteSample заканчиваются точно в 33,3333333 мс ... нет.
  • Вы пишете каждый кадр точно в 33,3333333 мс ... нет.

Так что просто делать rtStart + = rtDuration неправильно, потому что вы не захватываете и записываете экран точно в в этот раз. И GetDIBits / DirectX9 не могут обрабатывать на 30 FPS, поверьте мне. И почему Microsoft предоставила Windows Desktop Duplication (только для windows 8/10)?

Ключ - задержка.

Знаете ли вы, сколько времени занимают GetDIBits, MFCopyImage и WriteSample? Вы должны знать, чтобы понять проблему. Обычно это занимает более 33,3333333 мс. Но это переменная. Вы должны знать это, чтобы настроить правильный FPS для кодера. Но вам также нужно будет написать WriteSample в нужное время.

Если вы используете MF_MT_FRAME_RATE с 5-10 FPS вместо 30 FPS, вы увидите, что это более реалистично c, но не оптимально.

Например, используйте IMFPresentationClock для обработки правильного времени WriteSample.

...