From cd5eb1bb199253747800483203976200e7775617 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Fri, 15 Jan 2016 18:29:06 -0800 Subject: ao_openal: wipe out global context on init error Previously this would break all further attempts to init the driver after one had failed. --- audio/out/ao_openal.c | 1 + 1 file changed, 1 insertion(+) (limited to 'audio/out') diff --git a/audio/out/ao_openal.c b/audio/out/ao_openal.c index c6c924b244..72e8799e00 100644 --- a/audio/out/ao_openal.c +++ b/audio/out/ao_openal.c @@ -236,6 +236,7 @@ static int init(struct ao *ao) return 0; err_out: + ao_data = NULL; return -1; } -- cgit v1.2.3 From a99b63db08306b34ef53f5a18807c811b64491a7 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Tue, 5 Jan 2016 18:40:35 -0800 Subject: ao_wasapi: use share_mode value instead of raw option opt_exclusive Previously used opt_exclusive option to decide which volume control code to run. The might not always reflect the actual state, for example if passthrough is used. Admittedly, none of the volume controls will work anyway with passthrough, but this is the right thing to do. --- audio/out/ao_wasapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'audio/out') diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index 1c0e85b7bb..f56c853627 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -441,7 +441,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) return CONTROL_OK; } - return state->opt_exclusive ? + return state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE ? control_exclusive(ao, cmd, arg) : control_shared(ao, cmd, arg); } -- cgit v1.2.3 From 8a9b64329c0e387dc59a1fca477a43c50f59ff34 Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 19 Jan 2016 18:36:06 +0100 Subject: Relicense some non-MPlayer source files to LGPL 2.1 or later This covers source files which were added in mplayer2 and mpv times only, and where all code is covered by LGPL relicensing agreements. There are probably more files to which this applies, but I'm being conservative here. A file named ao_sdl.c exists in MPlayer too, but the mpv one is a complete rewrite, and was added some time after the original ao_sdl.c was removed. The same applies to vo_sdl.c, for which the SDL2 API is radically different in addition (MPlayer supports SDL 1.2 only). common.c contains only code written by me. But common.h is a strange case: although it originally was named mp_common.h and exists in MPlayer too, by now it contains only definitions written by uau and me. The exceptions are the CONTROL_ defines - thus not changing the license of common.h yet. codec_tags.c contained once large tables generated from MPlayer's codecs.conf, but all of these tables were removed. From demux_playlist.c I'm removing a code fragment from someone who was not asked; this probably could be done later (see commit 15dccc37). misc.c is a bit complicated to reason about (it was split off mplayer.c and thus contains random functions out of this file), but actually all functions have been added post-MPlayer. Except get_relative_time(), which was written by uau, but looks similar to 3 different versions of something similar in each of the Unix/win32/OSX timer source files. I'm not sure what that means in regards to copyright, so I've just moved it into another still-GPL source file for now. screenshot.c once had some minor parts of MPlayer's vf_screenshot.c, but they're all gone. --- audio/out/ao_sdl.c | 14 +++++++------- audio/out/ao_wasapi.c | 14 +++++++------- audio/out/ao_wasapi.h | 14 +++++++------- audio/out/ao_wasapi_changenotify.c | 14 +++++++------- audio/out/ao_wasapi_utils.c | 14 +++++++------- audio/out/pull.c | 14 +++++++------- audio/out/push.c | 14 +++++++------- 7 files changed, 49 insertions(+), 49 deletions(-) (limited to 'audio/out') diff --git a/audio/out/ao_sdl.c b/audio/out/ao_sdl.c index 5e5bd25b96..627a1098cf 100644 --- a/audio/out/ao_sdl.c +++ b/audio/out/ao_sdl.c @@ -4,18 +4,18 @@ * * This file is part of mpv. * - * mpv is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * mpv 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. * * mpv 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with mpv. If not, see . + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . */ #include "config.h" diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index f56c853627..5c30f7394a 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -3,18 +3,18 @@ * * Original author: Jonathan Yong <10walls@gmail.com> * - * mpv is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * mpv 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. * * mpv 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with mpv. If not, see . + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . */ #include diff --git a/audio/out/ao_wasapi.h b/audio/out/ao_wasapi.h index 5ba2aa325c..642d92f66f 100644 --- a/audio/out/ao_wasapi.h +++ b/audio/out/ao_wasapi.h @@ -3,18 +3,18 @@ * * Original author: Jonathan Yong <10walls@gmail.com> * - * mpv is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * mpv 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. * * mpv 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with mpv. If not, see . + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . */ #ifndef MP_AO_WASAPI_H_ diff --git a/audio/out/ao_wasapi_changenotify.c b/audio/out/ao_wasapi_changenotify.c index c2bce0d42b..c25b806c8e 100644 --- a/audio/out/ao_wasapi_changenotify.c +++ b/audio/out/ao_wasapi_changenotify.c @@ -3,18 +3,18 @@ * * Original author: Jonathan Yong <10walls@gmail.com> * - * mpv is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * mpv 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. * * mpv 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with mpv. If not, see . + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . */ #include diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index d29c6bed7d..c5e1eab455 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -3,18 +3,18 @@ * * Original author: Jonathan Yong <10walls@gmail.com> * - * mpv is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * mpv 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. * * mpv 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with mpv. If not, see . + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . */ #include diff --git a/audio/out/pull.c b/audio/out/pull.c index 1484c5195f..89805809b7 100644 --- a/audio/out/pull.c +++ b/audio/out/pull.c @@ -1,18 +1,18 @@ /* * This file is part of mpv. * - * mpv is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * mpv 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. * * mpv 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with mpv. If not, see . + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . */ #include diff --git a/audio/out/push.c b/audio/out/push.c index 301004bd99..4fa2bc53d5 100644 --- a/audio/out/push.c +++ b/audio/out/push.c @@ -1,18 +1,18 @@ /* * This file is part of mpv. * - * mpv is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * mpv 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. * * mpv 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with mpv. If not, see . + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . */ #include -- cgit v1.2.3 From 7737499a7459e32cd95e20be9af319be8cba98df Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 19 Jan 2016 21:21:49 +0100 Subject: ao_coreaudio_chmap: change license to LGPL While the situation is not really clear for the other rewritten coreaudio code, it's very clear for the channel mapping code. It was all written by us. (MPlayer doesn't even have any channel map handling.) --- audio/out/ao_coreaudio_chmap.c | 14 +++++++------- audio/out/ao_coreaudio_chmap.h | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) (limited to 'audio/out') diff --git a/audio/out/ao_coreaudio_chmap.c b/audio/out/ao_coreaudio_chmap.c index bdd625ff53..2b64d21aae 100644 --- a/audio/out/ao_coreaudio_chmap.c +++ b/audio/out/ao_coreaudio_chmap.c @@ -1,18 +1,18 @@ /* * This file is part of mpv. * - * mpv is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * mpv 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. * * mpv 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with mpv. If not, see . + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . */ #include "common/common.h" diff --git a/audio/out/ao_coreaudio_chmap.h b/audio/out/ao_coreaudio_chmap.h index a67e1dc252..d58270fc47 100644 --- a/audio/out/ao_coreaudio_chmap.h +++ b/audio/out/ao_coreaudio_chmap.h @@ -1,18 +1,18 @@ /* * This file is part of mpv. * - * mpv is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * mpv 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. * * mpv 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 General Public License for more details. + * GNU Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with mpv. If not, see . + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . */ #ifndef MPV_COREAUDIO_CHMAP_H -- cgit v1.2.3 From ff7884e635ca402e7ca8cd1ea230fb6792b81a65 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Thu, 21 Jan 2016 21:24:49 -0800 Subject: ao_wasapi: exit earlier if there are zero playback devices found Previously, if the enumerator found no devices, attempting to get the default device with IMMDeviceEnumerator::GetDefaultAudioEndpoint would result in the cryptic (and undocumented) E_PROP_ID_UNSUPPORTED. This way, the user is given a better indication of what exactly is wrong and isolates any other possible triggers for this error. --- audio/out/ao_wasapi_utils.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'audio/out') diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index c5e1eab455..2768104cb4 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -873,6 +873,11 @@ LPWSTR find_deviceID(struct ao *ao) if (!enumerator) goto exit_label; + if (!enumerator->count) { + MP_ERR(ao, "There are no playback devices available\n"); + goto exit_label; + } + if (!device.len) { MP_VERBOSE(ao, "No device specified. Selecting default.\n"); d = default_device_desc(enumerator); -- cgit v1.2.3 From ce0b26c60ff31166b4b4317d269c52fe7d576c5c Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Thu, 21 Jan 2016 21:37:04 -0800 Subject: ao_wasapi: use correct UINT type for device enumeration Notably, the address of the enumerator->count member is passed to IMMDeviceCollection::GetCount(), which expects a UINT variable, not an int. How did this ever work? --- audio/out/ao_wasapi_utils.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'audio/out') diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 2768104cb4..6d2a707aac 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -749,7 +749,7 @@ struct enumerator { struct mp_log *log; IMMDeviceEnumerator *pEnumerator; IMMDeviceCollection *pDevices; - int count; + UINT count; }; static void destroy_enumerator(struct enumerator *e) @@ -784,7 +784,7 @@ exit_label: return NULL; } -static struct device_desc *device_desc_for_num(struct enumerator *e, int i) +static struct device_desc *device_desc_for_num(struct enumerator *e, UINT i) { IMMDevice *pDevice = NULL; HRESULT hr = IMMDeviceCollection_Item(e->pDevices, i, &pDevice); @@ -818,7 +818,7 @@ void wasapi_list_devs(struct ao *ao, struct ao_device_list *list) if (!enumerator) return; - for (int i = 0; i < enumerator->count; i++) { + for (UINT i = 0; i < enumerator->count; i++) { struct device_desc *d = device_desc_for_num(enumerator, i); if (!d) goto exit_label; @@ -888,7 +888,7 @@ LPWSTR find_deviceID(struct ao *ao) // try selecting by number bstr rest; long long devno = bstrtoll(device, &rest, 10); - if (!rest.len && 0 <= devno && devno < enumerator->count) { + if (!rest.len && 0 <= devno && devno < (long long)enumerator->count) { MP_VERBOSE(ao, "Selecting device by number: #%lld\n", devno); d = device_desc_for_num(enumerator, devno); deviceID = select_device(ao->log, d); @@ -897,7 +897,7 @@ LPWSTR find_deviceID(struct ao *ao) // select by id or name bstr_eatstart0(&device, "{0.0.0.00000000}."); - for (int i = 0; i < enumerator->count; i++) { + for (UINT i = 0; i < enumerator->count; i++) { d = device_desc_for_num(enumerator, i); if (!d) goto exit_label; -- cgit v1.2.3 From f1072be3b795b1ca360be62cb22581250cd2d6d3 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Fri, 22 Jan 2016 05:16:48 -0800 Subject: ao_wasapi: fix check for already found device oops, forgot to change this when I made get_deviceID a more proper function. state->deviceID is not set or read here - that's for the caller to do. --- audio/out/ao_wasapi_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'audio/out') diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 6d2a707aac..842b7fd9e2 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -909,7 +909,7 @@ LPWSTR find_deviceID(struct ao *ao) } if (bstrcmp(device, bstr_strip(bstr0(d->name))) == 0) { - if (!state->deviceID) { + if (!deviceID) { MP_VERBOSE(ao, "Selecting device by name: \'%.*s\'\n", BSTR_P(device)); deviceID = select_device(ao->log, d); } else { -- cgit v1.2.3 From e927ff16661e7b5bd42cba2aa3f1bd3d4ac148ff Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Fri, 22 Jan 2016 21:35:51 -0800 Subject: ao_wasapi: correct check for specified device on default change Correctly avoid a reload if the current device was specified by the user through --audio-device. Previously, we only recognized if the user had specified --ao=wasapi:device=. --- audio/out/ao_wasapi.h | 1 + audio/out/ao_wasapi_changenotify.c | 6 +++--- audio/out/ao_wasapi_utils.c | 9 +++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) (limited to 'audio/out') diff --git a/audio/out/ao_wasapi.h b/audio/out/ao_wasapi.h index 642d92f66f..eaa056a931 100644 --- a/audio/out/ao_wasapi.h +++ b/audio/out/ao_wasapi.h @@ -119,6 +119,7 @@ char *mp_PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey); #define mp_PKEY_to_str(pkey) mp_PKEY_to_str_buf((char[42]){0}, 42, (pkey)) void wasapi_list_devs(struct ao *ao, struct ao_device_list *list); +bstr wasapi_get_specified_device_string(struct ao *ao); LPWSTR find_deviceID(struct ao *ao); void wasapi_dispatch(struct ao *ao); diff --git a/audio/out/ao_wasapi_changenotify.c b/audio/out/ao_wasapi_changenotify.c index c25b806c8e..661e957bcc 100644 --- a/audio/out/ao_wasapi_changenotify.c +++ b/audio/out/ao_wasapi_changenotify.c @@ -122,7 +122,6 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDefaultDeviceChanged( { change_notify *change = (change_notify *)This; struct ao *ao = change->ao; - struct wasapi_state *state = ao->priv; // don't care about "eCapture" or non-"eMultimedia" roles if (flow == eCapture || role != eMultimedia) return S_OK; @@ -133,9 +132,10 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDefaultDeviceChanged( ao_hotplug_event(ao); } else { // stay on the device the user specified - if (state->opt_device) { + bstr device = wasapi_get_specified_device_string(ao); + if (device.len) { MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered: " - "staying on specified device %s\n", state->opt_device); + "staying on specified device %.*s\n", BSTR_P(device)); return S_OK; } diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 842b7fd9e2..9ca72318ad 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -858,14 +858,19 @@ static LPWSTR select_device(struct mp_log *l, struct device_desc *d) (wcslen(d->deviceID) + 1) * sizeof(wchar_t)); } -LPWSTR find_deviceID(struct ao *ao) +bstr wasapi_get_specified_device_string(struct ao *ao) { - LPWSTR deviceID = NULL; struct wasapi_state *state = ao->priv; bstr device = bstr_strip(bstr0(state->opt_device)); if (!device.len) device = bstr_strip(bstr0(ao->device)); + return device; +} +LPWSTR find_deviceID(struct ao *ao) +{ + LPWSTR deviceID = NULL; + bstr device = wasapi_get_specified_device_string(ao); MP_DBG(ao, "Find device \'%.*s\'\n", BSTR_P(device)); struct device_desc *d = NULL; -- cgit v1.2.3 From 4d5d25fdbbe9b559eb1600520ef33fce380030c9 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Thu, 28 Jan 2016 00:45:38 -0800 Subject: ao_wasapi: add "wasapi" prefix to non-static find_deviceID function --- audio/out/ao_wasapi.c | 2 +- audio/out/ao_wasapi.h | 2 +- audio/out/ao_wasapi_utils.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'audio/out') diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index 5c30f7394a..d84b3e0252 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -281,7 +281,7 @@ static int init(struct ao *ao) struct wasapi_state *state = ao->priv; state->log = ao->log; - state->deviceID = find_deviceID(ao); + state->deviceID = wasapi_find_deviceID(ao); if (!state->deviceID) { uninit(ao); return -1; diff --git a/audio/out/ao_wasapi.h b/audio/out/ao_wasapi.h index eaa056a931..c1181450a3 100644 --- a/audio/out/ao_wasapi.h +++ b/audio/out/ao_wasapi.h @@ -120,7 +120,7 @@ char *mp_PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey); void wasapi_list_devs(struct ao *ao, struct ao_device_list *list); bstr wasapi_get_specified_device_string(struct ao *ao); -LPWSTR find_deviceID(struct ao *ao); +LPWSTR wasapi_find_deviceID(struct ao *ao); void wasapi_dispatch(struct ao *ao); HRESULT wasapi_thread_init(struct ao *ao); diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 9ca72318ad..e3cf6fccae 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -867,7 +867,7 @@ bstr wasapi_get_specified_device_string(struct ao *ao) return device; } -LPWSTR find_deviceID(struct ao *ao) +LPWSTR wasapi_find_deviceID(struct ao *ao) { LPWSTR deviceID = NULL; bstr device = wasapi_get_specified_device_string(ao); -- cgit v1.2.3 From 363a225364f334213b8a1bfb1e9bd77fccdd560e Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 4 Feb 2016 12:29:32 +0100 Subject: ao_coreaudio: fix 7.1(rear) channel mapping I can't explain this, but it seems to be a similar case to the ALSA HDMI one. I find it hard to tell because of the slightly different names and conventions in use in libavcodec, WAVEEXT channel masks, decoders, codec specifications, HDMI, and platform audio APIs. The fix is the same as the one for ao_alsa (see commit be49da72). This should fix at least playing 7.1 sources on OSX with 7.1(rear) selected in Audio MIDI Setup. The ao_alsa commit mentions XBMC, but I couldn't find out where it does that or if it also does that for CoreAudio. It's woth noting that PHT (essentially an old XBMC fork) also exhibited the incorrect behavior (i.e. side and back speakers were swapped). --- audio/out/ao_coreaudio_chmap.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'audio/out') diff --git a/audio/out/ao_coreaudio_chmap.c b/audio/out/ao_coreaudio_chmap.c index 2b64d21aae..3db2bdf3d5 100644 --- a/audio/out/ao_coreaudio_chmap.c +++ b/audio/out/ao_coreaudio_chmap.c @@ -133,6 +133,29 @@ coreaudio_error: return NULL; } + +#define CHMAP(n, ...) &(struct mp_chmap) MP_CONCAT(MP_CHMAP, n) (__VA_ARGS__) + +// Replace each channel in a with b (a->num == b->num) +static void replace_submap(struct mp_chmap *dst, struct mp_chmap *a, + struct mp_chmap *b) +{ + struct mp_chmap t = *dst; + if (!mp_chmap_is_valid(&t) || mp_chmap_diffn(a, &t) != 0) + return; + assert(a->num == b->num); + for (int n = 0; n < t.num; n++) { + for (int i = 0; i < a->num; i++) { + if (t.speaker[n] == a->speaker[i]) { + t.speaker[n] = b->speaker[i]; + break; + } + } + } + if (mp_chmap_is_valid(&t)) + *dst = t; +} + static bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout, struct mp_chmap *chmap) { @@ -163,6 +186,10 @@ static bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout, chmap->speaker[n] = speaker; } + // Remap weird 7.1(rear) layouts correctly. + replace_submap(chmap, CHMAP(6, FL, FR, BL, BR, SDL, SDR), + CHMAP(6, FL, FR, SL, SR, BL, BR)); + talloc_free(talloc_ctx); MP_VERBOSE(ao, "mp chmap: %s\n", mp_chmap_to_str(chmap)); return mp_chmap_is_valid(chmap) && !mp_chmap_is_unknown(chmap); -- cgit v1.2.3 From ff0112e08df32e88549d142371d49a2a8ac8a798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Ekstr=C3=B6m?= Date: Sun, 7 Feb 2016 14:27:46 +0200 Subject: Initial Android support * Adds an 'android' feature, which is automatically detected. * Android has a broken strnlen, so a wrapper is added from FreeBSD. --- audio/out/ao_wasapi_utils.c | 1 + 1 file changed, 1 insertion(+) (limited to 'audio/out') diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index e3cf6fccae..bb23464132 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -29,6 +29,7 @@ #include "audio/format.h" #include "osdep/timer.h" #include "osdep/io.h" +#include "osdep/strnlen.h" #include "ao_wasapi.h" #define MIXER_DEFAULT_LABEL L"mpv - video player" -- cgit v1.2.3 From 72aea5a12bbc07bec0d3cc5b1ce6c2485a0355c5 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Sun, 14 Feb 2016 20:03:47 +0300 Subject: ao: initial OpenSL ES support OpenSL ES is used on Android. At the moment only stereo output is supported. Two options are supported: 'frames-per-buffer' and 'sample-rate'. To get better latency the user of libmpv should pass values obtained from AudioManager.getProperty(PROPERTY_OUTPUT_FRAMES_PER_BUFFER) and AudioManager.getProperty(PROPERTY_OUTPUT_SAMPLE_RATE). --- audio/out/ao.c | 4 + audio/out/ao_opensles.c | 250 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 audio/out/ao_opensles.c (limited to 'audio/out') diff --git a/audio/out/ao.c b/audio/out/ao.c index ffcc43ab79..9c0f644c75 100644 --- a/audio/out/ao.c +++ b/audio/out/ao.c @@ -43,6 +43,7 @@ extern const struct ao_driver audio_out_sndio; extern const struct ao_driver audio_out_pulse; extern const struct ao_driver audio_out_jack; extern const struct ao_driver audio_out_openal; +extern const struct ao_driver audio_out_opensles; extern const struct ao_driver audio_out_null; extern const struct ao_driver audio_out_alsa; extern const struct ao_driver audio_out_wasapi; @@ -74,6 +75,9 @@ static const struct ao_driver * const audio_out_drivers[] = { #if HAVE_OPENAL &audio_out_openal, #endif +#if HAVE_OPENSLES + &audio_out_opensles, +#endif #if HAVE_SDL1 || HAVE_SDL2 &audio_out_sdl, #endif diff --git a/audio/out/ao_opensles.c b/audio/out/ao_opensles.c new file mode 100644 index 0000000000..0e80829557 --- /dev/null +++ b/audio/out/ao_opensles.c @@ -0,0 +1,250 @@ +/* + * OpenSL ES audio output driver. + * Copyright (C) 2016 Ilya Zhuravlev + * + * This file is part of mpv. + * + * mpv 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. + * + * mpv 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 mpv. If not, see . + */ + +#include "ao.h" +#include "internal.h" +#include "common/msg.h" +#include "audio/format.h" +#include "options/m_option.h" +#include "osdep/timer.h" + +#include +#include + +#include + +struct priv { + SLObjectItf sl, output_mix, player; + SLBufferQueueItf buffer_queue; + SLEngineItf engine; + SLPlayItf play; + char *curbuf, *buf1, *buf2; + size_t buffer_size; + pthread_mutex_t buffer_lock; + + int cfg_frames_per_buffer; + int cfg_sample_rate; +}; + +static const int fmtmap[][2] = { + { AF_FORMAT_U8, SL_PCMSAMPLEFORMAT_FIXED_8 }, + { AF_FORMAT_S16, SL_PCMSAMPLEFORMAT_FIXED_16 }, + { AF_FORMAT_S32, SL_PCMSAMPLEFORMAT_FIXED_32 }, + { 0 } +}; + +#define DESTROY(thing) \ + if (p->thing) { \ + (*p->thing)->Destroy(p->thing); \ + p->thing = NULL; \ + } + +static void uninit(struct ao *ao) +{ + struct priv *p = ao->priv; + + DESTROY(player); + DESTROY(output_mix); + DESTROY(sl); + + p->buffer_queue = NULL; + p->engine = NULL; + p->play = NULL; + + pthread_mutex_destroy(&p->buffer_lock); + + free(p->buf1); + free(p->buf2); + p->curbuf = p->buf1 = p->buf2 = NULL; + p->buffer_size = 0; +} + +#undef DESTROY + +static void buffer_callback(SLBufferQueueItf buffer_queue, void *context) +{ + struct ao *ao = context; + struct priv *p = ao->priv; + SLresult res; + void *data[1]; + double delay; + + pthread_mutex_lock(&p->buffer_lock); + + data[0] = p->curbuf; + delay = 2 * p->buffer_size / (double)ao->bps; + ao_read_data(ao, data, p->buffer_size / ao->sstride, + mp_time_us() + 1000000LL * delay); + + res = (*buffer_queue)->Enqueue(buffer_queue, p->curbuf, p->buffer_size); + if (res != SL_RESULT_SUCCESS) + MP_ERR(ao, "Failed to Enqueue: %d\n", res); + else + p->curbuf = (p->curbuf == p->buf1) ? p->buf2 : p->buf1; + + pthread_mutex_unlock(&p->buffer_lock); +} + +#define DEFAULT_BUFFER_SIZE_MS 50 + +#define CHK(stmt) \ + { \ + SLresult res = stmt; \ + if (res != SL_RESULT_SUCCESS) { \ + MP_ERR(ao, "%s: %d\n", #stmt, res); \ + goto error; \ + } \ + } + +static int init(struct ao *ao) +{ + struct priv *p = ao->priv; + SLDataLocator_BufferQueue locator_buffer_queue; + SLDataLocator_OutputMix locator_output_mix; + SLDataFormat_PCM pcm; + SLDataSource audio_source; + SLDataSink audio_sink; + + // This AO only supports two channels at the moment + mp_chmap_from_channels(&ao->channels, 2); + + CHK(slCreateEngine(&p->sl, 0, NULL, 0, NULL, NULL)); + CHK((*p->sl)->Realize(p->sl, SL_BOOLEAN_FALSE)); + CHK((*p->sl)->GetInterface(p->sl, SL_IID_ENGINE, (void*)&p->engine)); + CHK((*p->engine)->CreateOutputMix(p->engine, &p->output_mix, 0, NULL, NULL)); + CHK((*p->output_mix)->Realize(p->output_mix, SL_BOOLEAN_FALSE)); + + locator_buffer_queue.locatorType = SL_DATALOCATOR_BUFFERQUEUE; + locator_buffer_queue.numBuffers = 2; + + pcm.formatType = SL_DATAFORMAT_PCM; + pcm.numChannels = 2; + + int compatible_formats[AF_FORMAT_COUNT]; + af_get_best_sample_formats(ao->format, compatible_formats); + pcm.bitsPerSample = 0; + for (int i = 0; compatible_formats[i] && !pcm.bitsPerSample; ++i) + for (int j = 0; fmtmap[j][0]; ++j) + if (compatible_formats[i] == fmtmap[j][0]) { + ao->format = fmtmap[j][0]; + pcm.bitsPerSample = fmtmap[j][1]; + break; + } + if (!pcm.bitsPerSample) { + MP_ERR(ao, "Cannot find compatible audio format\n"); + goto error; + } + pcm.containerSize = 8 * af_fmt_to_bytes(ao->format); + pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; + + if (p->cfg_sample_rate) + ao->samplerate = p->cfg_sample_rate; + + // samplesPerSec is misnamed, actually it's samples per ms + pcm.samplesPerSec = ao->samplerate * 1000; + + if (p->cfg_frames_per_buffer) + ao->device_buffer = p->cfg_frames_per_buffer; + else + ao->device_buffer = ao->samplerate * DEFAULT_BUFFER_SIZE_MS / 1000; + p->buffer_size = ao->device_buffer * ao->channels.num * + af_fmt_to_bytes(ao->format); + p->buf1 = calloc(1, p->buffer_size); + p->buf2 = calloc(1, p->buffer_size); + p->curbuf = p->buf1; + if (!p->buf1 || !p->buf2) { + MP_ERR(ao, "Failed to allocate device buffer\n"); + goto error; + } + int r = pthread_mutex_init(&p->buffer_lock, NULL); + if (r) { + MP_ERR(ao, "Failed to initialize the mutex: %d\n", r); + goto error; + } + + audio_source.pFormat = (void*)&pcm; + audio_source.pLocator = (void*)&locator_buffer_queue; + + locator_output_mix.locatorType = SL_DATALOCATOR_OUTPUTMIX; + locator_output_mix.outputMix = p->output_mix; + + audio_sink.pLocator = (void*)&locator_output_mix; + audio_sink.pFormat = NULL; + + SLboolean required[] = { SL_BOOLEAN_TRUE }; + SLInterfaceID iid_array[] = { SL_IID_BUFFERQUEUE }; + CHK((*p->engine)->CreateAudioPlayer(p->engine, &p->player, &audio_source, + &audio_sink, 1, iid_array, required)); + CHK((*p->player)->Realize(p->player, SL_BOOLEAN_FALSE)); + CHK((*p->player)->GetInterface(p->player, SL_IID_PLAY, (void*)&p->play)); + CHK((*p->player)->GetInterface(p->player, SL_IID_BUFFERQUEUE, + (void*)&p->buffer_queue)); + CHK((*p->buffer_queue)->RegisterCallback(p->buffer_queue, + buffer_callback, ao)); + + return 1; +error: + uninit(ao); + return -1; +} + +#undef CHK + +static void set_play_state(struct ao *ao, SLuint32 state) +{ + struct priv *p = ao->priv; + SLresult res = (*p->play)->SetPlayState(p->play, state); + if (res != SL_RESULT_SUCCESS) + MP_ERR(ao, "Failed to SetPlayState(%d): %d\n", state, res); +} + +static void reset(struct ao *ao) +{ + set_play_state(ao, SL_PLAYSTATE_STOPPED); +} + +static void resume(struct ao *ao) +{ + struct priv *p = ao->priv; + set_play_state(ao, SL_PLAYSTATE_PLAYING); + + // enqueue two buffers + buffer_callback(p->buffer_queue, ao); + buffer_callback(p->buffer_queue, ao); +} + +#define OPT_BASE_STRUCT struct priv + +const struct ao_driver audio_out_opensles = { + .description = "OpenSL ES audio output", + .name = "opensles", + .init = init, + .uninit = uninit, + .reset = reset, + .resume = resume, + + .priv_size = sizeof(struct priv), + .options = (const struct m_option[]) { + OPT_INTRANGE("frames-per-buffer", cfg_frames_per_buffer, 0, 1, 10000), + OPT_INTRANGE("sample-rate", cfg_sample_rate, 0, 1000, 100000), + {0} + }, +}; -- cgit v1.2.3 From a842ad8f504477f8ca5599d6dda7fbdb63b1bdea Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Thu, 18 Feb 2016 22:24:15 -0800 Subject: ao_wasapi: use SUCCEEDED/FAILED macros --- audio/out/ao_wasapi.c | 20 ++++++++------------ audio/out/ao_wasapi_utils.c | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) (limited to 'audio/out') diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index d84b3e0252..6fbf257bcb 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -40,14 +40,12 @@ static HRESULT get_device_delay(struct wasapi_state *state, double *delay_us) { HRESULT hr; hr = IAudioClock_GetPosition(state->pAudioClock, &position, &qpc_position); + EXIT_ON_ERROR(hr); // GetPosition succeeded, but the result may be // inaccurate due to the length of the call // http://msdn.microsoft.com/en-us/library/windows/desktop/dd370889%28v=vs.85%29.aspx - if (hr == S_FALSE) { + if (hr == S_FALSE) MP_VERBOSE(state, "Possibly inaccurate device position.\n"); - hr = S_OK; - } - EXIT_ON_ERROR(hr); // convert position to number of samples careful to avoid overflow UINT64 sample_position = uint64_scale(position, @@ -135,7 +133,7 @@ static void thread_resume(struct ao *ao) MP_DBG(state, "Thread Resume\n"); UINT32 padding = 0; hr = IAudioClient_GetCurrentPadding(state->pAudioClient, &padding); - if (hr != S_OK) { + if (FAILED(hr)) { MP_ERR(state, "IAudioClient_GetCurrentPadding returned %s\n", mp_HRESULT_to_str(hr)); } @@ -151,7 +149,7 @@ static void thread_resume(struct ao *ao) atomic_compare_exchange_strong(&state->thread_state, &expected, WASAPI_THREAD_FEED); hr = IAudioClient_Start(state->pAudioClient); - if (hr != S_OK) { + if (FAILED(hr)) { MP_ERR(state, "IAudioClient_Start returned %s\n", mp_HRESULT_to_str(hr)); } @@ -165,13 +163,11 @@ static void thread_reset(struct ao *ao) HRESULT hr; MP_DBG(state, "Thread Reset\n"); hr = IAudioClient_Stop(state->pAudioClient); - // we may get S_FALSE if the stream is already stopped - if (hr != S_OK && hr != S_FALSE) + if (FAILED(hr)) MP_ERR(state, "IAudioClient_Stop returned: %s\n", mp_HRESULT_to_str(hr)); - // we may get S_FALSE if the stream is already reset hr = IAudioClient_Reset(state->pAudioClient); - if (hr != S_OK && hr != S_FALSE) + if (FAILED(hr)) MP_ERR(state, "IAudioClient_Reset returned: %s\n", mp_HRESULT_to_str(hr)); atomic_store(&state->sample_count, 0); @@ -190,7 +186,7 @@ static DWORD __stdcall AudioThread(void *lpParameter) state->init_ret = wasapi_thread_init(ao); SetEvent(state->hInitDone); - if (state->init_ret != S_OK) + if (FAILED(state->init_ret)) goto exit_label; MP_DBG(ao, "Entering dispatch loop\n"); @@ -307,7 +303,7 @@ static int init(struct ao *ao) WaitForSingleObject(state->hInitDone, INFINITE); // wait on init complete SAFE_RELEASE(state->hInitDone,CloseHandle(state->hInitDone)); - if (state->init_ret != S_OK) { + if (FAILED(state->init_ret)) { if (!ao->probing) MP_ERR(ao, "Received failure from audio thread\n"); uninit(ao); diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index bb23464132..7816517bb8 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -298,7 +298,7 @@ static bool try_format_exclusive(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat) if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT) EXIT_ON_ERROR(hr); - return hr == S_OK; + return SUCCEEDED(hr); exit_label: MP_ERR(state, "Error testing exclusive format: %s\n", mp_HRESULT_to_str(hr)); return false; -- cgit v1.2.3 From 5e124a4ac306292be698f8e1ebf7ce3dd15b02ba Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Thu, 25 Feb 2016 05:23:42 -0800 Subject: ao_wasapi: fix typo in comment --- audio/out/ao_wasapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'audio/out') diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index 6fbf257bcb..4615baa232 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -60,7 +60,7 @@ static HRESULT get_device_delay(struct wasapi_state *state, double *delay_us) { QueryPerformanceCounter(&qpc); INT64 qpc_diff = av_rescale(qpc.QuadPart, 10000000, state->qpc_frequency.QuadPart) - qpc_position; - // ignore the above calculation if it yeilds more than 10 seconds (due to + // ignore the above calculation if it yields more than 10 seconds (due to // possible overflow inside IAudioClock_GetPosition) if (qpc_diff < 10 * 10000000) { *delay_us -= qpc_diff / 10.0; // convert to us -- cgit v1.2.3 From 31539884c829a0d2aec63d85647110282234a27a Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Fri, 19 Feb 2016 11:04:29 -0800 Subject: ao_wasapi: avoid under-run cascade in exclusive mode. Don't wait for WASAPI to send another feed event if we detect an underfull buffer. It seems that WASAPI doesn't always send extra feed events if something causes rendering to fall behind. This causes every subsequent playback buffer to under-run until playback is reset. The fix is simply to do a one-shot double feed when this happens, which allows rendering to catch up with playback. This was observed to happen when using MsgWaitForMultipleObjects to wait for the feed event and toggling fullscreen with vo=opengl:backend=win. This commit improves the behaviour in that specific case and more generally makes exclusive mode significantly more robust. This commit also moves the logic to avoid *over*filling the exclusive mode buffer into thread_feed right next to the above described underfil logic. --- audio/out/ao_wasapi.c | 60 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 24 deletions(-) (limited to 'audio/out') diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index 4615baa232..509a4d3e48 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -69,7 +69,11 @@ static HRESULT get_device_delay(struct wasapi_state *state, double *delay_us) { "Ignoring it.\n", qpc_diff / 10000000.0); } - MP_TRACE(state, "Device delay: %g us\n", *delay_us); + if (sample_count > 0 && *delay_us <= 0) { + MP_WARN(state, "Under-run: Device delay: %g us\n", *delay_us); + } else { + MP_TRACE(state, "Device delay: %g us\n", *delay_us); + } return S_OK; exit_label: @@ -77,22 +81,39 @@ exit_label: return hr; } -static void thread_feed(struct ao *ao) +static bool thread_feed(struct ao *ao) { struct wasapi_state *state = ao->priv; HRESULT hr; UINT32 frame_count = state->bufferFrameCount; - + UINT32 padding; + hr = IAudioClient_GetCurrentPadding(state->pAudioClient, &padding); + EXIT_ON_ERROR(hr); + bool refill = false; if (state->share_mode == AUDCLNT_SHAREMODE_SHARED) { - UINT32 padding = 0; - hr = IAudioClient_GetCurrentPadding(state->pAudioClient, &padding); - EXIT_ON_ERROR(hr); - + // Return if there's nothing to do. + if (frame_count <= padding) + return false; + // In shared mode, there is only one buffer of size bufferFrameCount. + // We must therefore take care not to overwrite the samples that have + // yet to play. frame_count -= padding; - MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n", - frame_count, padding); + } else if (padding >= 2 * frame_count) { + // In exclusive mode, we exchange entire buffers of size + // bufferFrameCount with the device. If there are already two such + // full buffers waiting to play, there is no work to do. + return false; + } else if (padding < frame_count) { + // If there is not at least one full buffer of audio queued to play in + // exclusive mode, call this function again immediately to try and catch + // up and avoid a cascade of under-runs. WASAPI doesn't seem to be smart + // enough to send more feed events when it gets behind. + refill = true; } + MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n", + frame_count, padding); + double delay_us; hr = get_device_delay(state, &delay_us); EXIT_ON_ERROR(hr); @@ -117,12 +138,12 @@ static void thread_feed(struct ao *ao) atomic_fetch_add(&state->sample_count, frame_count); - return; + return refill; exit_label: MP_ERR(state, "Error feeding audio: %s\n", mp_HRESULT_to_str(hr)); MP_VERBOSE(ao, "Requesting ao reload\n"); ao_request_reload(ao); - return; + return false; } static void thread_resume(struct ao *ao) @@ -131,18 +152,7 @@ static void thread_resume(struct ao *ao) HRESULT hr; MP_DBG(state, "Thread Resume\n"); - UINT32 padding = 0; - hr = IAudioClient_GetCurrentPadding(state->pAudioClient, &padding); - if (FAILED(hr)) { - MP_ERR(state, "IAudioClient_GetCurrentPadding returned %s\n", - mp_HRESULT_to_str(hr)); - } - - // Fill the buffer before starting, but only if there is no audio queued to - // play. This prevents overfilling the buffer, which leads to problems in - // exclusive mode - if (padding < (UINT32) state->bufferFrameCount) - thread_feed(ao); + thread_feed(ao); // start feeding next wakeup if something else hasn't been requested int expected = WASAPI_THREAD_RESUME; @@ -199,7 +209,9 @@ static DWORD __stdcall AudioThread(void *lpParameter) case WAIT_OBJECT_0: switch (atomic_load(&state->thread_state)) { case WASAPI_THREAD_FEED: - thread_feed(ao); + // fill twice on under-full buffer (see comment in thread_feed) + if (thread_feed(ao) && thread_feed(ao)) + MP_ERR(ao, "Unable to fill buffer fast enough\n"); break; case WASAPI_THREAD_RESET: thread_reset(ao); -- cgit v1.2.3 From 84a3c21beb2f3360e4fda13179846406b4a24f7c Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Fri, 26 Feb 2016 06:58:09 -0800 Subject: ao_wasapi: replace laggy COM messaging with mp_dispatch_queue A COM message loop is apparently totally inappropriate for a low latency thread. It leads to audio glitches because the thread doesn't wake up fast enough when it should. It also causes mysterious correlations between the vo and ao thread (i.e., toggling fullscreen delays audio feed events). Instead use an mp_dispatch_queue to set/get volume/mute/session display name from the audio thread. This has the added benefit of obviating the need to marshal the associated interfaces from the audio thread. --- audio/out/ao_wasapi.c | 146 +++++++++++++++++++++++--------------------- audio/out/ao_wasapi.h | 16 +---- audio/out/ao_wasapi_utils.c | 93 ---------------------------- 3 files changed, 80 insertions(+), 175 deletions(-) (limited to 'audio/out') diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index 509a4d3e48..75090791b5 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -24,6 +24,7 @@ #include "options/m_option.h" #include "osdep/timer.h" #include "osdep/io.h" +#include "misc/dispatch.h" #include "ao_wasapi.h" // naive av_rescale for unsigned @@ -188,6 +189,21 @@ static void thread_reset(struct ao *ao) return; } +static void thread_wakeup(void *ptr) +{ + struct ao *ao = ptr; + struct wasapi_state *state = ao->priv; + SetEvent(state->hWake); +} + +static void set_thread_state(struct ao *ao, + enum wasapi_thread_state thread_state) +{ + struct wasapi_state *state = ao->priv; + atomic_store(&state->thread_state, thread_state); + thread_wakeup(ao); +} + static DWORD __stdcall AudioThread(void *lpParameter) { struct ao *ao = lpParameter; @@ -200,41 +216,31 @@ static DWORD __stdcall AudioThread(void *lpParameter) goto exit_label; MP_DBG(ao, "Entering dispatch loop\n"); - while (true) { // watch events - HANDLE events[] = {state->hWake}; - switch (MsgWaitForMultipleObjects(MP_ARRAY_SIZE(events), events, - FALSE, INFINITE, - QS_POSTMESSAGE | QS_SENDMESSAGE)) { - // AudioThread wakeup - case WAIT_OBJECT_0: - switch (atomic_load(&state->thread_state)) { - case WASAPI_THREAD_FEED: - // fill twice on under-full buffer (see comment in thread_feed) - if (thread_feed(ao) && thread_feed(ao)) - MP_ERR(ao, "Unable to fill buffer fast enough\n"); - break; - case WASAPI_THREAD_RESET: - thread_reset(ao); - break; - case WASAPI_THREAD_RESUME: - thread_reset(ao); - thread_resume(ao); - break; - case WASAPI_THREAD_SHUTDOWN: - thread_reset(ao); - goto exit_label; - default: - MP_ERR(ao, "Unhandled thread state\n"); - goto exit_label; - } + while (true) { + if (WaitForSingleObject(state->hWake, INFINITE) != WAIT_OBJECT_0) + MP_ERR(ao, "Unexpected return value from WaitForSingleObject\n"); + + mp_dispatch_queue_process(state->dispatch, 0); + + int thread_state = atomic_load(&state->thread_state); + switch (thread_state) { + case WASAPI_THREAD_FEED: + // fill twice on under-full buffer (see comment in thread_feed) + if (thread_feed(ao) && thread_feed(ao)) + MP_ERR(ao, "Unable to fill buffer fast enough\n"); break; - // messages to dispatch (COM marshalling) - case (WAIT_OBJECT_0 + MP_ARRAY_SIZE(events)): - wasapi_dispatch(ao); + case WASAPI_THREAD_RESET: + thread_reset(ao); break; - default: - MP_ERR(ao, "Unhandled thread event\n"); + case WASAPI_THREAD_RESUME: + thread_reset(ao); + thread_resume(ao); + break; + case WASAPI_THREAD_SHUTDOWN: + thread_reset(ao); goto exit_label; + default: + MP_ERR(ao, "Unhandled thread state: %d\n", thread_state); } } exit_label: @@ -245,19 +251,10 @@ exit_label: return 0; } -static void set_thread_state(struct ao *ao, - enum wasapi_thread_state thread_state) -{ - struct wasapi_state *state = ao->priv; - atomic_store(&state->thread_state, thread_state); - SetEvent(state->hWake); -} - static void uninit(struct ao *ao) { MP_DBG(ao, "Uninit wasapi\n"); struct wasapi_state *state = ao->priv; - wasapi_release_proxies(state); if (state->hWake) set_thread_state(ao, WASAPI_THREAD_SHUTDOWN); @@ -305,6 +302,9 @@ static int init(struct ao *ao) return -1; } + state->dispatch = mp_dispatch_create(state); + mp_dispatch_set_wakeup_fn(state->dispatch, thread_wakeup, ao); + state->init_ret = E_FAIL; state->hAudioThread = CreateThread(NULL, 0, &AudioThread, ao, 0, NULL); if (!state->hAudioThread) { @@ -322,19 +322,18 @@ static int init(struct ao *ao) return -1; } - wasapi_receive_proxies(state); MP_DBG(ao, "Init wasapi done\n"); return 0; } -static int control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg) +static int thread_control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg) { struct wasapi_state *state = ao->priv; switch (cmd) { case AOCONTROL_GET_VOLUME: case AOCONTROL_SET_VOLUME: - if (!state->pEndpointVolumeProxy || + if (!state->pEndpointVolume || !(state->vol_hw_support & ENDPOINT_HARDWARE_SUPPORT_VOLUME)) { return CONTROL_FALSE; } @@ -343,8 +342,7 @@ static int control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg) switch (cmd) { case AOCONTROL_GET_VOLUME: IAudioEndpointVolume_GetMasterVolumeLevelScalar( - state->pEndpointVolumeProxy, - &volume); + state->pEndpointVolume, &volume); *(ao_control_vol_t *)arg = (ao_control_vol_t){ .left = 100.0f * volume, .right = 100.0f * volume, @@ -353,13 +351,12 @@ static int control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg) case AOCONTROL_SET_VOLUME: volume = ((ao_control_vol_t *)arg)->left / 100.f; IAudioEndpointVolume_SetMasterVolumeLevelScalar( - state->pEndpointVolumeProxy, - volume, NULL); + state->pEndpointVolume, volume, NULL); return CONTROL_OK; } case AOCONTROL_GET_MUTE: case AOCONTROL_SET_MUTE: - if (!state->pEndpointVolumeProxy || + if (!state->pEndpointVolume || !(state->vol_hw_support & ENDPOINT_HARDWARE_SUPPORT_MUTE)) { return CONTROL_FALSE; } @@ -367,14 +364,12 @@ static int control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg) BOOL mute; switch (cmd) { case AOCONTROL_GET_MUTE: - IAudioEndpointVolume_GetMute(state->pEndpointVolumeProxy, - &mute); + IAudioEndpointVolume_GetMute(state->pEndpointVolume, &mute); *(bool *)arg = mute; return CONTROL_OK; case AOCONTROL_SET_MUTE: mute = *(bool *)arg; - IAudioEndpointVolume_SetMute(state->pEndpointVolumeProxy, - mute, NULL); + IAudioEndpointVolume_SetMute(state->pEndpointVolume, mute, NULL); return CONTROL_OK; } case AOCONTROL_HAS_PER_APP_VOLUME: @@ -384,18 +379,17 @@ static int control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg) } } -static int control_shared(struct ao *ao, enum aocontrol cmd, void *arg) +static int thread_control_shared(struct ao *ao, enum aocontrol cmd, void *arg) { struct wasapi_state *state = ao->priv; - if (!state->pAudioVolumeProxy) + if (!state->pAudioVolume) return CONTROL_UNKNOWN; float volume; BOOL mute; switch(cmd) { case AOCONTROL_GET_VOLUME: - ISimpleAudioVolume_GetMasterVolume(state->pAudioVolumeProxy, - &volume); + ISimpleAudioVolume_GetMasterVolume(state->pAudioVolume, &volume); *(ao_control_vol_t *)arg = (ao_control_vol_t){ .left = 100.0f * volume, .right = 100.0f * volume, @@ -403,16 +397,15 @@ static int control_shared(struct ao *ao, enum aocontrol cmd, void *arg) return CONTROL_OK; case AOCONTROL_SET_VOLUME: volume = ((ao_control_vol_t *)arg)->left / 100.f; - ISimpleAudioVolume_SetMasterVolume(state->pAudioVolumeProxy, - volume, NULL); + ISimpleAudioVolume_SetMasterVolume(state->pAudioVolume, volume, NULL); return CONTROL_OK; case AOCONTROL_GET_MUTE: - ISimpleAudioVolume_GetMute(state->pAudioVolumeProxy, &mute); + ISimpleAudioVolume_GetMute(state->pAudioVolume, &mute); *(bool *)arg = mute; return CONTROL_OK; case AOCONTROL_SET_MUTE: mute = *(bool *)arg; - ISimpleAudioVolume_SetMute(state->pAudioVolumeProxy, mute, NULL); + ISimpleAudioVolume_SetMute(state->pAudioVolume, mute, NULL); return CONTROL_OK; case AOCONTROL_HAS_PER_APP_VOLUME: return CONTROL_TRUE; @@ -421,14 +414,14 @@ static int control_shared(struct ao *ao, enum aocontrol cmd, void *arg) } } -static int control(struct ao *ao, enum aocontrol cmd, void *arg) +static int thread_control(struct ao *ao, enum aocontrol cmd, void *arg) { struct wasapi_state *state = ao->priv; // common to exclusive and shared switch (cmd) { case AOCONTROL_UPDATE_STREAM_TITLE: - if (!state->pSessionControlProxy) + if (!state->pSessionControl) return CONTROL_FALSE; wchar_t *title = mp_from_utf8(NULL, (char*)arg); @@ -437,12 +430,10 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) // it seems that *sometimes* the SetDisplayName does not take effect and // it still shows the old title. Use this loop to insist until it works. do { - IAudioSessionControl_SetDisplayName(state->pSessionControlProxy, - title, NULL); + IAudioSessionControl_SetDisplayName(state->pSessionControl, title, NULL); SAFE_RELEASE(tmp, CoTaskMemFree(tmp)); - IAudioSessionControl_GetDisplayName(state->pSessionControlProxy, - &tmp); + IAudioSessionControl_GetDisplayName(state->pSessionControl, &tmp); } while (lstrcmpW(title, tmp)); SAFE_RELEASE(tmp, CoTaskMemFree(tmp)); talloc_free(title); @@ -450,7 +441,26 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) } return state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE ? - control_exclusive(ao, cmd, arg) : control_shared(ao, cmd, arg); + thread_control_exclusive(ao, cmd, arg) : + thread_control_shared(ao, cmd, arg); +} + +static void run_control(void *p) +{ + void **pp = p; + struct ao *ao = pp[0]; + enum aocontrol cmd = *(enum aocontrol *)pp[1]; + void *arg = pp[2]; + *(int *)pp[3] = thread_control(ao, cmd, arg); +} + +static int control(struct ao *ao, enum aocontrol cmd, void *arg) +{ + struct wasapi_state *state = ao->priv; + int ret; + void *p[] = {ao, &cmd, arg, &ret}; + mp_dispatch_run(state->dispatch, run_control, p); + return ret; } static void audio_reset(struct ao *ao) diff --git a/audio/out/ao_wasapi.h b/audio/out/ao_wasapi.h index c1181450a3..3ae50159b3 100644 --- a/audio/out/ao_wasapi.h +++ b/audio/out/ao_wasapi.h @@ -66,6 +66,7 @@ typedef struct wasapi_state { HANDLE hAudioThread; // the audio thread itself HANDLE hWake; // thread wakeup event atomic_int thread_state; // enum wasapi_thread_state (what to do on wakeup) + struct mp_dispatch_queue *dispatch; // for volume/mute/session display // for setting the audio thread priority HANDLE hTask; @@ -83,25 +84,12 @@ typedef struct wasapi_state { UINT64 clock_frequency; // scale for position returned by GetPosition LARGE_INTEGER qpc_frequency; // frequency of Windows' high resolution timer - // WASAPI control (handles owned by audio thread but used by main thread) + // WASAPI control IAudioSessionControl *pSessionControl; // setting the stream title IAudioEndpointVolume *pEndpointVolume; // exclusive mode volume/mute ISimpleAudioVolume *pAudioVolume; // shared mode volume/mute DWORD vol_hw_support; // is hardware volume supported for exclusive-mode? - // Streams used to marshal the proxy objects. The thread owning the actual - // objects needs to marshal proxy objects into these streams, and the thread - // that wants the proxies unmarshals them from here. - IStream *sSessionControl; - IStream *sEndpointVolume; - IStream *sAudioVolume; - - // WASAPI proxy handles, for Single-Threaded Apartment communication. One is - // needed for each audio thread object that's accessed from the main thread. - IAudioSessionControl *pSessionControlProxy; - IAudioEndpointVolume *pEndpointVolumeProxy; - ISimpleAudioVolume *pAudioVolumeProxy; - // ao options int opt_exclusive; int opt_list; diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 7816517bb8..ce084731e4 100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -936,93 +936,6 @@ exit_label: return deviceID; } -static void *unmarshal(struct wasapi_state *state, REFIID type, IStream **from) -{ - if (!*from) - return NULL; - void *to_proxy = NULL; - HRESULT hr = CoGetInterfaceAndReleaseStream(*from, type, &to_proxy); - *from = NULL; // the stream is released even on failure - EXIT_ON_ERROR(hr); - return to_proxy; -exit_label: - MP_WARN(state, "Error reading COM proxy: %s\n", mp_HRESULT_to_str(hr)); - return to_proxy; -} - -void wasapi_receive_proxies(struct wasapi_state *state) { - state->pAudioVolumeProxy = unmarshal(state, &IID_ISimpleAudioVolume, - &state->sAudioVolume); - state->pEndpointVolumeProxy = unmarshal(state, &IID_IAudioEndpointVolume, - &state->sEndpointVolume); - state->pSessionControlProxy = unmarshal(state, &IID_IAudioSessionControl, - &state->sSessionControl); -} - -void wasapi_release_proxies(wasapi_state *state) { - SAFE_RELEASE(state->pAudioVolumeProxy, - ISimpleAudioVolume_Release(state->pAudioVolumeProxy)); - SAFE_RELEASE(state->pEndpointVolumeProxy, - IAudioEndpointVolume_Release(state->pEndpointVolumeProxy)); - SAFE_RELEASE(state->pSessionControlProxy, - IAudioSessionControl_Release(state->pSessionControlProxy)); -} - -// Must call CoReleaseMarshalData to decrement marshalled object's reference -// count. -#define SAFE_RELEASE_INTERFACE_STREAM(stream) do { \ - if ((stream) != NULL) { \ - CoReleaseM