summaryrefslogtreecommitdiffstats
path: root/audio/out/ao_wasapi0.c
diff options
context:
space:
mode:
Diffstat (limited to 'audio/out/ao_wasapi0.c')
-rw-r--r--audio/out/ao_wasapi0.c330
1 files changed, 258 insertions, 72 deletions
diff --git a/audio/out/ao_wasapi0.c b/audio/out/ao_wasapi0.c
index 2690f83f53..5f7a3a7c76 100644
--- a/audio/out/ao_wasapi0.c
+++ b/audio/out/ao_wasapi0.c
@@ -59,6 +59,14 @@ DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName,
static const GUID local_KSDATAFORMAT_SUBTYPE_PCM = {
0x1, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
};
+static const GUID local_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
+ 0x3, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+};
+
+union WAVEFMT {
+ WAVEFORMATEX *ex;
+ WAVEFORMATEXTENSIBLE *extensible;
+};
typedef struct wasapi0_state {
HANDLE threadLoop;
@@ -67,6 +75,7 @@ typedef struct wasapi0_state {
int init_ret;
HANDLE init_done;
HANDLE fatal_error; /* signal to indicate unrecoverable error */
+ int share_mode;
/* Events */
HANDLE hPause;
@@ -110,6 +119,7 @@ typedef struct wasapi0_state {
DWORD taskIndex; /* AV task ID */
WAVEFORMATEXTENSIBLE format;
+ int opt_exclusive;
int opt_list;
char *opt_device;
@@ -239,89 +249,243 @@ static void set_format(WAVEFORMATEXTENSIBLE *wformat, WORD bytepersample,
wformat->Format.wBitsPerSample = bytepersample * 8;
wformat->Format.cbSize =
22; /* must be at least 22 for WAVE_FORMAT_EXTENSIBLE */
- wformat->SubFormat = local_KSDATAFORMAT_SUBTYPE_PCM;
+ if (bytepersample == 4)
+ wformat->SubFormat = local_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+ else
+ wformat->SubFormat = local_KSDATAFORMAT_SUBTYPE_PCM;
wformat->Samples.wValidBitsPerSample = wformat->Format.wBitsPerSample;
wformat->dwChannelMask = chanmask;
}
-static HRESULT check_support(struct wasapi0_state *state,
- const WAVEFORMATEXTENSIBLE *wformat)
+static int format_set_bits(int old_format, int bits, int fp) {
+ int format = old_format;
+ format &= (~AF_FORMAT_BITS_MASK) & (~AF_FORMAT_POINT_MASK) & (~AF_FORMAT_SIGN_MASK);
+ format |= AF_FORMAT_SI;
+
+ switch (bits) {
+ case 32:
+ format |= AF_FORMAT_32BIT;
+ break;
+ case 24:
+ format |= AF_FORMAT_24BIT;
+ break;
+ case 16:
+ format |= AF_FORMAT_16BIT;
+ break;
+ default:
+ abort(); // (should be) unreachable
+ }
+
+ if (fp) {
+ format |= AF_FORMAT_F;
+ } else {
+ format |= AF_FORMAT_I;
+ }
+
+ return format;
+}
+
+static int set_ao_format(struct wasapi0_state *state,
+ struct ao *const ao,
+ WAVEFORMATEXTENSIBLE wformat) {
+ // .Data1 == 1 is PCM, .Data1 == 3 is IEEE_FLOAT
+ int format = format_set_bits(ao->format,
+ wformat.Format.wBitsPerSample, wformat.SubFormat.Data1 == 3);
+
+ if (wformat.SubFormat.Data1 != 1 && wformat.SubFormat.Data1 != 3) {
+ mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: unknown SubFormat %d\n",
+ wformat.SubFormat.Data1);
+ return 0;
+ }
+
+ ao->samplerate = wformat.Format.nSamplesPerSec;
+ ao->bps = wformat.Format.nAvgBytesPerSec;
+ ao->format = format;
+
+ if (ao->channels.num != wformat.Format.nChannels) {
+ mp_chmap_from_channels(&ao->channels, wformat.Format.nChannels);
+ }
+
+ state->format = wformat;
+ return 1;
+}
+
+static int try_format(struct wasapi0_state *state,
+ struct ao *const ao,
+ int bits, int samplerate,
+ const struct mp_chmap channels)
{
+ WAVEFORMATEXTENSIBLE wformat;
+ set_format(&wformat, bits / 8, samplerate, channels.num, mp_chmap_to_waveext(&channels));
+
+ int af_format = format_set_bits(ao->format, bits, bits == 32);
+
+ EnterCriticalSection(&state->print_lock);
+ mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: trying %dch %s @ %dhz\n",
+ channels.num, af_fmt2str_short(af_format), samplerate);
+ LeaveCriticalSection(&state->print_lock);
+
+ union WAVEFMT u;
+ u.extensible = &wformat;
+
+ WAVEFORMATEX *closestMatch;
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
- AUDCLNT_SHAREMODE_EXCLUSIVE,
- &(wformat->Format), NULL);
- if (hr != S_OK) {
- EnterCriticalSection(&state->print_lock);
- mp_msg(
- MSGT_AO, MSGL_ERR,
- "IAudioClient::IsFormatSupported failed with %s (%d at %"PRId32"Hz %dchannels, channelmask = %"PRIx32")\n",
- explain_err(
- hr), wformat->Format.wBitsPerSample,
- wformat->Format.nSamplesPerSec,
- wformat->Format.nChannels, wformat->dwChannelMask);
- LeaveCriticalSection(&state->print_lock);
+ state->share_mode,
+ u.ex, &closestMatch);
+
+ if (closestMatch) {
+ if (closestMatch->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
+ u.ex = closestMatch;
+ wformat = *u.extensible;
+ } else {
+ wformat.Format = *closestMatch;
+ }
+
+ CoTaskMemFree(closestMatch);
}
- return hr;
+
+ if (hr == S_FALSE) {
+ if (set_ao_format(state, ao, wformat)) {
+ EnterCriticalSection(&state->print_lock);
+ mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: accepted as %dch %s @ %dhz\n",
+ ao->channels.num, af_fmt2str_short(ao->format), ao->samplerate);
+ LeaveCriticalSection(&state->print_lock);
+
+ return 1;
+ }
+ } if (hr == S_OK || (!state->opt_exclusive && hr == AUDCLNT_E_UNSUPPORTED_FORMAT)) {
+ // AUDCLNT_E_UNSUPPORTED_FORMAT here means "works in shared, doesn't in exclusive"
+ if (set_ao_format(state, ao, wformat)) {
+ EnterCriticalSection(&state->print_lock);
+ mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: %dch %s @ %dhz accepted\n",
+ ao->channels.num, af_fmt2str_short(af_format), samplerate);
+ LeaveCriticalSection(&state->print_lock);
+ return 1;
+ }
+ }
+ return 0;
}
-static int enum_formats(struct ao *const ao)
+static int try_mix_format(struct wasapi0_state *state,
+ struct ao *const ao)
+{
+ WAVEFORMATEX *deviceFormat = NULL;
+ WAVEFORMATEX *closestMatch = NULL;
+ int ret = 0;
+
+ HRESULT hr = IAudioClient_GetMixFormat(state->pAudioClient, &deviceFormat);
+ EXIT_ON_ERROR(hr)
+
+ union WAVEFMT u;
+ u.ex = deviceFormat;
+ WAVEFORMATEXTENSIBLE wformat = *u.extensible;
+
+ ret = try_format(state, ao, wformat.Format.wBitsPerSample,
+ wformat.Format.nSamplesPerSec, ao->channels);
+
+exit_label:
+ SAFE_RELEASE(deviceFormat, CoTaskMemFree(deviceFormat));
+ SAFE_RELEASE(closestMatch, CoTaskMemFree(closestMatch));
+ return ret;
+}
+
+static int find_formats(struct ao *const ao)
{
- WAVEFORMATEXTENSIBLE wformat;
- DWORD chanmask;
- int bytes_per_sample;
struct wasapi0_state *state = (struct wasapi0_state *)ao->priv;
+ /* See if the format works as-is */
+ int bits = af_fmt2bits(ao->format);
+ /* don't try 8bits -- there are various 8bit modes other than PCM (*-law et al);
+ let's just stick to PCM or float here. */
+ if (bits == 8) {
+ bits = 16;
+ } else if (try_format(state, ao, bits, ao->samplerate, ao->channels)) {
+ return 0;
+ }
+ if (!state->opt_exclusive) {
+ /* shared mode, we can use the system default mix format. */
+ if (try_mix_format(state, ao)) {
+ return 0;
+ }
+
+ EnterCriticalSection(&state->print_lock);
+ mp_msg(MSGT_AO, MSGL_WARN, "ao-wasapi: couldn't use default mix format!\n");
+ LeaveCriticalSection(&state->print_lock);
+ }
+
+ /* Exclusive mode, we have to guess. */
+
/* as far as testing shows, only PCM 16/24LE (44100Hz - 192kHz) is supported
* Tested on Realtek High Definition Audio, (Realtek Semiconductor Corp. 6.0.1.6312)
* Drivers dated 2/18/2011
*/
- switch (ao->format) {
- case AF_FORMAT_S24_LE:
- bytes_per_sample = 3;
- break;
- case AF_FORMAT_S16_LE:
- bytes_per_sample = 2;
- break;
- default:
- bytes_per_sample = 0; /* unsupported, fail it */
+
+ /* try float first for non-16bit audio */
+ if (bits != 16) {
+ bits = 32;
}
- chanmask = mp_chmap_to_waveext(&ao->channels);
- set_format(&wformat, bytes_per_sample, ao->samplerate, ao->channels.num, chanmask);
- /* See if chosen format works */
- if (check_support(state, &wformat) == S_OK) {
- state->format = wformat;
- ao->bps = wformat.Format.nAvgBytesPerSec;
- return 0;
- } else { /* Try default, 16bit @ 44.1kHz */
- EnterCriticalSection(&state->print_lock);
- mp_msg(MSGT_AO, MSGL_WARN, "Trying 16LE at 44100Hz\n");
- LeaveCriticalSection(&state->print_lock);
- set_format(&wformat, 2, 44100, ao->channels.num, chanmask);
- if (check_support(state, &wformat) == S_OK) {
- ao->samplerate = wformat.Format.nSamplesPerSec;
- ao->bps = wformat.Format.nAvgBytesPerSec;
- ao->format = AF_FORMAT_S16_LE;
- state->format = wformat;
- return 0;
- } else { /* Poor quality hardware? Try stereo mode */
- EnterCriticalSection(&state->print_lock);
- mp_msg(MSGT_AO, MSGL_WARN, "Trying Stereo\n");
- LeaveCriticalSection(&state->print_lock);
- mp_chmap_from_channels(&ao->channels, 2);
- wformat.Format.nChannels = 2;
- set_format(&wformat, 2, 44100, ao->channels.num,
- mp_chmap_to_waveext(&ao->channels));
- if (check_support(state, &wformat) == S_OK) {
- ao->samplerate = wformat.Format.nSamplesPerSec;
- ao->bps = wformat.Format.nAvgBytesPerSec;
- ao->format = AF_FORMAT_S16_LE;
- state->format = wformat;
+ int start_bits = bits;
+ while (1) { // not infinite -- returns at bottom
+ for (; bits > 8; bits -= 8) {
+ int samplerate = ao->samplerate;
+ if (try_format(state, ao, bits, samplerate, ao->channels)) {
return 0;
}
+
+ // make samplerate fit in [44100 192000]
+ // we check for samplerate > 96k so that we can upsample instead of downsampling later
+ if (samplerate < 44100 || samplerate > 96000) {
+ if (samplerate < 44100)
+ samplerate = 44100;
+ if (samplerate > 96000)
+ samplerate = 192000;
+
+ if (try_format(state, ao, bits, samplerate, ao->channels)) {
+ return 0;
+ }
+ }
+
+ // try bounding to 96kHz
+ if (samplerate > 48000) {
+ samplerate = 96000;
+ if (try_format(state, ao, bits, samplerate, ao->channels)) {
+ return 0;
+ }
+ }
+
+ // try bounding to 48kHz
+ if (samplerate > 44100) {
+ samplerate = 48000;
+ if (try_format(state, ao, bits, samplerate, ao->channels)) {
+ return 0;
+ }
+ }
+
+ /* How bad is this? try 44100hz, but only on 16bit */
+ if (bits == 16 && samplerate != 44100) {
+ samplerate = 44100;
+
+ if (try_format(state, ao, bits, samplerate, ao->channels)) {
+ return 0;
+ }
+ }
+ }
+ if (ao->channels.num > 6) {
+ /* Maybe this is 5.1 hardware with no support for more. */
+ bits = start_bits;
+ mp_chmap_from_channels(&ao->channels, 6);
+ } else if (ao->channels.num != 2) {
+ /* Poor quality hardware? Try stereo mode, go through the list again. */
+ bits = start_bits;
+ mp_chmap_from_channels(&ao->channels, 2);
+ } else {
+ EnterCriticalSection(&state->print_lock);
+ mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: couldn't find acceptable audio format!\n");
+ LeaveCriticalSection(&state->print_lock);
+ return -1;
}
}
- return -1;
}
static int fix_format(struct wasapi0_state *state)
@@ -337,7 +501,7 @@ static int fix_format(struct wasapi0_state *state)
&state->minRequestedDuration);
reinit:
hr = IAudioClient_Initialize(state->pAudioClient,
- AUDCLNT_SHAREMODE_EXCLUSIVE,
+ state->share_mode,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
state->defaultRequestedDuration,
state->defaultRequestedDuration,
@@ -377,7 +541,8 @@ reinit:
hr = IAudioClient_GetBufferSize(state->pAudioClient,
&state->bufferFrameCount);
EXIT_ON_ERROR(hr)
- state->buffer_block_size = state->format.Format.nBlockAlign *
+ state->buffer_block_size = state->format.Format.nChannels *
+ state->format.Format.wBitsPerSample / 8 *
state->bufferFrameCount;
state->hTask =
state->VistaBlob.pAvSetMmThreadCharacteristicsW(L"Pro Audio", &state->taskIndex);
@@ -599,7 +764,7 @@ static int thread_init(struct ao *ao)
IAudioEndpointVolume_QueryHardwareSupport(state->pEndpointVolume,
&state->vol_hw_support);
- state->init_ret = enum_formats(ao); /* Probe support formats */
+ state->init_ret = find_formats(ao); /* Probe support formats */
if (state->init_ret)
goto exit_label;
if (!fix_format(state)) { /* now that we're sure what format to use */
@@ -630,27 +795,41 @@ static void thread_feed(wasapi0_state *state,int force_feed)
{
BYTE *pData;
int buffer_size;
- HRESULT hr = IAudioRenderClient_GetBuffer(state->pRenderClient,
- state->bufferFrameCount, &pData);
+ HRESULT hr;
+
+ UINT32 frame_count = state->bufferFrameCount;
+ UINT32 client_buffer = state->buffer_block_size;
+
+ if (state->share_mode == AUDCLNT_SHAREMODE_SHARED) {
+ UINT32 padding = 0;
+ hr = IAudioClient_GetCurrentPadding(state->pAudioClient, &padding);
+ EXIT_ON_ERROR(hr)
+
+ frame_count -= padding;
+ client_buffer = state->format.Format.nBlockAlign * frame_count;
+ }
+
+ hr = IAudioRenderClient_GetBuffer(state->pRenderClient,
+ frame_count, &pData);
EXIT_ON_ERROR(hr)
buffer_size = mp_ring_buffered(state->ringbuff);
- if( buffer_size > state->buffer_block_size) { /* OK to copy! */
+ if(buffer_size > client_buffer) { /* OK to copy! */
mp_ring_read(state->ringbuff, (unsigned char *)pData,
- state->buffer_block_size);
+ client_buffer);
} else if(force_feed) {
/* should be smaller than buffer block size by now */
- memset(pData,0,state->buffer_block_size);
- mp_ring_read(state->ringbuff, (unsigned char *)pData, buffer_size);
+ memset(pData,0,client_buffer);
+ mp_ring_read(state->ringbuff, (unsigned char *)pData, client_buffer);
} else {
/* buffer underrun?! abort */
hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient,
- state->bufferFrameCount,
+ frame_count,
AUDCLNT_BUFFERFLAGS_SILENT);
EXIT_ON_ERROR(hr)
return;
}
hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient,
- state->bufferFrameCount, 0);
+ frame_count, 0);
EXIT_ON_ERROR(hr)
return;
exit_label:
@@ -843,6 +1022,7 @@ static void uninit(struct ao *ao, bool immed)
#define OPT_BASE_STRUCT wasapi0_state
const struct m_sub_options ao_wasapi0_conf = {
.opts = (m_option_t[]){
+ OPT_FLAG("exclusive", opt_exclusive, 0),
OPT_FLAG("list", opt_list, 0),
OPT_STRING("device", opt_device, 0),
{NULL},
@@ -873,6 +1053,12 @@ static int init(struct ao *ao, char *params)
return -1;
}
+ if (state->opt_exclusive) {
+ state->share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
+ } else {
+ state->share_mode = AUDCLNT_SHAREMODE_SHARED;
+ }
+
state->init_done = CreateEventW(NULL, FALSE, FALSE, NULL);
state->hPlay = CreateEventW(NULL, FALSE, FALSE, NULL); /* kick start audio feed */
state->hPause = CreateEventW(NULL, FALSE, FALSE, NULL);