You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
330 lines
8.3 KiB
330 lines
8.3 KiB
/*****************************************************************************
|
|
* mmdevice.c : Windows Multimedia Device API audio output plugin for VLC
|
|
*****************************************************************************
|
|
* Copyright (C) 2012 Rémi Denis-Courmont
|
|
*
|
|
* This program 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 program 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 program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#define INITGUID
|
|
#define COBJMACROS
|
|
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <audiopolicy.h>
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_plugin.h>
|
|
#include <vlc_aout.h>
|
|
#include <vlc_modules.h>
|
|
#include "audio_output/mmdevice.h"
|
|
|
|
DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
|
|
0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
|
|
|
|
static void EnterMTA(void)
|
|
{
|
|
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
|
if (unlikely(FAILED(hr)))
|
|
abort();
|
|
}
|
|
|
|
static void LeaveMTA(void)
|
|
{
|
|
CoUninitialize();
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
aout_stream_t *stream; /**< Underlying audio output stream */
|
|
module_t *module;
|
|
IAudioClient *client;
|
|
} aout_sys_t;
|
|
|
|
static int vlc_FromHR(audio_output_t *aout, HRESULT hr)
|
|
{
|
|
aout_sys_t* sys = aout->sys;
|
|
/* Select the default device (and restart) on unplug */
|
|
if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED ||
|
|
hr == AUDCLNT_E_RESOURCES_INVALIDATED))
|
|
{
|
|
sys->client = NULL;
|
|
}
|
|
return SUCCEEDED(hr) ? 0 : -1;
|
|
}
|
|
|
|
static int VolumeSet(audio_output_t *aout, float vol)
|
|
{
|
|
aout_sys_t *sys = aout->sys;
|
|
if( unlikely( sys->client == NULL ) )
|
|
return VLC_EGENERIC;
|
|
HRESULT hr;
|
|
ISimpleAudioVolume *pc_AudioVolume = NULL;
|
|
float gain = 1.f;
|
|
|
|
vol = vol * vol * vol; /* ISimpleAudioVolume is tapered linearly. */
|
|
|
|
if (vol > 1.f)
|
|
{
|
|
gain = vol;
|
|
vol = 1.f;
|
|
}
|
|
|
|
aout_GainRequest(aout, gain);
|
|
|
|
hr = IAudioClient_GetService(sys->client, &IID_ISimpleAudioVolume, &pc_AudioVolume);
|
|
if (FAILED(hr))
|
|
{
|
|
msg_Err(aout, "cannot get volume service (error 0x%lx)", hr);
|
|
goto done;
|
|
}
|
|
|
|
hr = ISimpleAudioVolume_SetMasterVolume(pc_AudioVolume, vol, NULL);
|
|
if (FAILED(hr))
|
|
{
|
|
msg_Err(aout, "cannot set volume (error 0x%lx)", hr);
|
|
goto done;
|
|
}
|
|
|
|
done:
|
|
ISimpleAudioVolume_Release(pc_AudioVolume);
|
|
|
|
return SUCCEEDED(hr) ? 0 : -1;
|
|
}
|
|
|
|
static int MuteSet(audio_output_t *aout, bool mute)
|
|
{
|
|
aout_sys_t *sys = aout->sys;
|
|
if( unlikely( sys->client == NULL ) )
|
|
return VLC_EGENERIC;
|
|
HRESULT hr;
|
|
ISimpleAudioVolume *pc_AudioVolume = NULL;
|
|
|
|
hr = IAudioClient_GetService(sys->client, &IID_ISimpleAudioVolume, &pc_AudioVolume);
|
|
if (FAILED(hr))
|
|
{
|
|
msg_Err(aout, "cannot get volume service (error 0x%lx)", hr);
|
|
goto done;
|
|
}
|
|
|
|
hr = ISimpleAudioVolume_SetMute(pc_AudioVolume, mute, NULL);
|
|
if (FAILED(hr))
|
|
{
|
|
msg_Err(aout, "cannot set mute (error 0x%lx)", hr);
|
|
goto done;
|
|
}
|
|
|
|
done:
|
|
ISimpleAudioVolume_Release(pc_AudioVolume);
|
|
|
|
return SUCCEEDED(hr) ? 0 : -1;
|
|
}
|
|
|
|
static int TimeGet(audio_output_t *aout, vlc_tick_t *restrict delay)
|
|
{
|
|
aout_sys_t *sys = aout->sys;
|
|
if( unlikely( sys->client == NULL ) )
|
|
return VLC_EGENERIC;
|
|
HRESULT hr;
|
|
|
|
EnterMTA();
|
|
hr = aout_stream_TimeGet(sys->stream, delay);
|
|
LeaveMTA();
|
|
|
|
return SUCCEEDED(hr) ? 0 : -1;
|
|
}
|
|
|
|
static void Play(audio_output_t *aout, block_t *block, vlc_tick_t date)
|
|
{
|
|
aout_sys_t *sys = aout->sys;
|
|
if( unlikely( sys->client == NULL ) )
|
|
return;
|
|
|
|
EnterMTA();
|
|
HRESULT hr = aout_stream_Play(sys->stream, block);
|
|
LeaveMTA();
|
|
|
|
vlc_FromHR(aout, hr);
|
|
(void) date;
|
|
}
|
|
|
|
static void Pause(audio_output_t *aout, bool paused, vlc_tick_t date)
|
|
{
|
|
aout_sys_t *sys = aout->sys;
|
|
if( unlikely( sys->client == NULL ) )
|
|
return;
|
|
|
|
EnterMTA();
|
|
HRESULT hr = aout_stream_Pause(sys->stream, paused);
|
|
LeaveMTA();
|
|
|
|
(void) date;
|
|
vlc_FromHR(aout, hr);
|
|
}
|
|
|
|
static void Flush(audio_output_t *aout, bool wait)
|
|
{
|
|
aout_sys_t *sys = aout->sys;
|
|
if( unlikely( sys->client == NULL ) )
|
|
return;
|
|
|
|
EnterMTA();
|
|
HRESULT hr = aout_stream_Flush(sys->stream, wait);
|
|
LeaveMTA();
|
|
|
|
vlc_FromHR(aout, hr);
|
|
}
|
|
|
|
static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms,
|
|
void **restrict pv)
|
|
{
|
|
IAudioClient *client = opaque;
|
|
|
|
if (!IsEqualIID(iid, &IID_IAudioClient))
|
|
return E_NOINTERFACE;
|
|
if (actparms != NULL || client == NULL )
|
|
return E_INVALIDARG;
|
|
|
|
IAudioClient_AddRef(client);
|
|
*pv = opaque;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static int aout_stream_Start(void *func, va_list ap)
|
|
{
|
|
aout_stream_start_t start = func;
|
|
aout_stream_t *s = va_arg(ap, aout_stream_t *);
|
|
audio_sample_format_t *fmt = va_arg(ap, audio_sample_format_t *);
|
|
HRESULT *hr = va_arg(ap, HRESULT *);
|
|
|
|
(void) forced;
|
|
*hr = start(s, fmt, &GUID_VLC_AUD_OUT);
|
|
return SUCCEEDED(*hr) ? VLC_SUCCESS : VLC_EGENERIC;
|
|
}
|
|
|
|
static void aout_stream_Stop(void *func, va_list ap)
|
|
{
|
|
aout_stream_stop_t stop = func;
|
|
aout_stream_t *s = va_arg(ap, aout_stream_t *);
|
|
|
|
stop(s);
|
|
}
|
|
|
|
static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
|
|
{
|
|
aout_sys_t *sys = aout->sys;
|
|
HRESULT hr;
|
|
|
|
aout_stream_t *s = vlc_object_create(aout, sizeof (*s));
|
|
if (unlikely(s == NULL))
|
|
return -1;
|
|
|
|
s->owner.device = sys->client;
|
|
s->owner.activate = ActivateDevice;
|
|
|
|
EnterMTA();
|
|
sys->module = vlc_module_load(s, "aout stream", NULL, false,
|
|
aout_stream_Start, s, fmt, &hr);
|
|
LeaveMTA();
|
|
|
|
if (sys->module == NULL)
|
|
{
|
|
vlc_object_release(s);
|
|
return -1;
|
|
}
|
|
|
|
assert (sys->stream == NULL);
|
|
sys->stream = s;
|
|
return 0;
|
|
}
|
|
|
|
static void Stop(audio_output_t *aout)
|
|
{
|
|
aout_sys_t *sys = aout->sys;
|
|
|
|
assert (sys->stream != NULL);
|
|
|
|
EnterMTA();
|
|
vlc_module_unload(sys->module, aout_stream_Stop, sys->stream);
|
|
LeaveMTA();
|
|
|
|
vlc_object_release(sys->stream);
|
|
sys->stream = NULL;
|
|
}
|
|
|
|
static int DeviceSelect(audio_output_t *aout, const char* psz_device)
|
|
{
|
|
if( psz_device == NULL )
|
|
return VLC_EGENERIC;
|
|
char* psz_end;
|
|
aout_sys_t* sys = aout->sys;
|
|
intptr_t ptr = strtoll( psz_device, &psz_end, 16 );
|
|
if ( *psz_end != 0 )
|
|
return VLC_EGENERIC;
|
|
if (sys->client == (IAudioClient*)ptr)
|
|
return VLC_SUCCESS;
|
|
sys->client = (IAudioClient*)ptr;
|
|
var_SetAddress( vlc_object_parent(aout), "winstore-client", sys->client );
|
|
aout_RestartRequest( aout, AOUT_RESTART_OUTPUT );
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static int Open(vlc_object_t *obj)
|
|
{
|
|
audio_output_t *aout = (audio_output_t *)obj;
|
|
|
|
aout_sys_t *sys = malloc(sizeof (*sys));
|
|
if (unlikely(sys == NULL))
|
|
return VLC_ENOMEM;
|
|
|
|
aout->sys = sys;
|
|
sys->stream = NULL;
|
|
sys->client = var_CreateGetAddress( vlc_object_parent(aout), "winstore-client" );
|
|
if (sys->client != NULL)
|
|
msg_Dbg( aout, "Reusing previous client: %p", sys->client );
|
|
aout->start = Start;
|
|
aout->stop = Stop;
|
|
aout->time_get = TimeGet;
|
|
aout->volume_set = VolumeSet;
|
|
aout->mute_set = MuteSet;
|
|
aout->play = Play;
|
|
aout->pause = Pause;
|
|
aout->flush = Flush;
|
|
aout->device_select = DeviceSelect;
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static void Close(vlc_object_t *obj)
|
|
{
|
|
audio_output_t *aout = (audio_output_t *)obj;
|
|
aout_sys_t *sys = aout->sys;
|
|
|
|
free(sys);
|
|
}
|
|
|
|
vlc_module_begin()
|
|
set_shortname("winstore")
|
|
set_description("Windows Store audio output")
|
|
set_capability("audio output", 0)
|
|
set_category(CAT_AUDIO)
|
|
set_subcategory(SUBCAT_AUDIO_AOUT)
|
|
add_shortcut("wasapi")
|
|
set_callbacks(Open, Close)
|
|
vlc_module_end()
|
|
|