winepulse: expose individual audio devices directly

This commit is contained in:
Mark Harmstone 2014-11-04 02:30:03 +00:00 committed by Sebastian Lackner
parent 463d01d3aa
commit 305af53597
5 changed files with 343 additions and 0 deletions

View File

@ -39,6 +39,11 @@ Wine. All those differences are also documented on the
Included bug fixes and improvements
===================================
**Bugfixes and features included in the next upcoming release [1]:**
* Allow selection of audio device for PulseAudio backend
**Bugs fixed in Wine Staging 1.7.30 [90]:**
* ATL IOCS data should not be stored in GWLP_USERDATA ([Wine Bug #21767](https://bugs.winehq.org/show_bug.cgi?id=21767))

1
debian/changelog vendored
View File

@ -1,5 +1,6 @@
wine-compholio (1.7.31) UNRELEASED; urgency=low
* Added possibility to temporarily disable patches to patch system.
* Added patch to allow selecting specific audio device for PulseAudio backend.
* Removed patch for iphlpapi stub functions (accepted upstream).
* Removed first part of patches for FindFirstFileExW (accepted upstream).
-- Sebastian Lackner <sebastian@fds-team.de> Mon, 03 Nov 2014 20:10:04 +0100

View File

@ -1622,6 +1622,7 @@ winemenubuilder-Desktop_Icon_Path.ok:
# | Included patches:
# | * Winepulse patches extracted from https://launchpad.net/~ubuntu-
# | wine/+archive/ubuntu/ppa/+files/wine1.7_1.7.22-0ubuntu1.debian.tar.gz. [rev 4, by Maarten Lankhorst]
# | * Expose audio devices directly to programs. [by Mark Harmstone]
# |
# | This patchset fixes the following Wine bugs:
# | * [#10495] Support for PulseAudio backend for audio
@ -1659,8 +1660,10 @@ winepulse-PulseAudio_Support.ok:
$(call APPLY_FILE,winepulse-PulseAudio_Support/0025-winepulse-use-a-pi-mutex-for-serialization.patch)
$(call APPLY_FILE,winepulse-PulseAudio_Support/0026-winepulse-add-support-for-IMarshal.patch)
$(call APPLY_FILE,winepulse-PulseAudio_Support/0027-winepulse-handle-stream-create-failing-correctly.patch)
$(call APPLY_FILE,winepulse-PulseAudio_Support/0028-winepulse-expose-audio-devices-directly-to-programs.patch)
@( \
echo '+ { "winepulse-PulseAudio_Support", "Maarten Lankhorst", "Winepulse patches extracted from https://launchpad.net/~ubuntu-wine/+archive/ubuntu/ppa/+files/wine1.7_1.7.22-0ubuntu1.debian.tar.gz. [rev 4]" },'; \
echo '+ { "winepulse-PulseAudio_Support", "Mark Harmstone", "Expose audio devices directly to programs." },'; \
) > winepulse-PulseAudio_Support.ok
# Patchset winex11-CandidateWindowPos

View File

@ -0,0 +1,329 @@
From daf353964820e0c9c3b49a5dab3eb952f27d27c3 Mon Sep 17 00:00:00 2001
From: Mark Harmstone <mark@harmstone.com>
Date: Mon, 3 Nov 2014 02:06:40 +0000
Subject: winepulse: expose audio devices directly to programs
At present, winepulse only exposes one input device and one output device. This
patch adds support for individual audio devices, allowing (among other things)
the same program to record from two devices at the same time. It also brings
winepulse more in line with both winealsa et al. and Windows itself. The
moveable "Pulseaudio" devices are still present, and should presumably be
used by default.
Changes by Sebastian Lackner <sebastian@fds-team.de>:
* Merge functions set_device_guid and get_device_guid as they are always used together
* Fixed compiler warnings with -Werror
* Some style fixes and better error handling
---
dlls/winepulse.drv/mmdevdrv.c | 227 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 220 insertions(+), 7 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c
index e755e8a..f6688d8 100644
--- a/dlls/winepulse.drv/mmdevdrv.c
+++ b/dlls/winepulse.drv/mmdevdrv.c
@@ -85,6 +85,11 @@ const WCHAR pulse_keyW[] = {'S','o','f','t','w','a','r','e','\\',
'W','i','n','e','\\','P','u','l','s','e',0};
const WCHAR pulse_streamW[] = { 'S','t','r','e','a','m','V','o','l',0 };
+static const WCHAR drv_key_devicesW[] = {'S','o','f','t','w','a','r','e','\\',
+ 'W','i','n','e','\\','D','r','i','v','e','r','s','\\',
+ 'w','i','n','e','p','u','l','s','e','.','d','r','v','\\','D','e','v','i','c','e','s',0};
+static const WCHAR guidW[] = {'g','u','i','d',0};
+
static GUID pulse_render_guid =
{ 0xfd47d9cc, 0x4218, 0x4135, { 0x9c, 0xe2, 0x0c, 0x19, 0x5c, 0x87, 0x40, 0x5b } };
static GUID pulse_capture_guid =
@@ -173,6 +178,7 @@ struct ACImpl {
IMMDevice *parent;
struct list entry;
float vol[PA_CHANNELS_MAX];
+ char device[256];
LONG ref;
EDataFlow dataflow;
@@ -692,6 +698,9 @@ static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) {
char buffer[64];
static LONG number;
pa_buffer_attr attr;
+ int moving = 0;
+ const char *dev = NULL;
+
if (This->stream) {
pa_stream_disconnect(This->stream);
while (pa_stream_get_state(This->stream) == PA_STREAM_READY)
@@ -716,12 +725,21 @@ static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) {
attr.maxlength = attr.tlength = This->bufsize_bytes;
attr.prebuf = pa_frame_size(&This->ss);
dump_attr(&attr);
+
+ /* If device name is given use exactly the specified device */
+ if (This->device[0]){
+ moving = PA_STREAM_DONT_MOVE;
+ dev = This->device;
+ }
+
if (This->dataflow == eRender)
- ret = pa_stream_connect_playback(This->stream, NULL, &attr,
- PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS, NULL, NULL);
+ ret = pa_stream_connect_playback(This->stream, dev, &attr,
+ PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|
+ PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS|moving, NULL, NULL);
else
- ret = pa_stream_connect_record(This->stream, NULL, &attr,
- PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS);
+ ret = pa_stream_connect_record(This->stream, dev, &attr,
+ PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|
+ PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS|moving);
if (ret < 0) {
WARN("Returns %i\n", ret);
return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
@@ -740,11 +758,127 @@ static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) {
return S_OK;
}
-HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID **keys,
+static BOOL get_device_guid(EDataFlow flow, const char *device, GUID *guid)
+{
+ HKEY key, dev_key;
+ DWORD type, size = sizeof(*guid);
+ WCHAR key_name[258];
+
+ key_name[0] = (flow == eCapture) ? '1' : '0';
+ key_name[1] = ',';
+ if (!MultiByteToWideChar(CP_UTF8, 0, device, -1, key_name + 2,
+ (sizeof(key_name) / sizeof(*key_name)) - 2))
+ return FALSE;
+
+ if (RegCreateKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, NULL, 0,
+ KEY_WRITE|KEY_READ, NULL, &key, NULL) != ERROR_SUCCESS){
+ ERR("Failed to open registry key %s\n", debugstr_w(drv_key_devicesW));
+ return FALSE;
+ }
+
+ if (RegCreateKeyExW(key, key_name, 0, NULL, 0, KEY_WRITE|KEY_READ,
+ NULL, &dev_key, NULL) != ERROR_SUCCESS){
+ ERR("Failed to open registry key for device %s\n", debugstr_w(key_name));
+ RegCloseKey(key);
+ return FALSE;
+ }
+
+ if (RegQueryValueExW(dev_key, guidW, 0, &type, (BYTE *)guid,
+ &size) == ERROR_SUCCESS){
+ if (type == REG_BINARY && size == sizeof(*guid)){
+ RegCloseKey(dev_key);
+ RegCloseKey(key);
+ return TRUE;
+ }
+
+ ERR("Invalid type for device %s GUID: %u; ignoring and overwriting\n",
+ wine_dbgstr_w(key_name), type);
+ }
+
+ /* generate new GUID for this device */
+ CoCreateGuid(guid);
+
+ if (RegSetValueExW(dev_key, guidW, 0, REG_BINARY, (BYTE *)guid,
+ sizeof(GUID)) != ERROR_SUCCESS)
+ ERR("Failed to store device GUID for %s to registry\n", device);
+
+ RegCloseKey(dev_key);
+ RegCloseKey(key);
+ return TRUE;
+}
+
+struct pulse_all_info_cb_data {
+ EDataFlow flow;
+ WCHAR **ids;
+ GUID *keys;
+ UINT num;
+};
+
+static void pulse_all_sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
+ struct pulse_all_info_cb_data *st = (struct pulse_all_info_cb_data*)userdata;
+ void *tmp;
+ DWORD len;
+
+ if (!i)
+ return;
+
+ tmp = HeapReAlloc(GetProcessHeap(), 0, st->ids, sizeof(WCHAR*) * (st->num + 1));
+ if (!tmp) return;
+ st->ids = tmp;
+
+ tmp = HeapReAlloc(GetProcessHeap(), 0, st->keys, sizeof(GUID) * (st->num + 1));
+ if (!tmp) return;
+ st->keys = tmp;
+
+ len = MultiByteToWideChar(CP_UTF8, 0, i->description, -1, NULL, 0);
+ if (!len) return;
+
+ st->ids[st->num] = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
+ if (!st->ids[st->num]) return;
+
+ MultiByteToWideChar(CP_UTF8, 0, i->description, -1, st->ids[st->num], len);
+ if (!get_device_guid(st->flow, i->name, &(st->keys[st->num])))
+ CoCreateGuid(&(st->keys[st->num]));
+
+ st->num++;
+}
+
+static void pulse_all_source_info_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) {
+ struct pulse_all_info_cb_data *st = (struct pulse_all_info_cb_data*)userdata;
+ void *tmp;
+ DWORD len;
+
+ if (!i)
+ return;
+
+ tmp = HeapReAlloc(GetProcessHeap(), 0, st->ids, sizeof(WCHAR*) * (st->num + 1));
+ if (!tmp) return;
+ st->ids = tmp;
+
+ tmp = HeapReAlloc(GetProcessHeap(), 0, st->keys, sizeof(GUID) * (st->num + 1));
+ if (!tmp) return;
+ st->keys = tmp;
+
+ len = MultiByteToWideChar(CP_UTF8, 0, i->description, -1, NULL, 0);
+ if (!len) return;
+
+ st->ids[st->num] = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
+ if (!st->ids[st->num]) return;
+
+ MultiByteToWideChar(CP_UTF8, 0, i->description, -1, st->ids[st->num], len);
+ if (!get_device_guid(st->flow, i->name, &(st->keys[st->num])))
+ CoCreateGuid(&(st->keys[st->num]));
+
+ st->num++;
+}
+
+HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids, GUID **keys,
UINT *num, UINT *def_index)
{
HRESULT hr = S_OK;
WCHAR *id;
+ pa_operation* o;
+ struct pulse_all_info_cb_data st;
TRACE("%d %p %p %p\n", flow, ids, num, def_index);
@@ -778,6 +912,25 @@ HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID **
else
(*keys)[0] = pulse_capture_guid;
+ st.flow = flow;
+ st.ids = *ids;
+ st.keys = *keys;
+ st.num = *num;
+
+ if (flow == eRender)
+ o = pa_context_get_sink_info_list(pulse_ctx, &pulse_all_sink_info_cb, &st);
+ else
+ o = pa_context_get_source_info_list(pulse_ctx, &pulse_all_source_info_cb, &st);
+
+ if (o){
+ while (pa_operation_get_state(o) == PA_OPERATION_RUNNING){ }
+ pa_operation_unref(o);
+ }
+
+ *ids = st.ids;
+ *keys = st.keys;
+ *num = st.num;
+
return S_OK;
}
@@ -794,8 +947,66 @@ int WINAPI AUDDRV_GetPriority(void)
return SUCCEEDED(hr) ? 3 : 0;
}
+static BOOL get_pulse_name_by_guid(const GUID *guid, char *name, DWORD name_size, EDataFlow *flow)
+{
+ HKEY key;
+ DWORD index = 0;
+ WCHAR key_name[258];
+ DWORD key_name_size;
+
+ if (RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_READ,
+ &key) != ERROR_SUCCESS){
+ ERR("No devices found in registry?\n");
+ return FALSE;
+ }
+
+ while(1){
+ HKEY dev_key;
+ DWORD size, type;
+ GUID reg_guid;
+
+ key_name_size = sizeof(key_name)/sizeof(WCHAR);
+ if(RegEnumKeyExW(key, index++, key_name, &key_name_size, NULL,
+ NULL, NULL, NULL) != ERROR_SUCCESS)
+ break;
+
+ if (RegOpenKeyExW(key, key_name, 0, KEY_READ, &dev_key) != ERROR_SUCCESS){
+ ERR("Couldn't open key: %s\n", wine_dbgstr_w(key_name));
+ continue;
+ }
+
+ size = sizeof(reg_guid);
+ if (RegQueryValueExW(dev_key, guidW, 0, &type, (BYTE *)&reg_guid, &size) == ERROR_SUCCESS){
+ if (type == REG_BINARY && size == sizeof(reg_guid) && IsEqualGUID(&reg_guid, guid)){
+ RegCloseKey(dev_key);
+ RegCloseKey(key);
+
+ TRACE("Found matching device key: %s\n", wine_dbgstr_w(key_name));
+
+ if (key_name[0] == '0')
+ *flow = eRender;
+ else if (key_name[0] == '1')
+ *flow = eCapture;
+ else{
+ ERR("Unknown device type: %c\n", key_name[0]);
+ return FALSE;
+ }
+
+ return WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, name, name_size, NULL, NULL);
+ }
+ }
+
+ RegCloseKey(dev_key);
+ }
+
+ RegCloseKey(key);
+ WARN("No matching device in registry for GUID %s\n", debugstr_guid(guid));
+ return FALSE;
+}
+
HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient **out)
{
+ char pulse_name[256] = {0};
HRESULT hr;
ACImpl *This;
int i;
@@ -813,12 +1024,13 @@ HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient
}
TRACE("%s %p %p\n", debugstr_guid(guid), dev, out);
+
if (IsEqualGUID(guid, &pulse_render_guid))
dataflow = eRender;
else if (IsEqualGUID(guid, &pulse_capture_guid))
dataflow = eCapture;
- else
- return E_UNEXPECTED;
+ else if(!get_pulse_name_by_guid(guid, pulse_name, sizeof(pulse_name), &dataflow))
+ return AUDCLNT_E_DEVICE_INVALIDATED;
*out = NULL;
pthread_mutex_lock(&pulse_lock);
@@ -841,6 +1053,7 @@ HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient
This->parent = dev;
for (i = 0; i < PA_CHANNELS_MAX; ++i)
This->vol[i] = 1.f;
+ strcpy(This->device, pulse_name);
hr = CoCreateFreeThreadedMarshaler((IUnknown*)This, &This->marshal);
if (hr) {
--
2.1.3

View File

@ -2,3 +2,8 @@ Author: Maarten Lankhorst
Subject: Winepulse patches extracted from https://launchpad.net/~ubuntu-wine/+archive/ubuntu/ppa/+files/wine1.7_1.7.22-0ubuntu1.debian.tar.gz.
Revision: 4
Fixes: [10495] Support for PulseAudio backend for audio
Author: Mark Harmstone
Subject: Expose audio devices directly to programs.
Revision: 1
Fixes: Allow selection of audio device for PulseAudio backend