[PATCH] mmdevapi: Implement SpatialAudio features

Andrew Eikum aeikum at codeweavers.com
Mon Feb 1 11:23:07 CST 2021


On Mon, Feb 01, 2021 at 04:56:38PM +0200, Arkadiusz Hiler wrote:
> From: Andrew Eikum <aeikum at codeweavers.com>
> 

I'm fine with how you've done it, but if you'd like Git authorship for
the tests, you could send that patch separately.


In spatialaudio.c:

> +static HRESULT activate_stream(SpatialAudioStreamImpl *stream)
> +{
> +    WAVEFORMATEXTENSIBLE *object_fmtex = (WAVEFORMATEXTENSIBLE *)stream->params.ObjectFormat;
> +    HRESULT hr;
> +    REFERENCE_TIME period;
> +
> +    if(!(object_fmtex->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT ||
> +                (object_fmtex->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
> +                 IsEqualGUID(&object_fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)))){
> +        FIXME("Only float formats are supported for now\n");
> +        return E_INVALIDARG;
> +    }
> +
> +    hr = IMMDevice_Activate(stream->sa_client->mmdev, &IID_IAudioClient,
> +            CLSCTX_INPROC_SERVER, NULL, (void**)&stream->client);
> +    if(FAILED(hr)){
> +        WARN("Activate failed: %08x\n", hr);
> +        return hr;
> +    }
> +
> +    hr = IAudioClient_GetDevicePeriod(stream->client, &period, NULL);
> +    if(FAILED(hr)){
> +        WARN("GetDevicePeriod failed: %08x\n", hr);
> +        IAudioClient_Release(stream->client);
> +        return hr;
> +    }
> +
> +    stream->stream_fmtex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
> +    static_mask_to_channels(stream->params.StaticObjectTypeMask,
> +            &stream->stream_fmtex.Format.nChannels, &stream->stream_fmtex.dwChannelMask,
> +            stream->static_object_map);
> +    stream->stream_fmtex.Format.nSamplesPerSec = stream->params.ObjectFormat->nSamplesPerSec;
> +    stream->stream_fmtex.Format.wBitsPerSample = stream->params.ObjectFormat->wBitsPerSample;
> +    stream->stream_fmtex.Format.nBlockAlign = (stream->stream_fmtex.Format.nChannels * stream->stream_fmtex.Format.wBitsPerSample) / 8;
> +    stream->stream_fmtex.Format.nAvgBytesPerSec = stream->stream_fmtex.Format.nSamplesPerSec * stream->stream_fmtex.Format.nBlockAlign;
> +    stream->stream_fmtex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
> +    stream->stream_fmtex.Samples.wValidBitsPerSample = stream->stream_fmtex.Format.wBitsPerSample;
> +    stream->stream_fmtex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
> +
> +    hr = IAudioClient_Initialize(stream->client, AUDCLNT_SHAREMODE_SHARED,
> +            AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,
> +            period * MAX_PERIODS, 0, &stream->stream_fmtex.Format, NULL);
> +    if(FAILED(hr)){
> +        WARN("Initialize failed: %08x\n", hr);
> +        IAudioClient_Release(stream->client);
> +        return hr;
> +    }
> +
> +    /* XXX: OK that this is the user's handle? */

We can remove this comment, we are cloning the handle below.

> +static HRESULT WINAPI SAC_ActivateSpatialAudioStream(ISpatialAudioClient *iface,
> +        const PROPVARIANT *prop, REFIID riid, void **stream)
> +{
> +    SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface);
> +    SpatialAudioObjectRenderStreamActivationParams *params;
> +    HRESULT hr;
> +
> +    TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), stream);
> +
> +    if(IsEqualIID(riid, &IID_ISpatialAudioObjectRenderStream)){
> +        SpatialAudioStreamImpl *obj;
> +
> +        if(prop &&
> +                (prop->vt != VT_BLOB ||
> +                 prop->u.blob.cbSize != sizeof(SpatialAudioObjectRenderStreamActivationParams))){
> +            WARN("Got invalid params\n");
> +            *stream = NULL;
> +            return E_INVALIDARG;
> +        }
> +
> +        params = (SpatialAudioObjectRenderStreamActivationParams*) prop->u.blob.pBlobData;
> +
> +        if(params->StaticObjectTypeMask & AudioObjectType_Dynamic){
> +            *stream = NULL;
> +            return E_INVALIDARG;
> +        }
> +
> +        if(!params->ObjectFormat || memcmp(params->ObjectFormat, &This->object_fmtex.Format, sizeof(*params->ObjectFormat) + params->ObjectFormat->cbSize)) {
> +            *stream = NULL;
> +            return AUDCLNT_E_UNSUPPORTED_FORMAT;
> +        }
> +
> +        obj = heap_alloc_zero(sizeof(SpatialAudioStreamImpl));
> +
> +        obj->ISpatialAudioObjectRenderStream_iface.lpVtbl = &ISpatialAudioObjectRenderStream_vtbl;
> +        obj->ref = 1;
> +        memcpy(&obj->params, params, sizeof(obj->params));
> +
> +        obj->update_frames = ~0;
> +
> +        InitializeCriticalSection(&obj->lock);
> +        list_init(&obj->objects);
> +
> +        obj->sa_client = This;
> +        SAC_AddRef(&This->ISpatialAudioClient_iface);
> +
> +        obj->params.ObjectFormat = clone_fmtex(obj->params.ObjectFormat);
> +
> +        if(obj->params.EventHandle != INVALID_HANDLE_VALUE &&
> +                obj->params.EventHandle != 0)

The tests show you can't give a NULL or invalid handle, so I think you
could check for this and bail out earlier.


In tests/spatialaudio.c:

> +static void fill_activation_params(SpatialAudioObjectRenderStreamActivationParams *activation_params)
> +{
> +	activation_params->StaticObjectTypeMask =  \
> +		    AudioObjectType_FrontLeft     |
> +		    AudioObjectType_FrontRight    |
> +		    AudioObjectType_FrontCenter   |
> +		    AudioObjectType_LowFrequency  |
> +		    AudioObjectType_SideLeft      |
> +		    AudioObjectType_SideRight     |
> +		    AudioObjectType_BackLeft      |
> +		    AudioObjectType_BackRight     |
> +		    AudioObjectType_TopFrontLeft  |
> +		    AudioObjectType_TopFrontRight |
> +		    AudioObjectType_TopBackLeft   |
> +		    AudioObjectType_TopBackRight;
> +
> +	activation_params->MinDynamicObjectCount = 0;
> +	activation_params->MaxDynamicObjectCount = 0;
> +	activation_params->Category = AudioCategory_GameEffects;
> +	activation_params->EventHandle = event;
> +	activation_params->NotifyObject = NULL;
> +
> +        activation_params->ObjectFormat = &format;

Mixed tabs/spaces (and all over this file).

> +static void test_audio_object_buffers(void)
> +{
> +        HRESULT hr;
> +        ISpatialAudioObjectRenderStream *sas = NULL;
> +	ISpatialAudioObject *sao[4];
> +        UINT32 dyn_object_count, frame_count, buffer_length;
> +        BYTE *buffer;
> +        INT i;
> +
> +	SpatialAudioObjectRenderStreamActivationParams activation_params;
> +	PROPVARIANT activation_params_prop;
> +
> +	PropVariantInit(&activation_params_prop);
> +	activation_params_prop.vt = VT_BLOB;
> +	activation_params_prop.blob.cbSize = sizeof(activation_params);
> +	activation_params_prop.blob.pBlobData = (BYTE*) &activation_params;
> +
> +        fill_activation_params(&activation_params);
> +	hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas);
> +        ok(hr == S_OK, "Failed to activate spatial audio stream: 0x%08x\n", hr);
> +
> +        hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontLeft, &sao[0]);
> +        ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr);
> +
> +        hr = ISpatialAudioObjectRenderStream_Start(sas);
> +        ok(hr == S_OK, "Failed to activate spatial audio render stream: 0x%08x\n", hr);
> +
> +        hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontRight, &sao[1]);
> +        ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr);
> +
> +        hr = WaitForSingleObject(event, 200);
> +        ok(hr == WAIT_OBJECT_0, "Expected event to be flagged: 0x%08x\n", hr);
> +
> +        hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_SideLeft, &sao[2]);
> +        ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr);
> +
> +        hr = ISpatialAudioObjectRenderStream_BeginUpdatingAudioObjects(sas, &dyn_object_count, &frame_count);
> +        ok(hr == S_OK, "Failed to beging updating audio objects: 0x%08x\n", hr);
> +        ok(dyn_object_count == 0, "Unexpected dynamic objects\n");
> +        ok(frame_count > 0, "Expected to get non-zero frames to update\n");
> +
> +        hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_SideRight, &sao[3]);
> +        ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08x\n", hr);
> +
> +        for (i = 0; i < ARRAYSIZE(sao); i++)
> +        {
> +            hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length);
> +            ok(hr == S_OK, "Expected to be able to get buffers for audio object: 0x%08x\n", hr);
> +            ok(buffer != NULL, "Expected to get a non-NULL buffer\n");
> +            ok(buffer_length == frame_count * format.wBitsPerSample / 8, "Expected buffer length to be sample_size * frame_count = %hu but got %u\n",
> +                    frame_count * format.wBitsPerSample / 8, buffer_length);
> +            ok(is_buffer_zeroed(buffer, buffer_length), "Expected audio object's buffer to be zeroed\n");
> +        }
> +
> +        hr = ISpatialAudioObjectRenderStream_EndUpdatingAudioObjects(sas);
> +        ok(hr == S_OK, "Failed to end updating audio objects: 0x%08x\n", hr);
> +
> +        hr = WaitForSingleObject(event, 200);
> +        ok(hr == WAIT_OBJECT_0, "Expected event to be flagged: 0x%08x\n", hr);
> +
> +        hr = ISpatialAudioObjectRenderStream_BeginUpdatingAudioObjects(sas, &dyn_object_count, &frame_count);
> +        ok(hr == S_OK, "Failed to beging updating audio objects: 0x%08x\n", hr);
> +        ok(dyn_object_count == 0, "Unexpected dynamic objects\n");
> +        ok(frame_count > 0, "Expected to get non-zero frames to update\n");
> +
> +        /* one more iteration but not with every object */
> +        for (i = 0; i < ARRAYSIZE(sao) - 1; i++)
> +        {
> +            hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length);
> +            ok(hr == S_OK, "Expected to be able to get buffers for audio object: 0x%08x\n", hr);
> +            ok(buffer != NULL, "Expected to get a non-NULL buffer\n");
> +            ok(buffer_length == frame_count * format.wBitsPerSample / 8, "Expected buffer length to be sample_size * frame_count = %hu but got %u\n",
> +                    frame_count * format.wBitsPerSample / 8, buffer_length);
> +            ok(is_buffer_zeroed(buffer, buffer_length), "Expected audio object's buffer to be zeroed\n");
> +        }
> +
> +        hr = ISpatialAudioObjectRenderStream_EndUpdatingAudioObjects(sas);
> +        ok(hr == S_OK, "Failed to end updating audio objects: 0x%08x\n", hr);
> +
> +        /* ending the stream */
> +        hr = ISpatialAudioObject_SetEndOfStream(sao[0], 0);
> +        todo_wine ok(hr == SPTLAUDCLNT_E_OUT_OF_ORDER, "Expected that ending the stream at this point won't be allowed: 0x%08x\n", hr);
> +
> +        hr = WaitForSingleObject(event, 10000);

This wait timeout can be decreased.

> +START_TEST(spatialaudio)
> +{
> +    HRESULT hr;
> +
> +    event = CreateEventA(NULL, FALSE, FALSE, "spatial-audio-test-prog-event");
> +    ok (event != NULL, "Failed to create event, last error: 0x%08x\n", GetLastError());

Extra space here.

Andrew



More information about the wine-devel mailing list