[PATCH v3] mf/sar: Allow requesting more than a sample per period.

Giovanni Mascellani gmascellani at codeweavers.com
Tue Jun 22 09:16:08 CDT 2021


The IAudioClient implementation from both Windows and winepulse.drv
never sets the event more than once per period, which is usually
around 10 ms long. Some codecs produce audio samples shorter than
10 ms, so it is critical that the SAR is able to process more than
a sample per period.

This is not currently the case: a new sample is requested only in
audio_renderer_render, which is executed (at most) once per period.
This results in the SAR not being able to keep up with the audio
client, and eventually underrunning.

With this patch the SAR keeps a count of how many frames are
currently queued, and a new sample is immediately requested if
the internal queue has less than two periods of frames.

This patch fixes audio stuttering problems in the logo videos
of Borderlands 3, Deep Rock Galactic and Mutant Year Zero.

Signed-off-by: Giovanni Mascellani <gmascellani at codeweavers.com>
---
v2: Remove some changes that didn't really help the solution and
update the description accordingly.
v3: Reimplement from scratches, using a different strategy and
avoiding manually setting the event.

This patch supersedes 208098.

 dlls/mf/sar.c | 26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/dlls/mf/sar.c b/dlls/mf/sar.c
index eba822ae0fe..e39dc09146a 100644
--- a/dlls/mf/sar.c
+++ b/dlls/mf/sar.c
@@ -101,6 +101,8 @@ struct audio_renderer
     HANDLE buffer_ready_event;
     MFWORKITEM_KEY buffer_ready_key;
     unsigned int frame_size;
+    unsigned int queued_frames;
+    unsigned int target_queued_frames;
     struct list queue;
     enum stream_state state;
     unsigned int flags;
@@ -1330,6 +1332,13 @@ static HRESULT WINAPI audio_renderer_stream_GetMediaTypeHandler(IMFStreamSink *i
 static HRESULT stream_queue_sample(struct audio_renderer *renderer, IMFSample *sample)
 {
     struct queued_object *object;
+    DWORD sample_len, sample_frames;
+    HRESULT hr;
+
+    if (FAILED(hr = IMFSample_GetTotalLength(sample, &sample_len)))
+        return hr;
+
+    sample_frames = sample_len / renderer->frame_size;
 
     if (!(object = calloc(1, sizeof(*object))))
         return E_OUTOFMEMORY;
@@ -1339,6 +1348,7 @@ static HRESULT stream_queue_sample(struct audio_renderer *renderer, IMFSample *s
     IMFSample_AddRef(object->u.sample.sample);
 
     list_add_tail(&renderer->queue, &object->entry);
+    renderer->queued_frames += sample_frames;
 
     return S_OK;
 }
@@ -1359,7 +1369,10 @@ static HRESULT WINAPI audio_renderer_stream_ProcessSample(IMFStreamSink *iface,
     EnterCriticalSection(&renderer->cs);
     if (renderer->state == STREAM_STATE_RUNNING)
         hr = stream_queue_sample(renderer, sample);
-    renderer->flags &= ~SAR_SAMPLE_REQUESTED;
+    if (renderer->queued_frames < renderer->target_queued_frames)
+        IMFMediaEventQueue_QueueEventParamVar(renderer->stream_event_queue, MEStreamSinkRequestSample, &GUID_NULL, S_OK, NULL);
+    else
+        renderer->flags &= ~SAR_SAMPLE_REQUESTED;
     LeaveCriticalSection(&renderer->cs);
 
     return hr;
@@ -1516,6 +1529,8 @@ static HRESULT audio_renderer_create_audio_client(struct audio_renderer *rendere
     unsigned int flags;
     WAVEFORMATEX *wfx;
     HRESULT hr;
+    REFERENCE_TIME period;
+    DWORD samples_per_second;
 
     audio_renderer_release_audio_client(renderer);
 
@@ -1543,6 +1558,7 @@ static HRESULT audio_renderer_create_audio_client(struct audio_renderer *rendere
         flags |= AUDCLNT_STREAMFLAGS_NOPERSIST;
     hr = IAudioClient_Initialize(renderer->audio_client, AUDCLNT_SHAREMODE_SHARED, flags, 1000000, 0, wfx,
             &renderer->stream_config.session_id);
+    samples_per_second = wfx->nSamplesPerSec;
     CoTaskMemFree(wfx);
     if (FAILED(hr))
     {
@@ -1576,6 +1592,13 @@ static HRESULT audio_renderer_create_audio_client(struct audio_renderer *rendere
         return hr;
     }
 
+    if (FAILED(hr = IAudioClient_GetDevicePeriod(renderer->audio_client, &period, NULL)))
+    {
+        WARN("Failed to retrieve device period, hr %#x.\n", hr);
+        return hr;
+    }
+    renderer->target_queued_frames = 2 * period * samples_per_second / 10000000;
+
     if (SUCCEEDED(hr = MFCreateAsyncResult(NULL, &renderer->render_callback, NULL, &result)))
     {
         if (FAILED(hr = MFPutWaitingWorkItem(renderer->buffer_ready_event, 0, result, &renderer->buffer_ready_key)))
@@ -1789,6 +1812,7 @@ static void audio_renderer_render(struct audio_renderer *renderer, IMFAsyncResul
                                     IAudioRenderClient_ReleaseBuffer(renderer->audio_render_client, dst_frames, 0);
 
                                     obj->u.sample.frame_offset += dst_frames;
+                                    renderer->queued_frames -= dst_frames;
                                 }
 
                                 keep_sample = FAILED(hr) || src_frames > max_frames;
-- 
2.32.0




More information about the wine-devel mailing list