title: Playback speed of video encoded with IMFSinkWriter changes based on width
body: <p>I'm making a screen recorder (without audio) using <a href="https://docs.microsoft.com/en-us/windows/win32/medfound/tutorial--using-the-sink-writer-to-encode-video" rel="nofollow noreferrer">Win32s Sink Writer</a> to encode a series of bitmaps into an MP4 file.</p>
  
  <p>For some reason, the video playback speed increases (seemingly) proportionally with the video width.</p>
  
  <p>From <a href="https://stackoverflow.com/questions/46711214/playback-duration-from-mf-sinkwriter-mp4-file-is-half-the-time-when-adding-an-au">this post</a>, I've gathered that it's most likely because I'm calculating the buffer size incorrectly. The difference here is that their video playback issue was fixed once the calculation for the audio buffer size was correct, but since I don't encode any audio at all, I'm not sure what to take from it.</p>
  
  <p>I've also tried to read about <a href="https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/nn-mfobjects-imfmediabuffer" rel="nofollow noreferrer">how the buffer works</a>, but I'm really at a loss as to exactly <em>how</em> the buffer size is causing different playback speeds.</p>
  
  <p><a href="https://pastebin.com/SArFyjKE" rel="nofollow noreferrer">Here is a pastebin</a> for the entirity of the code, I really can't track down the problem any more than the buffer size and/or the frame index/duration.</p>
  
  <p><strong>i.e.:</strong>
  Depending on the width of the member variable <code>m_width</code> (measured in pixels), the playback speed changes. That is; the higher the width, the faster the video plays, and vice versa.</p>
  
  <p><strong>Here are two video examples:</strong>
  <a href="https://imgur.com/a/ExMyK2t" rel="nofollow noreferrer">3840x1080</a> and <a href="https://imgur.com/a/MjXgwV9" rel="nofollow noreferrer">640x1080</a>, notice the system clock.
  Imugr does not retain the original resolution of the files, but I double checked before uploading, and the program does indeed create files of the claimed resolutions.</p>
  
  <p>rtStart and rtDuration are defined as such, and are both private members of the MP4File class.</p>
  
  <pre><code>LONGLONG rtStart = 0;
  UINT64   rtDuration;
  MFFrameRateToAverageTimePerFrame(m_FPS, 1, &amp;rtDuration);
  </code></pre>
  
  <p>This is where rtStart is updated, and the individual bits of the bitmap is passed to the frame writer.</p>
  
  <p>Moved the <code>LPVOID</code> object to private members to hopefully increase performance. Now there's no need for heap allocation every time a frame is appended.</p>
  
  <pre><code>HRESULT MP4File::AppendFrame(HBITMAP frame)
  {
      HRESULT hr = NULL;
  
      if (m_isInitialFrame) 
      {
          hr = InitializeMovieCreation();
  
          if (FAILED(hr))
              return hr;
  
          m_isInitialFrame = false;
      }
  
      if (m_hHeap &amp;&amp; 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, &amp;bmpInfo, DIB_RGB_COLORS);
          bmpInfo.bmiHeader.biCompression = BI_RGB;
          GetDIBits(m_hDC, frame, 0, bmpInfo.bmiHeader.biHeight, m_lpBitsBuffer, &amp;bmpInfo, DIB_RGB_COLORS);
  
          hr = WriteFrame();
  
          if (SUCCEEDED(hr))
          {
              rtStart += rtDuration;
          }
      }
  
      return m_writeFrameResult = hr;
  }
  </code></pre>
  
  <p>And lastly, the frame writer which actually loads the bits into the buffer, and then writes to the Sink Writer.</p>
  
  <pre><code>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, &amp;pBuffer);
  
      // Lock the buffer and copy the video frame to the buffer.
      if (SUCCEEDED(hr))
      {
          hr = pBuffer-&gt;Lock(&amp;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-&gt;Unlock();
      }
  
      // Set the data length of the buffer.
      if (SUCCEEDED(hr))
      {
          hr = pBuffer-&gt;SetCurrentLength(cbBufferSize);
      }
  
      // Create a media sample and add the buffer to the sample.
      if (SUCCEEDED(hr))
      {
          hr = MFCreateSample(&amp;pSample);
      }
      if (SUCCEEDED(hr))
      {
          hr = pSample-&gt;AddBuffer(pBuffer);
      }
  
      // Set the time stamp and the duration.
      if (SUCCEEDED(hr))
      {
          hr = pSample-&gt;SetSampleTime(rtStart);
      }
      if (SUCCEEDED(hr))
      {
          hr = pSample-&gt;SetSampleDuration(rtDuration);
      }
  
      // Send the sample to the Sink Writer and update the timestamp
      if (SUCCEEDED(hr))
      {
          hr = m_pSinkWriter-&gt;WriteSample(m_streamIndex, pSample);
      }
  
      SafeRelease(&amp;pSample);
      SafeRelease(&amp;pBuffer);
  
      return hr;
  }
  </code></pre>
  
  <p>A couple details about the encoding:</p>
  
  <ul>
  <li>Framerate: 30FPS</li>
  <li>Bitrate: 15 000 000</li>
  <li>Output encoding format: H264 (MP4)</li>
  </ul>