* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
+
+/*
+ * Patched audio jack hot-plug event handling
+ *
+ * Contributors:
+ * - S-Core Co., Ltd
+ *
+ * Contact:
+ * Byeongki Shin <bk0121.shin@samsung.com>
+ */
+
#ifdef DSBTYPE_IN
#define NAME "capture buffer"
#define NAME2 "DirectSoundCapture"
}
#ifdef DSBTYPE_IN
-static int dsound_init_in(HWVoiceIn *hw, struct audsettings *as,
+static int _dsound_init_in(HWVoiceIn *hw, struct audsettings *as,
void *drv_opaque)
#else
-static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as,
+static int _dsound_init_out(HWVoiceOut *hw, struct audsettings *as,
void *drv_opaque)
#endif
{
DSoundVoiceIn *ds = (DSoundVoiceIn *) hw;
DSCBUFFERDESC bd;
DSCBCAPS bc;
+ s->dsi = ds;
#else
const char *typ = "DAC";
DSoundVoiceOut *ds = (DSoundVoiceOut *) hw;
DSBUFFERDESC bd;
DSBCAPS bc;
+ s->dso = ds;
#endif
+ dolog2("(%s) Starting.\n", __func__);
+
if (!s->FIELD2) {
- dolog ("Attempt to initialize voice without " NAME2 " object\n");
+ dolog2 ("Attempt to initialize voice without " NAME2 " object\n");
return -1;
}
&ds->dsound_buffer,
NULL
);
+ dolog2 ("(%s) ds(%p), ds->dsound_buffer(%p)\n", __func__,
+ ds, &ds->dsound_buffer);
#endif
if (FAILED (hr)) {
}
hw->samples = bc.dwBufferBytes >> hw->info.shift;
ds->s = s;
-
+ s->settings = obt_as;
#ifdef DEBUG_DSOUND
dolog ("caps %ld, desc %ld\n",
bc.dwBufferBytes, bd.dwBufferBytes);
- dolog ("bufsize %d, freq %d, chan %d, fmt %d\n",
- hw->bufsize, settings.freq, settings.nchannels, settings.fmt);
+ dolog ("freq %d, chan %d, fmt %d\n",
+ s->settings.freq, s->settings.nchannels, s->settings.fmt);
#endif
return 0;
return -1;
}
+#ifdef DSBTYPE_IN
+static int dsound_default_init_in(HWVoiceIn *hw, struct audsettings *as,
+ void *drv_opaque)
+#else
+static int dsound_default_init_out(HWVoiceOut *hw, struct audsettings *as,
+ void *drv_opaque)
+#endif
+{
+ dsound *s = drv_opaque;
+ DSoundConf *conf = &s->conf;
+#ifdef DSBTYPE_IN
+ DSoundVoiceIn *ds = (DSoundVoiceIn *) hw;
+#else
+ DSoundVoiceOut *ds = (DSoundVoiceOut *) hw;
+#endif
+
+ dolog2("(%s) s(%p) hw(%p)\n", __func__, s, hw);
+
+ hw->samples = conf->bufsize_out >> hw->info.shift;
+ ds->s = s;
+#ifdef DSBTYPE_IN
+ s->dsi = ds;
+#else
+ s->dso = ds;
+#endif
+ s->settings = *as;
+ audio_pcm_init_info (&hw->info, as);
+
+ return 0;
+}
+
+#ifdef DSBTYPE_IN
+static int dsound_init_in(HWVoiceIn *hw, struct audsettings *as,
+ void *drv_opaque)
+#else
+static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as,
+ void *drv_opaque)
+#endif
+{
+ dsound *s = drv_opaque;
+
+#ifdef DSBTYPE_IN
+ dolog2("(%s) s(%p) hw(%p) s->in_init_success=%d\n",
+ __func__, s, hw, s->in_init_success);
+ if (s->in_init_success == true) {
+#else
+ dolog2("(%s) s(%p) hw(%p) s->out_init_success=%d\n",
+ __func__, s, hw, s->out_init_success);
+ if (s->out_init_success == true) {
+#endif
+ return (glue (_dsound_init_, TYPE) (hw, as, s));
+ } else {
+ /* We can't get buffer size and hw info yet,
+ * because failed to initialize.
+ * So, we use conf as default
+ */
+ return (glue (dsound_default_init_, TYPE) (hw, as, s));
+ }
+}
+
#undef NAME
#undef NAME2
#undef TYPE
* SEAL 1.07 by Carlos 'pel' Hasan was used as documentation
*/
+/*
+ * Patched audio jack hot-plug event handling
+ *
+ * Contributors:
+ * - S-Core Co., Ltd
+ *
+ * Contact:
+ * Byeongki Shin <bk0121.shin@samsung.com>
+ */
+
#include "qemu-common.h"
#include "audio.h"
+#include "qemu/thread.h"
#define AUDIO_CAP "dsound"
+#define COBJMACROS
#include "audio_int.h"
#include <windows.h>
#include <mmsystem.h>
#include <objbase.h>
#include <dsound.h>
+#include <initguid.h>
+#include <mmdeviceapi.h>
#include "audio_win_int.h"
/* #define DEBUG_DSOUND */
+#ifdef DEBUG_DSOUND
+#define dolog2 dolog
+#else
+#define dolog2(...)
+#endif
+
+/* Assume that each plugging event cannot occur at the same time */
+enum dsound_plugging_event {
+ OUT_UNPLUGGED,
+ OUT_PLUGGED,
+ IN_UNPLUGGED,
+ IN_PLUGGED,
+ NOT_SUPPORTED
+};
+
typedef struct {
int bufsize_in;
int bufsize_out;
} DSoundConf;
typedef struct {
- LPDIRECTSOUND dsound;
- LPDIRECTSOUNDCAPTURE dsound_capture;
- struct audsettings settings;
- DSoundConf conf;
-} dsound;
-
-typedef struct {
HWVoiceOut hw;
LPDIRECTSOUNDBUFFER dsound_buffer;
DWORD old_pos;
int first_time;
- dsound *s;
+ struct dsound *s;
#ifdef DEBUG_DSOUND
DWORD old_ppos;
DWORD played;
HWVoiceIn hw;
int first_time;
LPDIRECTSOUNDCAPTUREBUFFER dsound_capture_buffer;
- dsound *s;
+ struct dsound *s;
} DSoundVoiceIn;
+typedef struct dsound {
+ LPDIRECTSOUND dsound;
+ LPDIRECTSOUNDCAPTURE dsound_capture;
+ DSoundVoiceIn *dsi;
+ DSoundVoiceOut *dso;
+ struct audsettings settings;
+ DSoundConf conf;
+ IMMDeviceEnumerator *mmdev_enumerator;
+ IMMNotificationClient noti_client;
+
+ enum dsound_plugging_event event;
+ bool out_init_success;
+ bool in_init_success;
+
+ QemuThread th;
+ QemuMutex th_mutex;
+ QemuMutex init_mutex;
+ QemuCond th_cond;
+ QemuCond init_cond;
+ bool th_initialized;
+ bool th_exit;
+} dsound;
+
+static void dsound_audio_fini (void *opaque);
+
static void dsound_log_hresult (HRESULT hr)
{
const char *str = "BUG";
return -1;
}
+ /* Because ctl_out of pcm_ops changed to be checked its success,
+ * this function should be able to return success
+ * in the DSERR_BUFFERLOST case.
+ */
if (*statusp & DSERR_BUFFERLOST) {
- dsound_restore_out(dsb, s);
- return -1;
+ return dsound_restore_out(dsb, s);
}
return 0;
dsound *s = ds->s;
if (!dsb) {
- dolog ("Attempt to control voice without a buffer\n");
- return 0;
+ dolog2 ("Attempt to control voice without a buffer\n");
+ return -1;
}
+ dolog2 ("(%s) ds(%p), ds->dsound_buffer(%p)\n", __func__,
+ ds, &ds->dsound_buffer);
switch (cmd) {
case VOICE_ENABLE:
if (dsound_get_status_out (dsb, &status, s)) {
DSoundConf *conf = &s->conf;
if (!dsb) {
- dolog ("Attempt to run empty with playback buffer\n");
+ dolog2 ("Attempt to run empty with playback buffer\n");
return 0;
}
}
#ifdef DEBUG_DSOUND
- ds->played += audio_ring_dist (ds->old_pos, ppos, hw->bufsize);
+ ds->played += audio_ring_dist (ds->old_pos, ppos, bufsize);
#endif
old_pos = ds->old_pos;
}
LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer;
if (!dscb) {
- dolog ("Attempt to control capture voice without a buffer\n");
+ dolog2 ("Attempt to control capture voice without a buffer\n");
return -1;
}
dsound *s = ds->s;
if (!dscb) {
- dolog ("Attempt to run without capture buffer\n");
+ dolog2 ("Attempt to run without capture buffer\n");
return 0;
}
return decr;
}
+static enum dsound_plugging_event dsound_determine_event(DWORD state, EDataFlow flow)
+{
+ enum dsound_plugging_event event;
+
+ /* XXX: is it possible for speaker and mic to be plugged at the same time? */
+ if ((state != DEVICE_STATE_ACTIVE && state != DEVICE_STATE_UNPLUGGED) ||
+ (flow != eRender && flow != eCapture)) {
+ dolog("(%s) not supported event. state=%#lx, flow=%d\n", __func__, state, flow);
+ return NOT_SUPPORTED;
+ }
+
+ if (state == DEVICE_STATE_ACTIVE) {
+ if (flow == eRender) {
+ event = OUT_PLUGGED;
+ } else if (flow == eCapture) {
+ event = IN_PLUGGED;
+ }
+ } else {
+ if (flow == eRender) {
+ event = OUT_UNPLUGGED;
+ } else if (flow == eCapture) {
+ event = IN_UNPLUGGED;
+ }
+ }
+
+ return event;
+}
+
+static int dsound_audio_out_init(dsound *s)
+{
+ HRESULT hr;
+ int err;
+
+ dolog2("(%s) Starting. s->dsound(%p)\n", __func__, s->dsound);
+
+ hr = IDirectSound_Initialize (s->dsound, NULL);
+ if (hr != DSERR_ALREADYINITIALIZED && FAILED (hr)) {
+ dsound_logerr (hr, "Could not initialize DirectSound\n");
+ return -1;
+ }
+
+ err = dsound_open (s);
+ if (err) {
+ dsound_audio_fini (s);
+ return -1;
+ }
+
+ dolog2("(%s) success\n", __func__);
+
+ return 0;
+}
+
+static int dsound_audio_in_init(dsound *s)
+{
+ HRESULT hr;
+
+ dolog2("(%s) Starting. s->dsound_capture(%p)\n", __func__, s->dsound_capture);
+ hr = IDirectSoundCapture_Initialize (s->dsound_capture, NULL);
+ if (hr != DSERR_ALREADYINITIALIZED && FAILED (hr)) {
+ dsound_logerr (hr, "Could not initialize DirectSoundCapture\n");
+ return -1;
+ }
+
+ dolog("(%s) success\n", __func__);
+
+ return 0;
+}
+
+/* Implementation of IMMNotificationClient callbacks */
+static HRESULT STDMETHODCALLTYPE dsound_QueryInterface(IMMNotificationClient *This,
+ REFIID riid, void **ppvObject)
+{
+ dolog2("(%s)\n", __func__);
+ return E_NOTIMPL;
+}
+
+static ULONG STDMETHODCALLTYPE dsound_AddRef(IMMNotificationClient *This)
+{
+ dolog2("(%s)\n", __func__);
+ return E_NOTIMPL;
+}
+
+static ULONG STDMETHODCALLTYPE dsound_Release(IMMNotificationClient *This)
+{
+ dolog2("(%s)\n", __func__);
+ return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE dsound_OnDeviceStateChanged(IMMNotificationClient* This,
+ LPCWSTR pwstrDeviceId, DWORD dwNewState)
+{
+ dsound *s = CONTAINING_RECORD(This, dsound, noti_client);
+ HRESULT hr;
+ IMMDevice *mmdev;
+ IMMEndpoint *ep;
+ EDataFlow flow;
+
+ /* To decide whether IN or OUT, get IMMEndpoint interface */
+ hr = IMMDeviceEnumerator_GetDevice(s->mmdev_enumerator, pwstrDeviceId, &mmdev);
+ if (FAILED (hr)) {
+ dolog("(%s) Could not get mmdevice\n", __func__);
+ return E_FAIL;
+ }
+ dolog2("(%s) mmdev(%p)\n", __func__, mmdev);
+
+ hr = IMMDevice_QueryInterface(mmdev, &IID_IMMEndpoint, (void **)&ep);
+ if (FAILED (hr)) {
+ dolog("(%s) Could not get enpoint\n", __func__);
+ return E_FAIL;
+ }
+
+ hr = IMMEndpoint_GetDataFlow(ep, &flow);
+ if (FAILED (hr)) {
+ dolog("(%s) Could not get dataflow\n", __func__);
+ return E_FAIL;
+ }
+ dolog2("(%s) ep(%p), flow=%d\n", __func__, ep, flow);
+
+ qemu_mutex_lock(&s->th_mutex);
+ s->event = dsound_determine_event(dwNewState, flow);
+ if (s->event != NOT_SUPPORTED) {
+ qemu_cond_signal(&s->th_cond);
+ }
+ qemu_mutex_unlock(&s->th_mutex);
+
+ return S_OK;
+}
+
+static HRESULT STDMETHODCALLTYPE dsound_OnDeviceAdded(IMMNotificationClient* This,
+ LPCWSTR pwstrDeviceId)
+{
+ dolog2("(%s)\n", __func__);
+ return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE dsound_OnDeviceRemoved(IMMNotificationClient* This,
+ LPCWSTR pwstrDeviceId)
+{
+ dolog2("(%s)\n", __func__);
+ return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE dsound_OnDefaultDeviceChanged(IMMNotificationClient* This,
+ EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId)
+{
+ dolog2("(%s)\n", __func__);
+ return E_NOTIMPL;
+}
+
+static HRESULT STDMETHODCALLTYPE dsound_OnPropertyValueChanged(IMMNotificationClient* This,
+ LPCWSTR pwstrDeviceId, const PROPERTYKEY key)
+{
+ dolog2("(%s)\n", __func__);
+ return E_NOTIMPL;
+}
+
+static IMMNotificationClientVtbl dsound_dsound_vtbl = {
+ dsound_QueryInterface,
+ dsound_AddRef,
+ dsound_Release,
+ dsound_OnDeviceStateChanged,
+ dsound_OnDeviceAdded,
+ dsound_OnDeviceRemoved,
+ dsound_OnDefaultDeviceChanged,
+ dsound_OnPropertyValueChanged
+};
+
static DSoundConf glob_conf = {
.bufsize_in = 16384,
.bufsize_out = 16384,
HRESULT hr;
dsound *s = opaque;
+ qemu_mutex_lock(&s->th_mutex);
+ s->th_exit = true;
+ qemu_cond_signal(&s->th_cond);
+ qemu_mutex_unlock(&s->th_mutex);
+
+ qemu_thread_join(&s->th);
+ qemu_cond_destroy(&s->th_cond);
+ qemu_cond_destroy(&s->init_cond);
+ qemu_mutex_destroy(&s->th_mutex);
+ qemu_mutex_destroy(&s->init_mutex);
+
if (!s->dsound) {
g_free(s);
return;
g_free(s);
}
-static void *dsound_audio_init (void)
+/* Because a trial initializing in noti-handler makes some deadlock,
+ * the initialization of DirectSound with COM
+ * should be executed by one thread.
+ */
+static void *dsound_initialization_thread(void *opaque)
{
- int err;
HRESULT hr;
- dsound *s = g_malloc0(sizeof(dsound));
+ dsound *s = (dsound *)opaque;
+ HWVoiceOut *hwo;
+ HWVoiceIn *hwi;
+
+ dolog2("(%s) Starting. dsound(%p)\n", __func__, s);
- s->conf = glob_conf;
hr = CoInitialize (NULL);
if (FAILED (hr)) {
dsound_logerr (hr, "Could not initialize COM\n");
- g_free(s);
- return NULL;
+ goto CoFailed;
}
+ dolog2("(%s) CoInitialize succeeded\n", __func__);
+ /* For Playback */
hr = CoCreateInstance (
&CLSID_DirectSound,
NULL,
);
if (FAILED (hr)) {
dsound_logerr (hr, "Could not create DirectSound instance\n");
- g_free(s);
- return NULL;
+ goto CoFailed;
}
-
- hr = IDirectSound_Initialize (s->dsound, NULL);
- if (FAILED (hr)) {
- dsound_logerr (hr, "Could not initialize DirectSound\n");
-
- hr = IDirectSound_Release (s->dsound);
- if (FAILED (hr)) {
- dsound_logerr (hr, "Could not release DirectSound\n");
- }
- g_free(s);
- return NULL;
+ dolog2("(%s) CoCreateInstance succeeded. s->dsound(%p)\n", __func__, s->dsound);
+
+ /* try once at init time */
+ if (dsound_audio_out_init(s) < 0) {
+ dolog2("(%s) dsound_audio_out_init failed.\n", __func__);
+ s->out_init_success = false;
+ } else {
+ dolog2("(%s) dsound_audio_out_init success.\n", __func__);
+ s->out_init_success = true;
}
+ /* For Capture */
hr = CoCreateInstance (
&CLSID_DirectSoundCapture,
NULL,
);
if (FAILED (hr)) {
dsound_logerr (hr, "Could not create DirectSoundCapture instance\n");
+ goto CoFailed;
}
- else {
- hr = IDirectSoundCapture_Initialize (s->dsound_capture, NULL);
- if (FAILED (hr)) {
- dsound_logerr (hr, "Could not initialize DirectSoundCapture\n");
- hr = IDirectSoundCapture_Release (s->dsound_capture);
- if (FAILED (hr)) {
- dsound_logerr (hr, "Could not release DirectSoundCapture\n");
+ /* try once at init time */
+ if (dsound_audio_in_init(s) < 0) {
+ dolog2("(%s) dsound_audio_in_init failed.\n", __func__);
+ s->in_init_success = false;
+ } else {
+ dolog2("(%s) dsound_audio_in_init success.\n", __func__);
+ s->in_init_success = true;
+ }
+
+CoFailed:
+ qemu_mutex_lock(&s->init_mutex);
+ s->th_initialized = true;
+ qemu_cond_signal(&s->init_cond);
+ qemu_mutex_unlock(&s->init_mutex);
+
+ if (FAILED (hr)) {
+ dolog("(%s) terminating.\n", __func__);
+ return NULL;
+ }
+
+ /* event handling loop */
+ qemu_mutex_lock(&s->th_mutex);
+ while (1) {
+ qemu_cond_wait(&s->th_cond, &s->th_mutex);
+
+ if (s->th_exit) {
+ break;
+ }
+
+ switch (s->event) {
+ case OUT_UNPLUGGED:
+ hwo = (HWVoiceOut *)s->dso;
+ assert(hwo != NULL);
+ assert(s->dsound != NULL);
+
+ dsound_fini_out(hwo);
+ s->out_init_success = false;
+ dolog("(%s) handled OUT_UNPLUGGED.\n", __func__);
+ break;
+
+ case OUT_PLUGGED:
+ hwo = (HWVoiceOut *)s->dso;
+ assert(hwo != NULL);
+
+ if (dsound_audio_out_init(s) < 0) {
+ dolog("(%s) dsound_audio_out_init failed.\n", __func__);
+ s->out_init_success = false;
+ break;
}
- s->dsound_capture = NULL;
+ s->out_init_success = true;
+
+ /* After audio system init finished,
+ * pcm_ops.init_out is not called any more.
+ * So, we should update backend data here.
+ */
+ if (dsound_init_out(hwo, &s->settings, s) < 0 ) {
+ dolog2("(%s) dsound_init_out failed\n", __func__);
+ } else {
+ dolog2("(%s) dsound_init_out succeeded\n", __func__);
+ }
+ dolog("(%s) handled OUT_PLUGGED.\n", __func__);
+ break;
+
+ case IN_UNPLUGGED:
+ hwi = (HWVoiceIn *)s->dsi;
+ assert(hwi != NULL);
+ assert(s->dsound_capture != NULL);
+
+ dsound_fini_in(hwi);
+ s->in_init_success = false;
+
+ dolog("(%s) handled IN_UNPLUGGED\n", __func__);
+ break;
+
+ case IN_PLUGGED:
+ hwi = (HWVoiceIn *)s->dsi;
+ assert(hwi != NULL);
+
+ if (dsound_audio_in_init(s) < 0) {
+ dolog("(%s) dsound_audio_in_init failed.\n", __func__);
+ s->in_init_success = false;
+ break;
+ }
+ s->in_init_success = true;
+
+ /* Since, IN has same reason to out case */
+ if (dsound_init_in(hwi, &s->settings, s) < 0 ) {
+ dolog2("(%s) dsound_init_in failed\n", __func__);
+ } else {
+ dolog2("(%s) dsound_init_in succeeded\n", __func__);
+ }
+
+ dolog("(%s) handled IN_PLUGGED\n", __func__);
+ break;
+
+ default:
+ dolog("(%s) Unknown event\n", __func__);
+ break;
}
}
+ qemu_mutex_unlock(&s->th_mutex);
- err = dsound_open (s);
- if (err) {
- dsound_audio_fini (s);
+ return NULL;
+}
+
+static void dsound_create_thread(dsound *s)
+{
+ qemu_cond_init(&s->th_cond);
+ qemu_cond_init(&s->init_cond);
+ qemu_mutex_init(&s->th_mutex);
+ qemu_mutex_init(&s->init_mutex);
+
+ qemu_thread_create(&s->th,
+ "dsound-init-thread",
+ dsound_initialization_thread,
+ (void *)s,
+ QEMU_THREAD_JOINABLE);
+
+ dolog("(%s) Done\n", __func__);
+}
+
+static void *dsound_audio_init (void)
+{
+ HRESULT hr;
+ dsound *s = g_malloc0(sizeof(dsound));
+
+ s->conf = glob_conf;
+
+ hr = CoInitialize (NULL);
+ if (FAILED (hr)) {
+ dsound_logerr (hr, "Could not initialize COM\n");
+ g_free(s);
return NULL;
}
+ hr = CoCreateInstance (
+ &CLSID_MMDeviceEnumerator,
+ NULL,
+ CLSCTX_ALL,
+ &IID_IMMDeviceEnumerator,
+ (void **) &s->mmdev_enumerator
+ );
+ if (FAILED (hr)) {
+ dsound_logerr (hr, "Could not create MMDeviceEnumerator instance\n");
+ g_free(s);
+ return NULL;
+ }
+
+ s->noti_client.lpVtbl = &dsound_dsound_vtbl;
+ hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(s->mmdev_enumerator, &s->noti_client);
+ if (FAILED (hr)) {
+ dsound_logerr (hr, "Could not register endpoint notification callbacks\n");
+ g_free(s);
+ return NULL;
+ }
+ dolog("Registering endpoint notification callback succeeded.\n");
+
+ dsound_create_thread(s);
+
+ /* For serialization with init thread
+ * If audio.c invoks pcm_ops.init_out before the init thread finishes it's work,
+ * init_out callback works with undetermined status that is 'out_init_success'.
+ */
+ qemu_mutex_lock(&s->init_mutex);
+ while (!s->th_initialized) {
+ dolog2("(%s) Waiting for finishing initialization of thread\n", __func__);
+ qemu_cond_wait(&s->init_cond, &s->init_mutex);
+ }
+ qemu_mutex_unlock(&s->init_mutex);
+
+
+ dolog ("(%s) finished\n", __func__);
+
return s;
}