mirror of
https://gitlab.winehq.org/wine/wine.git
synced 2025-08-29 02:33:58 +02:00
As tests show, the DirectSound renderer will return immediately from Receive() and EndOfStream(), holding the sample to be rendered arbitrarily later. This is also necessary to properly clear the buffer if the renderer is starved for a long period of time.
1601 lines
50 KiB
C
1601 lines
50 KiB
C
/*
|
|
* DirectSound renderer filter unit tests
|
|
*
|
|
* Copyright (C) 2010 Maarten Lankhorst for CodeWeavers
|
|
* Copyright (C) 2007 Google (Lei Zhang)
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#define COBJMACROS
|
|
#include <math.h>
|
|
#include "dshow.h"
|
|
#include "initguid.h"
|
|
#include "dsound.h"
|
|
#include "amaudio.h"
|
|
#include "mmreg.h"
|
|
#include "wine/strmbase.h"
|
|
#include "wine/test.h"
|
|
|
|
static const WCHAR sink_id[] = L"Audio Input pin (rendered)";
|
|
|
|
static IBaseFilter *create_dsound_render(void)
|
|
{
|
|
IBaseFilter *filter = NULL;
|
|
HRESULT hr = CoCreateInstance(&CLSID_DSoundRender, NULL, CLSCTX_INPROC_SERVER,
|
|
&IID_IBaseFilter, (void **)&filter);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
return filter;
|
|
}
|
|
|
|
static IFilterGraph2 *create_graph(void)
|
|
{
|
|
IFilterGraph2 *ret;
|
|
HRESULT hr;
|
|
hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, &IID_IFilterGraph2, (void **)&ret);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
return ret;
|
|
}
|
|
|
|
static inline BOOL compare_media_types(const AM_MEDIA_TYPE *a, const AM_MEDIA_TYPE *b)
|
|
{
|
|
return !memcmp(a, b, offsetof(AM_MEDIA_TYPE, pbFormat))
|
|
&& !memcmp(a->pbFormat, b->pbFormat, a->cbFormat);
|
|
}
|
|
|
|
static ULONG get_refcount(void *iface)
|
|
{
|
|
IUnknown *unknown = iface;
|
|
IUnknown_AddRef(unknown);
|
|
return IUnknown_Release(unknown);
|
|
}
|
|
|
|
static HRESULT WINAPI property_bag_QueryInterface(IPropertyBag *iface, REFIID iid, void **out)
|
|
{
|
|
ok(0, "Unexpected call (iid %s).\n", wine_dbgstr_guid(iid));
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
static ULONG WINAPI property_bag_AddRef(IPropertyBag *iface)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return 2;
|
|
}
|
|
|
|
static ULONG WINAPI property_bag_Release(IPropertyBag *iface)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return 1;
|
|
}
|
|
|
|
static HRESULT WINAPI property_bag_Read(IPropertyBag *iface, const WCHAR *name, VARIANT *var, IErrorLog *log)
|
|
{
|
|
WCHAR guidstr[39];
|
|
|
|
ok(!wcscmp(name, L"DSGuid"), "Got unexpected name %s.\n", wine_dbgstr_w(name));
|
|
ok(V_VT(var) == VT_BSTR, "Got unexpected type %u.\n", V_VT(var));
|
|
StringFromGUID2(&DSDEVID_DefaultPlayback, guidstr, ARRAY_SIZE(guidstr));
|
|
V_BSTR(var) = SysAllocString(guidstr);
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI property_bag_Write(IPropertyBag *iface, const WCHAR *name, VARIANT *var)
|
|
{
|
|
ok(0, "Unexpected call (name %s).\n", wine_dbgstr_w(name));
|
|
return E_FAIL;
|
|
}
|
|
|
|
static const IPropertyBagVtbl property_bag_vtbl =
|
|
{
|
|
property_bag_QueryInterface,
|
|
property_bag_AddRef,
|
|
property_bag_Release,
|
|
property_bag_Read,
|
|
property_bag_Write,
|
|
};
|
|
|
|
static void test_property_bag(void)
|
|
{
|
|
IPropertyBag property_bag = {&property_bag_vtbl};
|
|
IPersistPropertyBag *ppb;
|
|
HRESULT hr;
|
|
ULONG ref;
|
|
|
|
hr = CoCreateInstance(&CLSID_DSoundRender, NULL, CLSCTX_INPROC_SERVER,
|
|
&IID_IPersistPropertyBag, (void **)&ppb);
|
|
todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
if (hr != S_OK) return;
|
|
|
|
hr = IPersistPropertyBag_InitNew(ppb);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IPersistPropertyBag_Load(ppb, &property_bag, NULL);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
ref = IPersistPropertyBag_Release(ppb);
|
|
ok(!ref, "Got unexpected refcount %ld.\n", ref);
|
|
}
|
|
|
|
#define check_interface(a, b, c) check_interface_(__LINE__, a, b, c)
|
|
static void check_interface_(unsigned int line, void *iface_ptr, REFIID iid, BOOL supported)
|
|
{
|
|
IUnknown *iface = iface_ptr;
|
|
HRESULT hr, expected_hr;
|
|
IUnknown *unk;
|
|
|
|
expected_hr = supported ? S_OK : E_NOINTERFACE;
|
|
|
|
hr = IUnknown_QueryInterface(iface, iid, (void **)&unk);
|
|
ok_(__FILE__, line)(hr == expected_hr, "Got hr %#lx, expected %#lx.\n", hr, expected_hr);
|
|
if (SUCCEEDED(hr))
|
|
IUnknown_Release(unk);
|
|
}
|
|
|
|
static void test_interfaces(void)
|
|
{
|
|
IBaseFilter *filter = create_dsound_render();
|
|
IPin *pin;
|
|
|
|
check_interface(filter, &IID_IAMDirectSound, TRUE);
|
|
check_interface(filter, &IID_IBaseFilter, TRUE);
|
|
check_interface(filter, &IID_IBasicAudio, TRUE);
|
|
todo_wine check_interface(filter, &IID_IDirectSound3DBuffer, TRUE);
|
|
check_interface(filter, &IID_IMediaFilter, TRUE);
|
|
check_interface(filter, &IID_IMediaPosition, TRUE);
|
|
check_interface(filter, &IID_IMediaSeeking, TRUE);
|
|
check_interface(filter, &IID_IPersist, TRUE);
|
|
todo_wine check_interface(filter, &IID_IPersistPropertyBag, TRUE);
|
|
check_interface(filter, &IID_IQualityControl, TRUE);
|
|
check_interface(filter, &IID_IReferenceClock, TRUE);
|
|
check_interface(filter, &IID_IUnknown, TRUE);
|
|
|
|
check_interface(filter, &IID_IAMFilterMiscFlags, FALSE);
|
|
check_interface(filter, &IID_IBasicVideo, FALSE);
|
|
check_interface(filter, &IID_IDispatch, FALSE);
|
|
check_interface(filter, &IID_IKsPropertySet, FALSE);
|
|
check_interface(filter, &IID_IPin, FALSE);
|
|
check_interface(filter, &IID_IQualProp, FALSE);
|
|
check_interface(filter, &IID_IVideoWindow, FALSE);
|
|
|
|
IBaseFilter_FindPin(filter, sink_id, &pin);
|
|
|
|
check_interface(pin, &IID_IPin, TRUE);
|
|
check_interface(pin, &IID_IMemInputPin, TRUE);
|
|
todo_wine check_interface(pin, &IID_IQualityControl, TRUE);
|
|
check_interface(pin, &IID_IUnknown, TRUE);
|
|
|
|
check_interface(pin, &IID_IAsyncReader, FALSE);
|
|
check_interface(pin, &IID_IMediaPosition, FALSE);
|
|
check_interface(pin, &IID_IMediaSeeking, FALSE);
|
|
|
|
IPin_Release(pin);
|
|
IBaseFilter_Release(filter);
|
|
}
|
|
|
|
static const GUID test_iid = {0x33333333};
|
|
static LONG outer_ref = 1;
|
|
|
|
static HRESULT WINAPI outer_QueryInterface(IUnknown *iface, REFIID iid, void **out)
|
|
{
|
|
if (IsEqualGUID(iid, &IID_IUnknown)
|
|
|| IsEqualGUID(iid, &IID_IBaseFilter)
|
|
|| IsEqualGUID(iid, &test_iid))
|
|
{
|
|
*out = (IUnknown *)0xdeadbeef;
|
|
return S_OK;
|
|
}
|
|
ok(0, "unexpected call %s\n", wine_dbgstr_guid(iid));
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
static ULONG WINAPI outer_AddRef(IUnknown *iface)
|
|
{
|
|
return InterlockedIncrement(&outer_ref);
|
|
}
|
|
|
|
static ULONG WINAPI outer_Release(IUnknown *iface)
|
|
{
|
|
return InterlockedDecrement(&outer_ref);
|
|
}
|
|
|
|
static const IUnknownVtbl outer_vtbl =
|
|
{
|
|
outer_QueryInterface,
|
|
outer_AddRef,
|
|
outer_Release,
|
|
};
|
|
|
|
static IUnknown test_outer = {&outer_vtbl};
|
|
|
|
static void test_aggregation(void)
|
|
{
|
|
IBaseFilter *filter, *filter2;
|
|
IUnknown *unk, *unk2;
|
|
HRESULT hr;
|
|
ULONG ref;
|
|
|
|
filter = (IBaseFilter *)0xdeadbeef;
|
|
hr = CoCreateInstance(&CLSID_DSoundRender, &test_outer, CLSCTX_INPROC_SERVER,
|
|
&IID_IBaseFilter, (void **)&filter);
|
|
ok(hr == E_NOINTERFACE, "Got hr %#lx.\n", hr);
|
|
ok(!filter, "Got interface %p.\n", filter);
|
|
|
|
hr = CoCreateInstance(&CLSID_DSoundRender, &test_outer, CLSCTX_INPROC_SERVER,
|
|
&IID_IUnknown, (void **)&unk);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(outer_ref == 1, "Got unexpected refcount %ld.\n", outer_ref);
|
|
ok(unk != &test_outer, "Returned IUnknown should not be outer IUnknown.\n");
|
|
ref = get_refcount(unk);
|
|
ok(ref == 1, "Got unexpected refcount %ld.\n", ref);
|
|
|
|
ref = IUnknown_AddRef(unk);
|
|
ok(ref == 2, "Got unexpected refcount %ld.\n", ref);
|
|
ok(outer_ref == 1, "Got unexpected refcount %ld.\n", outer_ref);
|
|
|
|
ref = IUnknown_Release(unk);
|
|
ok(ref == 1, "Got unexpected refcount %ld.\n", ref);
|
|
ok(outer_ref == 1, "Got unexpected refcount %ld.\n", outer_ref);
|
|
|
|
hr = IUnknown_QueryInterface(unk, &IID_IUnknown, (void **)&unk2);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(unk2 == unk, "Got unexpected IUnknown %p.\n", unk2);
|
|
IUnknown_Release(unk2);
|
|
|
|
hr = IUnknown_QueryInterface(unk, &IID_IBaseFilter, (void **)&filter);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBaseFilter_QueryInterface(filter, &IID_IUnknown, (void **)&unk2);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(unk2 == (IUnknown *)0xdeadbeef, "Got unexpected IUnknown %p.\n", unk2);
|
|
|
|
hr = IBaseFilter_QueryInterface(filter, &IID_IBaseFilter, (void **)&filter2);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(filter2 == (IBaseFilter *)0xdeadbeef, "Got unexpected IBaseFilter %p.\n", filter2);
|
|
|
|
hr = IUnknown_QueryInterface(unk, &test_iid, (void **)&unk2);
|
|
ok(hr == E_NOINTERFACE, "Got hr %#lx.\n", hr);
|
|
ok(!unk2, "Got unexpected IUnknown %p.\n", unk2);
|
|
|
|
hr = IBaseFilter_QueryInterface(filter, &test_iid, (void **)&unk2);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(unk2 == (IUnknown *)0xdeadbeef, "Got unexpected IUnknown %p.\n", unk2);
|
|
|
|
IBaseFilter_Release(filter);
|
|
ref = IUnknown_Release(unk);
|
|
ok(!ref, "Got unexpected refcount %ld.\n", ref);
|
|
ok(outer_ref == 1, "Got unexpected refcount %ld.\n", outer_ref);
|
|
}
|
|
|
|
static void test_enum_pins(void)
|
|
{
|
|
IBaseFilter *filter = create_dsound_render();
|
|
IEnumPins *enum1, *enum2;
|
|
ULONG count, ref;
|
|
IPin *pins[2];
|
|
HRESULT hr;
|
|
|
|
ref = get_refcount(filter);
|
|
ok(ref == 1, "Got unexpected refcount %ld.\n", ref);
|
|
|
|
hr = IBaseFilter_EnumPins(filter, NULL);
|
|
ok(hr == E_POINTER, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBaseFilter_EnumPins(filter, &enum1);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ref = get_refcount(filter);
|
|
ok(ref == 2, "Got unexpected refcount %ld.\n", ref);
|
|
ref = get_refcount(enum1);
|
|
ok(ref == 1, "Got unexpected refcount %ld.\n", ref);
|
|
|
|
hr = IEnumPins_Next(enum1, 1, NULL, NULL);
|
|
ok(hr == E_POINTER, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumPins_Next(enum1, 1, pins, NULL);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ref = get_refcount(filter);
|
|
ok(ref == 3, "Got unexpected refcount %ld.\n", ref);
|
|
ref = get_refcount(pins[0]);
|
|
ok(ref == 3, "Got unexpected refcount %ld.\n", ref);
|
|
ref = get_refcount(enum1);
|
|
ok(ref == 1, "Got unexpected refcount %ld.\n", ref);
|
|
IPin_Release(pins[0]);
|
|
ref = get_refcount(filter);
|
|
ok(ref == 2, "Got unexpected refcount %ld.\n", ref);
|
|
|
|
hr = IEnumPins_Next(enum1, 1, pins, NULL);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumPins_Reset(enum1);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumPins_Next(enum1, 1, pins, &count);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(count == 1, "Got count %lu.\n", count);
|
|
IPin_Release(pins[0]);
|
|
|
|
hr = IEnumPins_Next(enum1, 1, pins, &count);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
ok(!count, "Got count %lu.\n", count);
|
|
|
|
hr = IEnumPins_Reset(enum1);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumPins_Next(enum1, 2, pins, NULL);
|
|
ok(hr == E_INVALIDARG, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumPins_Next(enum1, 2, pins, &count);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
ok(count == 1, "Got count %lu.\n", count);
|
|
IPin_Release(pins[0]);
|
|
|
|
hr = IEnumPins_Reset(enum1);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumPins_Clone(enum1, &enum2);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumPins_Skip(enum1, 2);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumPins_Skip(enum1, 1);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumPins_Skip(enum1, 1);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumPins_Next(enum1, 1, pins, NULL);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumPins_Next(enum2, 1, pins, NULL);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
IPin_Release(pins[0]);
|
|
|
|
IEnumPins_Release(enum2);
|
|
IEnumPins_Release(enum1);
|
|
ref = IBaseFilter_Release(filter);
|
|
ok(!ref, "Got outstanding refcount %ld.\n", ref);
|
|
}
|
|
|
|
static void test_find_pin(void)
|
|
{
|
|
IBaseFilter *filter = create_dsound_render();
|
|
IEnumPins *enum_pins;
|
|
IPin *pin, *pin2;
|
|
HRESULT hr;
|
|
ULONG ref;
|
|
|
|
hr = IBaseFilter_FindPin(filter, L"In", &pin);
|
|
ok(hr == VFW_E_NOT_FOUND, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBaseFilter_FindPin(filter, L"input pin", &pin);
|
|
ok(hr == VFW_E_NOT_FOUND, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBaseFilter_FindPin(filter, sink_id, &pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBaseFilter_EnumPins(filter, &enum_pins);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumPins_Next(enum_pins, 1, &pin2, NULL);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(pin == pin2, "Expected pin %p, got %p.\n", pin2, pin);
|
|
IPin_Release(pin);
|
|
IPin_Release(pin2);
|
|
|
|
IEnumPins_Release(enum_pins);
|
|
ref = IBaseFilter_Release(filter);
|
|
ok(!ref, "Got outstanding refcount %ld.\n", ref);
|
|
}
|
|
|
|
static void test_pin_info(void)
|
|
{
|
|
IBaseFilter *filter = create_dsound_render();
|
|
PIN_DIRECTION dir;
|
|
PIN_INFO info;
|
|
HRESULT hr;
|
|
WCHAR *id;
|
|
ULONG ref;
|
|
IPin *pin;
|
|
|
|
hr = IBaseFilter_FindPin(filter, sink_id, &pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ref = get_refcount(filter);
|
|
ok(ref == 2, "Got unexpected refcount %ld.\n", ref);
|
|
ref = get_refcount(pin);
|
|
ok(ref == 2, "Got unexpected refcount %ld.\n", ref);
|
|
|
|
hr = IPin_QueryPinInfo(pin, &info);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(info.pFilter == filter, "Expected filter %p, got %p.\n", filter, info.pFilter);
|
|
ok(info.dir == PINDIR_INPUT, "Got direction %d.\n", info.dir);
|
|
ok(!wcscmp(info.achName, sink_id), "Got name %s.\n", wine_dbgstr_w(info.achName));
|
|
ref = get_refcount(filter);
|
|
ok(ref == 3, "Got unexpected refcount %ld.\n", ref);
|
|
ref = get_refcount(pin);
|
|
ok(ref == 3, "Got unexpected refcount %ld.\n", ref);
|
|
IBaseFilter_Release(info.pFilter);
|
|
|
|
hr = IPin_QueryDirection(pin, &dir);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(dir == PINDIR_INPUT, "Got direction %d.\n", dir);
|
|
|
|
hr = IPin_QueryId(pin, &id);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(!wcscmp(id, sink_id), "Got id %s.\n", wine_dbgstr_w(id));
|
|
CoTaskMemFree(id);
|
|
|
|
hr = IPin_QueryInternalConnections(pin, NULL, NULL);
|
|
ok(hr == E_NOTIMPL, "Got hr %#lx.\n", hr);
|
|
|
|
IPin_Release(pin);
|
|
ref = IBaseFilter_Release(filter);
|
|
ok(!ref, "Got outstanding refcount %ld.\n", ref);
|
|
}
|
|
|
|
static void test_basic_audio(void)
|
|
{
|
|
IBaseFilter *filter = create_dsound_render();
|
|
LONG balance, volume;
|
|
ITypeInfo *typeinfo;
|
|
unsigned int count;
|
|
IBasicAudio *audio;
|
|
TYPEATTR *typeattr;
|
|
HRESULT hr;
|
|
ULONG ref;
|
|
|
|
hr = IBaseFilter_QueryInterface(filter, &IID_IBasicAudio, (void **)&audio);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBasicAudio_get_Balance(audio, NULL);
|
|
ok(hr == E_POINTER, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBasicAudio_get_Balance(audio, &balance);
|
|
if (hr != VFW_E_MONO_AUDIO_HW)
|
|
{
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(balance == 0, "Got balance %ld.\n", balance);
|
|
|
|
hr = IBasicAudio_put_Balance(audio, DSBPAN_LEFT - 1);
|
|
ok(hr == E_INVALIDARG, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBasicAudio_put_Balance(audio, DSBPAN_LEFT);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBasicAudio_get_Balance(audio, &balance);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(balance == DSBPAN_LEFT, "Got balance %ld.\n", balance);
|
|
}
|
|
|
|
hr = IBasicAudio_get_Volume(audio, &volume);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(volume == 0, "Got volume %ld.\n", volume);
|
|
|
|
hr = IBasicAudio_put_Volume(audio, DSBVOLUME_MIN - 1);
|
|
ok(hr == E_INVALIDARG, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBasicAudio_put_Volume(audio, DSBVOLUME_MIN);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBasicAudio_get_Volume(audio, &volume);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(volume == DSBVOLUME_MIN, "Got volume %ld.\n", volume);
|
|
|
|
hr = IBasicAudio_GetTypeInfoCount(audio, &count);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(count == 1, "Got count %u.\n", count);
|
|
|
|
hr = IBasicAudio_GetTypeInfo(audio, 0, 0, &typeinfo);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
hr = ITypeInfo_GetTypeAttr(typeinfo, &typeattr);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(typeattr->typekind == TKIND_DISPATCH, "Got kind %u.\n", typeattr->typekind);
|
|
ok(IsEqualGUID(&typeattr->guid, &IID_IBasicAudio), "Got IID %s.\n", wine_dbgstr_guid(&typeattr->guid));
|
|
ITypeInfo_ReleaseTypeAttr(typeinfo, typeattr);
|
|
ITypeInfo_Release(typeinfo);
|
|
|
|
IBasicAudio_Release(audio);
|
|
ref = IBaseFilter_Release(filter);
|
|
ok(!ref, "Got outstanding refcount %ld.\n", ref);
|
|
}
|
|
|
|
static void test_enum_media_types(void)
|
|
{
|
|
IBaseFilter *filter = create_dsound_render();
|
|
IEnumMediaTypes *enum1, *enum2;
|
|
AM_MEDIA_TYPE *mts[2];
|
|
ULONG ref, count;
|
|
HRESULT hr;
|
|
IPin *pin;
|
|
|
|
IBaseFilter_FindPin(filter, sink_id, &pin);
|
|
|
|
hr = IPin_EnumMediaTypes(pin, &enum1);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumMediaTypes_Next(enum1, 1, mts, NULL);
|
|
todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
if (hr == S_OK) CoTaskMemFree(mts[0]->pbFormat);
|
|
if (hr == S_OK) CoTaskMemFree(mts[0]);
|
|
|
|
hr = IEnumMediaTypes_Next(enum1, 1, mts, NULL);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumMediaTypes_Reset(enum1);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumMediaTypes_Next(enum1, 1, mts, &count);
|
|
todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
todo_wine ok(count == 1, "Got count %lu.\n", count);
|
|
if (hr == S_OK) CoTaskMemFree(mts[0]->pbFormat);
|
|
if (hr == S_OK) CoTaskMemFree(mts[0]);
|
|
|
|
hr = IEnumMediaTypes_Next(enum1, 1, mts, &count);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
ok(!count, "Got count %lu.\n", count);
|
|
|
|
hr = IEnumMediaTypes_Reset(enum1);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumMediaTypes_Next(enum1, 2, mts, &count);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
todo_wine ok(count == 1, "Got count %lu.\n", count);
|
|
if (count > 0) CoTaskMemFree(mts[0]->pbFormat);
|
|
if (count > 0) CoTaskMemFree(mts[0]);
|
|
|
|
hr = IEnumMediaTypes_Reset(enum1);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumMediaTypes_Clone(enum1, &enum2);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumMediaTypes_Skip(enum1, 2);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumMediaTypes_Skip(enum1, 1);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumMediaTypes_Reset(enum1);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumMediaTypes_Skip(enum1, 1);
|
|
todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumMediaTypes_Skip(enum1, 1);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumMediaTypes_Next(enum1, 1, mts, NULL);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumMediaTypes_Next(enum2, 1, mts, NULL);
|
|
todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
if (hr == S_OK) CoTaskMemFree(mts[0]->pbFormat);
|
|
if (hr == S_OK) CoTaskMemFree(mts[0]);
|
|
|
|
IEnumMediaTypes_Release(enum1);
|
|
IEnumMediaTypes_Release(enum2);
|
|
IPin_Release(pin);
|
|
|
|
ref = IBaseFilter_Release(filter);
|
|
ok(!ref, "Got outstanding refcount %ld.\n", ref);
|
|
}
|
|
|
|
struct testfilter
|
|
{
|
|
struct strmbase_filter filter;
|
|
struct strmbase_source source;
|
|
IMediaSeeking IMediaSeeking_iface;
|
|
};
|
|
|
|
static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface)
|
|
{
|
|
return CONTAINING_RECORD(iface, struct testfilter, filter);
|
|
}
|
|
|
|
static struct strmbase_pin *testfilter_get_pin(struct strmbase_filter *iface, unsigned int index)
|
|
{
|
|
struct testfilter *filter = impl_from_strmbase_filter(iface);
|
|
if (!index)
|
|
return &filter->source.pin;
|
|
return NULL;
|
|
}
|
|
|
|
static void testfilter_destroy(struct strmbase_filter *iface)
|
|
{
|
|
struct testfilter *filter = impl_from_strmbase_filter(iface);
|
|
strmbase_source_cleanup(&filter->source);
|
|
strmbase_filter_cleanup(&filter->filter);
|
|
}
|
|
|
|
static const struct strmbase_filter_ops testfilter_ops =
|
|
{
|
|
.filter_get_pin = testfilter_get_pin,
|
|
.filter_destroy = testfilter_destroy,
|
|
};
|
|
|
|
static HRESULT testsource_query_interface(struct strmbase_pin *iface, REFIID iid, void **out)
|
|
{
|
|
struct testfilter *filter = impl_from_strmbase_filter(iface->filter);
|
|
|
|
ok(!IsEqualGUID(iid, &IID_IQualityControl), "Unexpected query for IQualityControl.\n");
|
|
|
|
if (IsEqualGUID(iid, &IID_IMediaSeeking))
|
|
*out = &filter->IMediaSeeking_iface;
|
|
else
|
|
return E_NOINTERFACE;
|
|
|
|
IUnknown_AddRef((IUnknown *)*out);
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI testsource_DecideAllocator(struct strmbase_source *iface,
|
|
IMemInputPin *peer, IMemAllocator **allocator)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
static const struct strmbase_source_ops testsource_ops =
|
|
{
|
|
.base.pin_query_interface = testsource_query_interface,
|
|
.pfnAttemptConnection = BaseOutputPinImpl_AttemptConnection,
|
|
.pfnDecideAllocator = testsource_DecideAllocator,
|
|
};
|
|
|
|
static struct testfilter *impl_from_IMediaSeeking(IMediaSeeking *iface)
|
|
{
|
|
return CONTAINING_RECORD(iface, struct testfilter, IMediaSeeking_iface);
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_QueryInterface(IMediaSeeking *iface, REFIID iid, void **out)
|
|
{
|
|
struct testfilter *filter = impl_from_IMediaSeeking(iface);
|
|
return IUnknown_QueryInterface(filter->filter.outer_unk, iid, out);
|
|
}
|
|
|
|
static ULONG WINAPI testseek_AddRef(IMediaSeeking *iface)
|
|
{
|
|
struct testfilter *filter = impl_from_IMediaSeeking(iface);
|
|
return IUnknown_AddRef(filter->filter.outer_unk);
|
|
}
|
|
|
|
static ULONG WINAPI testseek_Release(IMediaSeeking *iface)
|
|
{
|
|
struct testfilter *filter = impl_from_IMediaSeeking(iface);
|
|
return IUnknown_Release(filter->filter.outer_unk);
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_GetCapabilities(IMediaSeeking *iface, DWORD *caps)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_CheckCapabilities(IMediaSeeking *iface, DWORD *caps)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_IsFormatSupported(IMediaSeeking *iface, const GUID *format)
|
|
{
|
|
if (winetest_debug > 1) trace("IsFormatSupported(%s)\n", debugstr_guid(format));
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_QueryPreferredFormat(IMediaSeeking *iface, GUID *format)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_GetTimeFormat(IMediaSeeking *iface, GUID *format)
|
|
{
|
|
if (winetest_debug > 1) trace("GetTimeFormat()\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_IsUsingTimeFormat(IMediaSeeking *iface, const GUID *format)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_SetTimeFormat(IMediaSeeking *iface, const GUID *format)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_GetDuration(IMediaSeeking *iface, LONGLONG *duration)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_GetStopPosition(IMediaSeeking *iface, LONGLONG *stop)
|
|
{
|
|
if (winetest_debug > 1) trace("GetStopPosition()\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_GetCurrentPosition(IMediaSeeking *iface, LONGLONG *current)
|
|
{
|
|
if (winetest_debug > 1) trace("GetCurrentPosition()\n");
|
|
return 0xdeadbeef;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_ConvertTimeFormat(IMediaSeeking *iface, LONGLONG *target,
|
|
const GUID *target_format, LONGLONG source, const GUID *source_format)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_SetPositions(IMediaSeeking *iface, LONGLONG *current,
|
|
DWORD current_flags, LONGLONG *stop, DWORD stop_flags)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_GetPositions(IMediaSeeking *iface, LONGLONG *current, LONGLONG *stop)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_GetAvailable(IMediaSeeking *iface, LONGLONG *earliest, LONGLONG *latest)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_SetRate(IMediaSeeking *iface, double rate)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_GetRate(IMediaSeeking *iface, double *rate)
|
|
{
|
|
if (winetest_debug > 1) trace("GetRate()\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI testseek_GetPreroll(IMediaSeeking *iface, LONGLONG *preroll)
|
|
{
|
|
ok(0, "Unexpected call.\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static const IMediaSeekingVtbl testseek_vtbl =
|
|
{
|
|
testseek_QueryInterface,
|
|
testseek_AddRef,
|
|
testseek_Release,
|
|
testseek_GetCapabilities,
|
|
testseek_CheckCapabilities,
|
|
testseek_IsFormatSupported,
|
|
testseek_QueryPreferredFormat,
|
|
testseek_GetTimeFormat,
|
|
testseek_IsUsingTimeFormat,
|
|
testseek_SetTimeFormat,
|
|
testseek_GetDuration,
|
|
testseek_GetStopPosition,
|
|
testseek_GetCurrentPosition,
|
|
testseek_ConvertTimeFormat,
|
|
testseek_SetPositions,
|
|
testseek_GetPositions,
|
|
testseek_GetAvailable,
|
|
testseek_SetRate,
|
|
testseek_GetRate,
|
|
testseek_GetPreroll,
|
|
};
|
|
|
|
static void testfilter_init(struct testfilter *filter)
|
|
{
|
|
static const GUID clsid = {0xabacab};
|
|
strmbase_filter_init(&filter->filter, NULL, &clsid, &testfilter_ops);
|
|
strmbase_source_init(&filter->source, &filter->filter, L"", &testsource_ops);
|
|
filter->IMediaSeeking_iface.lpVtbl = &testseek_vtbl;
|
|
}
|
|
|
|
static void test_allocator(IMemInputPin *input)
|
|
{
|
|
IMemAllocator *req_allocator, *ret_allocator;
|
|
ALLOCATOR_PROPERTIES props;
|
|
HRESULT hr;
|
|
|
|
hr = IMemInputPin_GetAllocatorRequirements(input, &props);
|
|
ok(hr == E_NOTIMPL, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMemInputPin_GetAllocator(input, &ret_allocator);
|
|
todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
hr = IMemAllocator_GetProperties(ret_allocator, &props);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(!props.cBuffers, "Got %ld buffers.\n", props.cBuffers);
|
|
ok(!props.cbBuffer, "Got size %ld.\n", props.cbBuffer);
|
|
ok(!props.cbAlign, "Got alignment %ld.\n", props.cbAlign);
|
|
ok(!props.cbPrefix, "Got prefix %ld.\n", props.cbPrefix);
|
|
|
|
hr = IMemInputPin_NotifyAllocator(input, ret_allocator, TRUE);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
IMemAllocator_Release(ret_allocator);
|
|
}
|
|
|
|
hr = IMemInputPin_NotifyAllocator(input, NULL, TRUE);
|
|
ok(hr == E_POINTER, "Got hr %#lx.\n", hr);
|
|
|
|
CoCreateInstance(&CLSID_MemoryAllocator, NULL, CLSCTX_INPROC_SERVER,
|
|
&IID_IMemAllocator, (void **)&req_allocator);
|
|
|
|
hr = IMemInputPin_NotifyAllocator(input, req_allocator, TRUE);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMemInputPin_GetAllocator(input, &ret_allocator);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(ret_allocator == req_allocator, "Allocators didn't match.\n");
|
|
|
|
IMemAllocator_Release(req_allocator);
|
|
IMemAllocator_Release(ret_allocator);
|
|
}
|
|
|
|
struct frame_thread_params
|
|
{
|
|
IMemInputPin *sink;
|
|
IMediaSample *sample;
|
|
};
|
|
|
|
static DWORD WINAPI frame_thread(void *arg)
|
|
{
|
|
struct frame_thread_params *params = arg;
|
|
HRESULT hr;
|
|
|
|
if (winetest_debug > 1) trace("%04lx: Sending frame.\n", GetCurrentThreadId());
|
|
hr = IMemInputPin_Receive(params->sink, params->sample);
|
|
if (winetest_debug > 1) trace("%04lx: Returned %#lx.\n", GetCurrentThreadId(), hr);
|
|
IMediaSample_Release(params->sample);
|
|
free(params);
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT send_frame(IMemInputPin *sink)
|
|
{
|
|
struct frame_thread_params *params = malloc(sizeof(*params));
|
|
REFERENCE_TIME start_time, end_time;
|
|
IMemAllocator *allocator;
|
|
unsigned short *words;
|
|
IMediaSample *sample;
|
|
unsigned int i;
|
|
HANDLE thread;
|
|
HRESULT hr;
|
|
BYTE *data;
|
|
DWORD ret;
|
|
|
|
hr = IMemInputPin_GetAllocator(sink, &allocator);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaSample_GetPointer(sample, &data);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
words = (unsigned short *)data;
|
|
for (i = 0; i < 44100 * 2; i += 2)
|
|
words[i] = words[i+1] = sinf(i / 20.0f) * 0x7fff;
|
|
|
|
hr = IMediaSample_SetActualDataLength(sample, 44100 * 4);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
start_time = 0;
|
|
end_time = start_time + 10000000;
|
|
hr = IMediaSample_SetTime(sample, &start_time, &end_time);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaSample_SetPreroll(sample, TRUE);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
params->sink = sink;
|
|
params->sample = sample;
|
|
thread = CreateThread(NULL, 0, frame_thread, params, 0, NULL);
|
|
ret = WaitForSingleObject(thread, 2000);
|
|
todo_wine_if (ret) ok(!ret, "Wait failed.\n");
|
|
GetExitCodeThread(thread, &ret);
|
|
CloseHandle(thread);
|
|
|
|
IMemAllocator_Release(allocator);
|
|
return ret;
|
|
}
|
|
|
|
static void test_filter_state(IMemInputPin *input, IMediaSeeking *seeking, IMediaControl *control)
|
|
{
|
|
OAFilterState state;
|
|
LONGLONG time;
|
|
HRESULT hr;
|
|
|
|
hr = send_frame(input);
|
|
ok(hr == VFW_E_WRONG_STATE, "Got hr %#lx.\n", hr);
|
|
|
|
/* The renderer is not fully paused until it receives a sample. The
|
|
* DirectSound renderer never blocks in Receive(), despite returning S_OK
|
|
* from ReceiveCanBlock(). Instead it holds on to each sample until its
|
|
* presentation time, then writes it into the buffer. This is more work
|
|
* than it's worth to emulate, so for now, we'll ignore this behaviour. */
|
|
|
|
hr = IMediaControl_Pause(control);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
/* It's possible to queue multiple samples while paused. The number of
|
|
* samples that can be queued depends on the length of each sample, but
|
|
* it's not particularly clear how. */
|
|
|
|
hr = IMediaControl_GetState(control, 0, &state);
|
|
ok(hr == VFW_S_STATE_INTERMEDIATE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = send_frame(input);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_GetState(control, 1000, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_Stop(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = send_frame(input);
|
|
ok(hr == VFW_E_WRONG_STATE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_Pause(control);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_GetState(control, 0, &state);
|
|
ok(hr == VFW_S_STATE_INTERMEDIATE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = send_frame(input);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_GetState(control, 1000, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_Run(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_GetState(control, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = send_frame(input);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = send_frame(input);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaSeeking_GetCurrentPosition(seeking, &time);
|
|
ok(hr == 0xdeadbeef, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_GetState(control, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_Run(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_GetState(control, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_Pause(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_GetState(control, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_Stop(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_GetState(control, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
/* The DirectSound renderer will silently refuse to transition to running
|
|
* if it hasn't finished pausing yet. Once it does it reports itself as
|
|
* completely paused. */
|
|
}
|
|
|
|
static void test_flushing(IPin *pin, IMemInputPin *input, IMediaControl *control)
|
|
{
|
|
OAFilterState state;
|
|
HRESULT hr;
|
|
|
|
hr = IMediaControl_Pause(control);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = send_frame(input);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_GetState(control, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IPin_BeginFlush(pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = send_frame(input);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IPin_EndFlush(pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_GetState(control, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_Run(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IPin_BeginFlush(pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = send_frame(input);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IPin_EndFlush(pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = send_frame(input);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_Stop(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
}
|
|
|
|
static unsigned int check_ec_complete(IMediaEvent *eventsrc, DWORD timeout)
|
|
{
|
|
LONG_PTR param1, param2;
|
|
unsigned int ret = 0;
|
|
HRESULT hr;
|
|
LONG code;
|
|
|
|
while ((hr = IMediaEvent_GetEvent(eventsrc, &code, ¶m1, ¶m2, timeout)) == S_OK)
|
|
{
|
|
if (code == EC_COMPLETE)
|
|
{
|
|
ok(param1 == S_OK, "Got param1 %#Ix.\n", param1);
|
|
ok(!param2, "Got param2 %#Ix.\n", param2);
|
|
ret++;
|
|
}
|
|
IMediaEvent_FreeEventParams(eventsrc, code, param1, param2);
|
|
timeout = 0;
|
|
}
|
|
ok(hr == E_ABORT, "Got hr %#lx.\n", hr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void test_eos(IPin *pin, IMemInputPin *input, IMediaSeeking *seeking, IMediaControl *control)
|
|
{
|
|
IMediaEvent *eventsrc;
|
|
OAFilterState state;
|
|
LONGLONG time;
|
|
HRESULT hr;
|
|
BOOL ret;
|
|
|
|
IMediaControl_QueryInterface(control, &IID_IMediaEvent, (void **)&eventsrc);
|
|
|
|
hr = IMediaControl_Pause(control);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(!ret, "Got unexpected EC_COMPLETE.\n");
|
|
|
|
hr = IPin_EndOfStream(pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_GetState(control, 1000, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(!ret, "Got unexpected EC_COMPLETE.\n");
|
|
|
|
hr = send_frame(input);
|
|
todo_wine ok(hr == VFW_E_SAMPLE_REJECTED_EOS, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_Run(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(ret == 1, "Expected EC_COMPLETE.\n");
|
|
|
|
hr = IMediaControl_Stop(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(!ret, "Got unexpected EC_COMPLETE.\n");
|
|
|
|
/* We do not receive an EC_COMPLETE notification until the last sample is
|
|
* done rendering. */
|
|
|
|
hr = IMediaControl_Pause(control);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
hr = send_frame(input);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
hr = IMediaControl_GetState(control, 1000, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
hr = IMediaControl_Run(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(!ret, "Got unexpected EC_COMPLETE.\n");
|
|
|
|
hr = IPin_EndOfStream(pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
todo_wine ok(!ret, "Got unexpected EC_COMPLETE.\n");
|
|
ret = check_ec_complete(eventsrc, 2000);
|
|
todo_wine ok(ret == 1, "Expected EC_COMPLETE.\n");
|
|
|
|
hr = IMediaSeeking_GetCurrentPosition(seeking, &time);
|
|
ok(hr == 0xdeadbeef, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_Stop(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(!ret, "Got unexpected EC_COMPLETE.\n");
|
|
|
|
/* Test sending EOS while flushing. */
|
|
|
|
hr = IMediaControl_Run(control);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
hr = send_frame(input);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IPin_BeginFlush(pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
hr = IPin_EndOfStream(pin);
|
|
todo_wine ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
hr = IPin_EndFlush(pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_Stop(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
todo_wine ok(!ret, "Got unexpected EC_COMPLETE.\n");
|
|
|
|
/* Test sending EOS and then flushing or stopping. */
|
|
|
|
hr = IMediaControl_Pause(control);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
hr = send_frame(input);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
hr = IMediaControl_GetState(control, 1000, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
hr = IMediaControl_Run(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(!ret, "Got unexpected EC_COMPLETE.\n");
|
|
|
|
hr = IPin_EndOfStream(pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
todo_wine ok(!ret, "Got unexpected EC_COMPLETE.\n");
|
|
|
|
hr = IPin_BeginFlush(pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
hr = IPin_EndFlush(pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = send_frame(input);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
hr = IPin_EndOfStream(pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(!ret, "Got unexpected EC_COMPLETE.\n");
|
|
|
|
hr = IMediaControl_Stop(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(!ret, "Got unexpected EC_COMPLETE.\n");
|
|
|
|
IMediaEvent_Release(eventsrc);
|
|
}
|
|
|
|
static void test_connect_pin(void)
|
|
{
|
|
ALLOCATOR_PROPERTIES req_props = {1, 4 * 44100, 1, 0}, ret_props;
|
|
WAVEFORMATEX wfx =
|
|
{
|
|
.wFormatTag = WAVE_FORMAT_PCM,
|
|
.nChannels = 2,
|
|
.nSamplesPerSec = 44100,
|
|
.nAvgBytesPerSec = 44100 * 4,
|
|
.nBlockAlign = 4,
|
|
.wBitsPerSample = 16,
|
|
};
|
|
AM_MEDIA_TYPE req_mt =
|
|
{
|
|
.majortype = MEDIATYPE_Audio,
|
|
.subtype = MEDIASUBTYPE_PCM,
|
|
.formattype = FORMAT_WaveFormatEx,
|
|
.cbFormat = sizeof(wfx),
|
|
.pbFormat = (BYTE *)&wfx,
|
|
};
|
|
IBaseFilter *filter = create_dsound_render();
|
|
struct testfilter source;
|
|
IMemAllocator *allocator;
|
|
IMediaSeeking *seeking;
|
|
IMediaControl *control;
|
|
IFilterGraph2 *graph;
|
|
IMemInputPin *input;
|
|
AM_MEDIA_TYPE mt;
|
|
IPin *pin, *peer;
|
|
HRESULT hr;
|
|
ULONG ref;
|
|
|
|
testfilter_init(&source);
|
|
|
|
CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, &IID_IFilterGraph2, (void **)&graph);
|
|
IFilterGraph2_AddFilter(graph, &source.filter.IBaseFilter_iface, L"source");
|
|
IFilterGraph2_AddFilter(graph, filter, L"sink");
|
|
IFilterGraph2_QueryInterface(graph, &IID_IMediaControl, (void **)&control);
|
|
|
|
IBaseFilter_FindPin(filter, sink_id, &pin);
|
|
IBaseFilter_QueryInterface(filter, &IID_IMediaSeeking, (void **)&seeking);
|
|
|
|
peer = (IPin *)0xdeadbeef;
|
|
hr = IPin_ConnectedTo(pin, &peer);
|
|
ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#lx.\n", hr);
|
|
ok(!peer, "Got peer %p.\n", peer);
|
|
|
|
hr = IPin_ConnectionMediaType(pin, &mt);
|
|
ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMediaControl_Pause(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
hr = IFilterGraph2_ConnectDirect(graph, &source.source.pin.IPin_iface, pin, &req_mt);
|
|
ok(hr == VFW_E_NOT_STOPPED, "Got hr %#lx.\n", hr);
|
|
hr = IMediaControl_Stop(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IFilterGraph2_ConnectDirect(graph, &source.source.pin.IPin_iface, pin, &req_mt);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IPin_ConnectedTo(pin, &peer);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(peer == &source.source.pin.IPin_iface, "Got peer %p.\n", peer);
|
|
IPin_Release(peer);
|
|
|
|
hr = IPin_ConnectionMediaType(pin, &mt);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(compare_media_types(&mt, &req_mt), "Media types didn't match.\n");
|
|
|
|
hr = IMediaControl_Pause(control);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
hr = IFilterGraph2_Disconnect(graph, pin);
|
|
ok(hr == VFW_E_NOT_STOPPED, "Got hr %#lx.\n", hr);
|
|
hr = IMediaControl_Stop(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
IPin_QueryInterface(pin, &IID_IMemInputPin, (void **)&input);
|
|
|
|
test_allocator(input);
|
|
|
|
hr = CoCreateInstance(&CLSID_MemoryAllocator, NULL, CLSCTX_INPROC_SERVER,
|
|
&IID_IMemAllocator, (void **)&allocator);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
hr = IMemAllocator_SetProperties(allocator, &req_props, &ret_props);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(!memcmp(&ret_props, &req_props, sizeof(req_props)), "Properties did not match.\n");
|
|
hr = IMemInputPin_NotifyAllocator(input, allocator, TRUE);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
hr = IMemAllocator_Commit(allocator);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IMemInputPin_ReceiveCanBlock(input);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
test_filter_state(input, seeking, control);
|
|
test_flushing(pin, input, control);
|
|
test_eos(pin, input, seeking, control);
|
|
|
|
hr = IFilterGraph2_Disconnect(graph, pin);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
hr = IFilterGraph2_Disconnect(graph, pin);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
ok(source.source.pin.peer == pin, "Got peer %p.\n", source.source.pin.peer);
|
|
IFilterGraph2_Disconnect(graph, &source.source.pin.IPin_iface);
|
|
|
|
peer = (IPin *)0xdeadbeef;
|
|
hr = IPin_ConnectedTo(pin, &peer);
|
|
ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#lx.\n", hr);
|
|
ok(!peer, "Got peer %p.\n", peer);
|
|
|
|
hr = IPin_ConnectionMediaType(pin, &mt);
|
|
ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#lx.\n", hr);
|
|
|
|
ref = IMemAllocator_Release(allocator);
|
|
ok(!ref, "Got outstanding refcount %ld.\n", ref);
|
|
IMemInputPin_Release(input);
|
|
IPin_Release(pin);
|
|
IMediaControl_Release(control);
|
|
ref = IFilterGraph2_Release(graph);
|
|
ok(!ref, "Got outstanding refcount %ld.\n", ref);
|
|
IMediaSeeking_Release(seeking);
|
|
ref = IBaseFilter_Release(filter);
|
|
ok(!ref, "Got outstanding refcount %ld.\n", ref);
|
|
ref = IBaseFilter_Release(&source.filter.IBaseFilter_iface);
|
|
ok(!ref, "Got outstanding refcount %ld.\n", ref);
|
|
}
|
|
|
|
static void test_unconnected_filter_state(void)
|
|
{
|
|
IBaseFilter *filter = create_dsound_render();
|
|
FILTER_STATE state;
|
|
HRESULT hr;
|
|
ULONG ref;
|
|
|
|
hr = IBaseFilter_GetState(filter, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(state == State_Stopped, "Got state %u.\n", state);
|
|
|
|
hr = IBaseFilter_Pause(filter);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBaseFilter_GetState(filter, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(state == State_Paused, "Got state %u.\n", state);
|
|
|
|
hr = IBaseFilter_Run(filter, 0);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBaseFilter_GetState(filter, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(state == State_Running, "Got state %u.\n", state);
|
|
|
|
hr = IBaseFilter_Pause(filter);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBaseFilter_GetState(filter, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(state == State_Paused, "Got state %u.\n", state);
|
|
|
|
hr = IBaseFilter_Stop(filter);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBaseFilter_GetState(filter, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(state == State_Stopped, "Got state %u.\n", state);
|
|
|
|
hr = IBaseFilter_Run(filter, 0);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBaseFilter_GetState(filter, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(state == State_Running, "Got state %u.\n", state);
|
|
|
|
hr = IBaseFilter_Stop(filter);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IBaseFilter_GetState(filter, 0, &state);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
ok(state == State_Stopped, "Got state %u.\n", state);
|
|
|
|
ref = IBaseFilter_Release(filter);
|
|
ok(!ref, "Got outstanding refcount %ld.\n", ref);
|
|
}
|
|
|
|
static HRESULT does_dsound_support_format(WAVEFORMATEX *format)
|
|
{
|
|
const DSBUFFERDESC desc =
|
|
{
|
|
.dwSize = sizeof(DSBUFFERDESC),
|
|
.dwBufferBytes = format->nAvgBytesPerSec,
|
|
.lpwfxFormat = format,
|
|
};
|
|
IDirectSoundBuffer *buffer;
|
|
IDirectSound *dsound;
|
|
HRESULT hr;
|
|
|
|
hr = DirectSoundCreate(NULL, &dsound, NULL);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IDirectSound_CreateSoundBuffer(dsound, &desc, &buffer, NULL);
|
|
if (hr == S_OK)
|
|
IDirectSoundBuffer_Release(buffer);
|
|
IDirectSound_Release(dsound);
|
|
|
|
return hr == S_OK ? S_OK : S_FALSE;
|
|
}
|
|
|
|
static void test_media_types(void)
|
|
{
|
|
IBaseFilter *filter = create_dsound_render();
|
|
AM_MEDIA_TYPE *mt, req_mt = {{0}};
|
|
IEnumMediaTypes *enummt;
|
|
WAVEFORMATEX wfx = {0};
|
|
HRESULT hr, expect_hr;
|
|
unsigned int i, j;
|
|
WORD channels;
|
|
ULONG ref;
|
|
IPin *pin;
|
|
|
|
static const DWORD sample_rates[] = {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 96000};
|
|
|
|
static const struct
|
|
{
|
|
WORD tag;
|
|
WORD depth;
|
|
} formats[] =
|
|
{
|
|
{WAVE_FORMAT_PCM, 8},
|
|
{WAVE_FORMAT_PCM, 16},
|
|
{WAVE_FORMAT_PCM, 24},
|
|
{WAVE_FORMAT_PCM, 32},
|
|
{WAVE_FORMAT_IEEE_FLOAT, 32},
|
|
{WAVE_FORMAT_IEEE_FLOAT, 64},
|
|
};
|
|
|
|
IBaseFilter_FindPin(filter, sink_id, &pin);
|
|
|
|
hr = IPin_EnumMediaTypes(pin, &enummt);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IEnumMediaTypes_Next(enummt, 1, &mt, NULL);
|
|
todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
if (hr == S_OK)
|
|
{
|
|
ok(IsEqualGUID(&mt->majortype, &MEDIATYPE_Audio), "Got major type %s.\n", wine_dbgstr_guid(&mt->majortype));
|
|
ok(IsEqualGUID(&mt->subtype, &GUID_NULL), "Got subtype %s.\n", wine_dbgstr_guid(&mt->subtype));
|
|
ok(mt->bFixedSizeSamples == TRUE, "Got fixed size %d.\n", mt->bFixedSizeSamples);
|
|
ok(!mt->bTemporalCompression, "Got temporal compression %d.\n", mt->bTemporalCompression);
|
|
ok(mt->lSampleSize == 1, "Got sample size %lu.\n", mt->lSampleSize);
|
|
ok(IsEqualGUID(&mt->formattype, &GUID_NULL), "Got format type %s.\n", wine_dbgstr_guid(&mt->formattype));
|
|
ok(!mt->pUnk, "Got pUnk %p.\n", mt->pUnk);
|
|
ok(!mt->cbFormat, "Got format size %lu.\n", mt->cbFormat);
|
|
ok(!mt->pbFormat, "Got unexpected format block.\n");
|
|
|
|
hr = IPin_QueryAccept(pin, mt);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
CoTaskMemFree(mt);
|
|
}
|
|
|
|
hr = IEnumMediaTypes_Next(enummt, 1, &mt, NULL);
|
|
ok(hr == S_FALSE, "Got hr %#lx.\n", hr);
|
|
|
|
req_mt.majortype = MEDIATYPE_Audio;
|
|
req_mt.formattype = FORMAT_WaveFormatEx;
|
|
req_mt.cbFormat = sizeof(WAVEFORMATEX);
|
|
req_mt.pbFormat = (BYTE *)&wfx;
|
|
|
|
IEnumMediaTypes_Release(enummt);
|
|
|
|
for (channels = 1; channels <= 2; ++channels)
|
|
{
|
|
wfx.nChannels = channels;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(formats); ++i)
|
|
{
|
|
wfx.wFormatTag = formats[i].tag;
|
|
wfx.wBitsPerSample = formats[i].depth;
|
|
wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;
|
|
for (j = 0; j < ARRAY_SIZE(sample_rates); ++j)
|
|
{
|
|
wfx.nSamplesPerSec = sample_rates[j];
|
|
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
|
|
|
expect_hr = does_dsound_support_format(&wfx);
|
|
|
|
hr = IPin_QueryAccept(pin, &req_mt);
|
|
ok(hr == expect_hr, "Expected hr %#lx, got %#lx, for %d channels, %d-bit %s, %ld Hz.\n",
|
|
expect_hr, hr, channels, formats[i].depth,
|
|
formats[i].tag == WAVE_FORMAT_PCM ? "integer" : "float", sample_rates[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
IPin_Release(pin);
|
|
ref = IBaseFilter_Release(filter);
|
|
ok(!ref, "Got outstanding refcount %ld.\n", ref);
|
|
}
|
|
|
|
static void test_unconnected_eos(void)
|
|
{
|
|
IBaseFilter *filter = create_dsound_render();
|
|
IFilterGraph2 *graph = create_graph();
|
|
IMediaControl *control;
|
|
IMediaEvent *eventsrc;
|
|
unsigned int ret;
|
|
HRESULT hr;
|
|
ULONG ref;
|
|
|
|
hr = IFilterGraph2_AddFilter(graph, filter, L"renderer");
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IFilterGraph2_QueryInterface(graph, &IID_IMediaControl, (void **)&control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
hr = IFilterGraph2_QueryInterface(graph, &IID_IMediaEvent, (void **)&eventsrc);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(!ret, "Got %u EC_COMPLETE events.\n", ret);
|
|
|
|
hr = IMediaControl_Pause(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(!ret, "Got %u EC_COMPLETE events.\n", ret);
|
|
|
|
hr = IMediaControl_Run(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(ret == 1, "Got %u EC_COMPLETE events.\n", ret);
|
|
|
|
hr = IMediaControl_Pause(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(!ret, "Got %u EC_COMPLETE events.\n", ret);
|
|
|
|
hr = IMediaControl_Run(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(ret == 1, "Got %u EC_COMPLETE events.\n", ret);
|
|
|
|
hr = IMediaControl_Stop(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(!ret, "Got %u EC_COMPLETE events.\n", ret);
|
|
|
|
hr = IMediaControl_Run(control);
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
|
|
ret = check_ec_complete(eventsrc, 0);
|
|
ok(ret == 1, "Got %u EC_COMPLETE events.\n", ret);
|
|
|
|
IMediaControl_Release(control);
|
|
IMediaEvent_Release(eventsrc);
|
|
ref = IFilterGraph2_Release(graph);
|
|
ok(!ref, "Got outstanding refcount %ld.\n", ref);
|
|
ref = IBaseFilter_Release(filter);
|
|
ok(!ref, "Got outstanding refcount %ld.\n", ref);
|
|
}
|
|
|
|
START_TEST(dsoundrender)
|
|
{
|
|
IBaseFilter *filter;
|
|
HRESULT hr;
|
|
|
|
CoInitialize(NULL);
|
|
|
|
hr = CoCreateInstance(&CLSID_DSoundRender, NULL, CLSCTX_INPROC_SERVER,
|
|
&IID_IBaseFilter, (void **)&filter);
|
|
if (hr == VFW_E_NO_AUDIO_HARDWARE)
|
|
{
|
|
skip("No audio hardware.\n");
|
|
CoUninitialize();
|
|
return;
|
|
}
|
|
ok(hr == S_OK, "Got hr %#lx.\n", hr);
|
|
IBaseFilter_Release(filter);
|
|
|
|
test_property_bag();
|
|
test_interfaces();
|
|
test_aggregation();
|
|
test_enum_pins();
|
|
test_find_pin();
|
|
test_pin_info();
|
|
test_basic_audio();
|
|
test_enum_media_types();
|
|
test_unconnected_filter_state();
|
|
test_media_types();
|
|
test_connect_pin();
|
|
test_unconnected_eos();
|
|
|
|
CoUninitialize();
|
|
}
|