mirror of
https://github.com/reactos/wine.git
synced 2024-11-26 21:20:25 +00:00
f27d88e16f
Allows pulseaudio to use the idsdriver interface, making it almost as fast as using native pulseaudio calls for as far as directsound is concerned. Prevents the creation of separate winmm feeder threads.
948 lines
29 KiB
C
948 lines
29 KiB
C
/*
|
|
* Sample Wine Driver for Advanced Linux Sound System (ALSA)
|
|
* Based on version <final> of the ALSA API
|
|
*
|
|
* Copyright 2002 Eric Pouech
|
|
* 2002 Marco Pietrobono
|
|
* 2003 Christian Costa : WaveIn support
|
|
* 2006-2007 Maarten Lankhorst
|
|
*
|
|
* This library 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 library 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 library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
/*======================================================================*
|
|
* Low level dsound output implementation *
|
|
*======================================================================*/
|
|
|
|
#include "config.h"
|
|
#include "wine/port.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <fcntl.h>
|
|
#ifdef HAVE_SYS_IOCTL_H
|
|
# include <sys/ioctl.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_MMAN_H
|
|
# include <sys/mman.h>
|
|
#endif
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
#include "wingdi.h"
|
|
#include "winerror.h"
|
|
#include "winuser.h"
|
|
#include "mmddk.h"
|
|
|
|
#include "alsa.h"
|
|
#include "wine/library.h"
|
|
#include "wine/unicode.h"
|
|
#include "wine/debug.h"
|
|
|
|
#ifdef HAVE_ALSA
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(dsalsa);
|
|
|
|
typedef struct IDsDriverImpl IDsDriverImpl;
|
|
typedef struct IDsDriverBufferImpl IDsDriverBufferImpl;
|
|
|
|
struct IDsDriverImpl
|
|
{
|
|
/* IUnknown fields */
|
|
const IDsDriverVtbl *lpVtbl;
|
|
LONG ref;
|
|
|
|
/* IDsDriverImpl fields */
|
|
IDsDriverBufferImpl* primary;
|
|
UINT wDevID;
|
|
};
|
|
|
|
struct IDsDriverBufferImpl
|
|
{
|
|
const IDsDriverBufferVtbl *lpVtbl;
|
|
LONG ref;
|
|
IDsDriverImpl* drv;
|
|
|
|
CRITICAL_SECTION pcm_crst;
|
|
BYTE *mmap_buffer;
|
|
DWORD mmap_buflen_bytes;
|
|
BOOL mmap;
|
|
|
|
snd_pcm_t *pcm;
|
|
snd_pcm_hw_params_t *hw_params;
|
|
snd_pcm_sw_params_t *sw_params;
|
|
snd_pcm_uframes_t mmap_buflen_frames, mmap_pos, mmap_commitahead;
|
|
};
|
|
|
|
/** Fill buffers, for starting and stopping
|
|
* Alsa won't start playing until everything is filled up
|
|
* This also updates mmap_pos
|
|
*
|
|
* Returns: Amount of periods in use so snd_pcm_avail_update
|
|
* doesn't have to be called up to 4x in GetPosition()
|
|
*/
|
|
static snd_pcm_uframes_t CommitAll(IDsDriverBufferImpl *This)
|
|
{
|
|
const snd_pcm_channel_area_t *areas;
|
|
snd_pcm_sframes_t used;
|
|
const snd_pcm_uframes_t commitahead = This->mmap_commitahead;
|
|
|
|
used = This->mmap_buflen_frames - snd_pcm_avail_update(This->pcm);
|
|
if (used < 0) used = 0;
|
|
TRACE("%p needs to commit to %lu, used: %ld\n", This, commitahead, used);
|
|
if (used < commitahead)
|
|
{
|
|
snd_pcm_uframes_t done, putin = commitahead - used;
|
|
if (This->mmap)
|
|
{
|
|
snd_pcm_mmap_begin(This->pcm, &areas, &This->mmap_pos, &putin);
|
|
done = snd_pcm_mmap_commit(This->pcm, This->mmap_pos, putin);
|
|
}
|
|
else
|
|
{
|
|
if (putin + This->mmap_pos > This->mmap_buflen_frames)
|
|
putin = This->mmap_buflen_frames - This->mmap_pos;
|
|
done = putin;
|
|
snd_pcm_writei(This->pcm, This->mmap_buffer + snd_pcm_frames_to_bytes(This->pcm, This->mmap_pos), putin);
|
|
}
|
|
This->mmap_pos += done;
|
|
used += done;
|
|
putin = commitahead - used;
|
|
|
|
if (This->mmap_pos == This->mmap_buflen_frames && (snd_pcm_sframes_t)putin > 0)
|
|
{
|
|
if (This->mmap)
|
|
{
|
|
snd_pcm_mmap_begin(This->pcm, &areas, &This->mmap_pos, &putin);
|
|
done = snd_pcm_mmap_commit(This->pcm, This->mmap_pos, putin);
|
|
This->mmap_pos += done;
|
|
}
|
|
else
|
|
{
|
|
snd_pcm_writei(This->pcm, This->mmap_buffer, putin);
|
|
This->mmap_pos = done = putin;
|
|
}
|
|
used += done;
|
|
}
|
|
}
|
|
|
|
if (This->mmap_pos == This->mmap_buflen_frames)
|
|
This->mmap_pos = 0;
|
|
|
|
return used;
|
|
}
|
|
|
|
static void CheckXRUN(IDsDriverBufferImpl* This)
|
|
{
|
|
snd_pcm_state_t state = snd_pcm_state(This->pcm);
|
|
snd_pcm_sframes_t delay;
|
|
int err;
|
|
|
|
snd_pcm_hwsync(This->pcm);
|
|
snd_pcm_delay(This->pcm, &delay);
|
|
if ( state == SND_PCM_STATE_XRUN )
|
|
{
|
|
err = snd_pcm_prepare(This->pcm);
|
|
CommitAll(This);
|
|
snd_pcm_start(This->pcm);
|
|
WARN("xrun occurred\n");
|
|
if ( err < 0 )
|
|
ERR("recovery from xrun failed, prepare failed: %s\n", snd_strerror(err));
|
|
}
|
|
else if ( state == SND_PCM_STATE_SUSPENDED )
|
|
{
|
|
int err = snd_pcm_resume(This->pcm);
|
|
TRACE("recovery from suspension occurred\n");
|
|
if (err < 0 && err != -EAGAIN){
|
|
err = snd_pcm_prepare(This->pcm);
|
|
if (err < 0)
|
|
ERR("recovery from suspend failed, prepare failed: %s\n", snd_strerror(err));
|
|
}
|
|
} else if ( state != SND_PCM_STATE_RUNNING ) {
|
|
FIXME("Unhandled state: %d\n", state);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Allocate the memory-mapped buffer for direct sound, and set up the
|
|
* callback.
|
|
*/
|
|
static int DSDB_CreateMMAP(IDsDriverBufferImpl* pdbi)
|
|
{
|
|
snd_pcm_t *pcm = pdbi->pcm;
|
|
snd_pcm_format_t format;
|
|
snd_pcm_uframes_t frames, ofs, avail, psize, boundary;
|
|
unsigned int channels, bits_per_sample, bits_per_frame;
|
|
int err, mmap_mode;
|
|
const snd_pcm_channel_area_t *areas;
|
|
snd_pcm_hw_params_t *hw_params = pdbi->hw_params;
|
|
snd_pcm_sw_params_t *sw_params = pdbi->sw_params;
|
|
void *buf;
|
|
|
|
mmap_mode = snd_pcm_type(pcm);
|
|
|
|
if (mmap_mode == SND_PCM_TYPE_HW)
|
|
TRACE("mmap'd buffer is a direct hardware buffer.\n");
|
|
else if (mmap_mode == SND_PCM_TYPE_DMIX)
|
|
TRACE("mmap'd buffer is an ALSA dmix buffer\n");
|
|
else
|
|
TRACE("mmap'd buffer is an ALSA type %d buffer\n", mmap_mode);
|
|
|
|
err = snd_pcm_hw_params_get_period_size(hw_params, &psize, NULL);
|
|
|
|
err = snd_pcm_hw_params_get_format(hw_params, &format);
|
|
err = snd_pcm_hw_params_get_buffer_size(hw_params, &frames);
|
|
err = snd_pcm_hw_params_get_channels(hw_params, &channels);
|
|
bits_per_sample = snd_pcm_format_physical_width(format);
|
|
bits_per_frame = bits_per_sample * channels;
|
|
|
|
if (TRACE_ON(dsalsa))
|
|
ALSA_TraceParameters(hw_params, NULL, FALSE);
|
|
|
|
TRACE("format=%s frames=%ld channels=%d bits_per_sample=%d bits_per_frame=%d\n",
|
|
snd_pcm_format_name(format), frames, channels, bits_per_sample, bits_per_frame);
|
|
|
|
pdbi->mmap_buflen_frames = frames;
|
|
pdbi->mmap_buflen_bytes = snd_pcm_frames_to_bytes( pcm, frames );
|
|
|
|
snd_pcm_sw_params_current(pcm, sw_params);
|
|
snd_pcm_sw_params_set_start_threshold(pcm, sw_params, 0);
|
|
snd_pcm_sw_params_get_boundary(sw_params, &boundary);
|
|
snd_pcm_sw_params_set_stop_threshold(pcm, sw_params, boundary);
|
|
snd_pcm_sw_params_set_silence_threshold(pcm, sw_params, boundary);
|
|
snd_pcm_sw_params_set_silence_size(pcm, sw_params, 0);
|
|
snd_pcm_sw_params_set_avail_min(pcm, sw_params, 0);
|
|
err = snd_pcm_sw_params(pcm, sw_params);
|
|
|
|
avail = snd_pcm_avail_update(pcm);
|
|
if ((snd_pcm_sframes_t)avail < 0)
|
|
{
|
|
ERR("No buffer is available: %s.\n", snd_strerror(avail));
|
|
return DSERR_GENERIC;
|
|
}
|
|
|
|
if (!pdbi->mmap)
|
|
{
|
|
buf = pdbi->mmap_buffer = HeapAlloc(GetProcessHeap(), 0, pdbi->mmap_buflen_bytes);
|
|
if (!buf)
|
|
return DSERR_OUTOFMEMORY;
|
|
|
|
snd_pcm_format_set_silence(format, buf, pdbi->mmap_buflen_frames);
|
|
}
|
|
else
|
|
{
|
|
err = snd_pcm_mmap_begin(pcm, &areas, &ofs, &avail);
|
|
if ( err < 0 )
|
|
{
|
|
ERR("Can't map sound device for direct access: %s/%d\n", snd_strerror(err), err);
|
|
return DSERR_GENERIC;
|
|
}
|
|
snd_pcm_format_set_silence(format, areas->addr, pdbi->mmap_buflen_frames);
|
|
pdbi->mmap_pos = ofs + snd_pcm_mmap_commit(pcm, ofs, 0);
|
|
pdbi->mmap_buffer = areas->addr;
|
|
}
|
|
|
|
TRACE("created mmap buffer of %ld frames (%d bytes) at %p\n",
|
|
frames, pdbi->mmap_buflen_bytes, pdbi->mmap_buffer);
|
|
|
|
return DS_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverBufferImpl_QueryInterface(PIDSDRIVERBUFFER iface, REFIID riid, LPVOID *ppobj)
|
|
{
|
|
/* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */
|
|
FIXME("(): stub!\n");
|
|
return DSERR_UNSUPPORTED;
|
|
}
|
|
|
|
static ULONG WINAPI IDsDriverBufferImpl_AddRef(PIDSDRIVERBUFFER iface)
|
|
{
|
|
IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
|
|
ULONG refCount = InterlockedIncrement(&This->ref);
|
|
|
|
TRACE("(%p)->(ref before=%u)\n",This, refCount - 1);
|
|
|
|
return refCount;
|
|
}
|
|
|
|
static ULONG WINAPI IDsDriverBufferImpl_Release(PIDSDRIVERBUFFER iface)
|
|
{
|
|
IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
|
|
ULONG refCount = InterlockedDecrement(&This->ref);
|
|
|
|
TRACE("(%p)->(ref before=%u)\n",This, refCount + 1);
|
|
|
|
if (refCount)
|
|
return refCount;
|
|
|
|
TRACE("mmap buffer %p destroyed\n", This->mmap_buffer);
|
|
|
|
if (This == This->drv->primary)
|
|
This->drv->primary = NULL;
|
|
|
|
This->pcm_crst.DebugInfo->Spare[0] = 0;
|
|
DeleteCriticalSection(&This->pcm_crst);
|
|
|
|
snd_pcm_drop(This->pcm);
|
|
snd_pcm_close(This->pcm);
|
|
This->pcm = NULL;
|
|
HeapFree(GetProcessHeap(), 0, This->sw_params);
|
|
HeapFree(GetProcessHeap(), 0, This->hw_params);
|
|
if (!This->mmap)
|
|
HeapFree(GetProcessHeap(), 0, This->mmap_buffer);
|
|
HeapFree(GetProcessHeap(), 0, This);
|
|
return 0;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverBufferImpl_Lock(PIDSDRIVERBUFFER iface,
|
|
LPVOID*ppvAudio1,LPDWORD pdwLen1,
|
|
LPVOID*ppvAudio2,LPDWORD pdwLen2,
|
|
DWORD dwWritePosition,DWORD dwWriteLen,
|
|
DWORD dwFlags)
|
|
{
|
|
IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
|
|
snd_pcm_uframes_t writepos;
|
|
|
|
TRACE("%d bytes from %d\n", dwWriteLen, dwWritePosition);
|
|
|
|
/* **** */
|
|
EnterCriticalSection(&This->pcm_crst);
|
|
|
|
if (dwFlags & DSBLOCK_ENTIREBUFFER)
|
|
dwWriteLen = This->mmap_buflen_bytes;
|
|
|
|
if (dwWriteLen > This->mmap_buflen_bytes || dwWritePosition >= This->mmap_buflen_bytes)
|
|
{
|
|
/* **** */
|
|
LeaveCriticalSection(&This->pcm_crst);
|
|
return DSERR_INVALIDPARAM;
|
|
}
|
|
|
|
if (ppvAudio2) *ppvAudio2 = NULL;
|
|
if (pdwLen2) *pdwLen2 = 0;
|
|
|
|
*ppvAudio1 = This->mmap_buffer + dwWritePosition;
|
|
*pdwLen1 = dwWriteLen;
|
|
|
|
if (dwWritePosition+dwWriteLen > This->mmap_buflen_bytes)
|
|
{
|
|
DWORD remainder = This->mmap_buflen_bytes - dwWritePosition;
|
|
*pdwLen1 = remainder;
|
|
|
|
if (ppvAudio2 && pdwLen2)
|
|
{
|
|
*ppvAudio2 = This->mmap_buffer;
|
|
*pdwLen2 = dwWriteLen - remainder;
|
|
}
|
|
else dwWriteLen = remainder;
|
|
}
|
|
|
|
writepos = snd_pcm_bytes_to_frames(This->pcm, dwWritePosition);
|
|
if (writepos == This->mmap_pos)
|
|
{
|
|
const snd_pcm_channel_area_t *areas;
|
|
snd_pcm_uframes_t writelen = snd_pcm_bytes_to_frames(This->pcm, dwWriteLen), putin = writelen;
|
|
TRACE("Hit mmap_pos, locking data!\n");
|
|
if (This->mmap)
|
|
snd_pcm_mmap_begin(This->pcm, &areas, &This->mmap_pos, &putin);
|
|
}
|
|
else
|
|
WARN("mmap_pos (%lu) != writepos (%lu) not locking data!\n", This->mmap_pos, writepos);
|
|
|
|
LeaveCriticalSection(&This->pcm_crst);
|
|
/* **** */
|
|
return DS_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverBufferImpl_Unlock(PIDSDRIVERBUFFER iface,
|
|
LPVOID pvAudio1,DWORD dwLen1,
|
|
LPVOID pvAudio2,DWORD dwLen2)
|
|
{
|
|
IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
|
|
snd_pcm_uframes_t writepos;
|
|
|
|
if (!dwLen1)
|
|
return DS_OK;
|
|
|
|
/* **** */
|
|
EnterCriticalSection(&This->pcm_crst);
|
|
|
|
writepos = snd_pcm_bytes_to_frames(This->pcm, (DWORD_PTR)pvAudio1 - (DWORD_PTR)This->mmap_buffer);
|
|
if (writepos == This->mmap_pos)
|
|
{
|
|
const snd_pcm_channel_area_t *areas;
|
|
snd_pcm_uframes_t writelen = snd_pcm_bytes_to_frames(This->pcm, dwLen1);
|
|
TRACE("Committing data\n");
|
|
if (This->mmap)
|
|
This->mmap_pos += snd_pcm_mmap_commit(This->pcm, This->mmap_pos, writelen);
|
|
else
|
|
{
|
|
int ret;
|
|
ret = snd_pcm_writei(This->pcm, pvAudio1, writelen);
|
|
if (ret == -EPIPE)
|
|
{
|
|
WARN("Underrun occured\n");
|
|
snd_pcm_prepare(This->pcm);
|
|
ret = snd_pcm_writei(This->pcm, pvAudio1, writelen);
|
|
snd_pcm_start(This->pcm);
|
|
}
|
|
if (ret < 0)
|
|
WARN("Committing data: %d / %s (%p %ld)\n", ret, snd_strerror(ret), pvAudio1, writelen);
|
|
This->mmap_pos += writelen;
|
|
}
|
|
|
|
if (This->mmap_pos == This->mmap_buflen_frames)
|
|
This->mmap_pos = 0;
|
|
if (!This->mmap_pos && dwLen2)
|
|
{
|
|
writelen = snd_pcm_bytes_to_frames(This->pcm, dwLen2);
|
|
if (This->mmap)
|
|
{
|
|
snd_pcm_mmap_begin(This->pcm, &areas, &This->mmap_pos, &writelen);
|
|
This->mmap_pos += snd_pcm_mmap_commit(This->pcm, This->mmap_pos, writelen);
|
|
}
|
|
else
|
|
{
|
|
int ret;
|
|
ret = snd_pcm_writei(This->pcm, pvAudio2, writelen);
|
|
This->mmap_pos = writelen;
|
|
}
|
|
assert(This->mmap_pos < This->mmap_buflen_frames);
|
|
}
|
|
}
|
|
LeaveCriticalSection(&This->pcm_crst);
|
|
/* **** */
|
|
|
|
return DS_OK;
|
|
}
|
|
|
|
static HRESULT SetFormat(IDsDriverBufferImpl *This, LPWAVEFORMATEX pwfx)
|
|
{
|
|
snd_pcm_t *pcm = NULL;
|
|
snd_pcm_hw_params_t *hw_params = This->hw_params;
|
|
unsigned int buffer_time = 500000;
|
|
snd_pcm_format_t format = -1;
|
|
snd_pcm_uframes_t psize;
|
|
DWORD rate = pwfx->nSamplesPerSec;
|
|
int err=0;
|
|
|
|
switch (pwfx->wBitsPerSample)
|
|
{
|
|
case 8: format = SND_PCM_FORMAT_U8; break;
|
|
case 16: format = SND_PCM_FORMAT_S16_LE; break;
|
|
case 24: format = SND_PCM_FORMAT_S24_3LE; break;
|
|
case 32: format = SND_PCM_FORMAT_S32_LE; break;
|
|
default: FIXME("Unsupported bpp: %d\n", pwfx->wBitsPerSample); return DSERR_GENERIC;
|
|
}
|
|
|
|
err = snd_pcm_open(&pcm, WOutDev[This->drv->wDevID].pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
|
if (err < 0)
|
|
{
|
|
if (errno != EBUSY || !This->pcm)
|
|
{
|
|
WARN("Cannot open sound device: %s\n", snd_strerror(err));
|
|
return DSERR_GENERIC;
|
|
}
|
|
snd_pcm_drop(This->pcm);
|
|
snd_pcm_close(This->pcm);
|
|
This->pcm = NULL;
|
|
err = snd_pcm_open(&pcm, WOutDev[This->drv->wDevID].pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
|
if (err < 0)
|
|
{
|
|
WARN("Cannot open sound device: %s\n", snd_strerror(err));
|
|
return DSERR_BUFFERLOST;
|
|
}
|
|
}
|
|
|
|
/* Set some defaults */
|
|
snd_pcm_hw_params_any(pcm, hw_params);
|
|
err = snd_pcm_hw_params_set_channels(pcm, hw_params, pwfx->nChannels);
|
|
if (err < 0) { WARN("Could not set channels to %d\n", pwfx->nChannels); goto err; }
|
|
|
|
err = snd_pcm_hw_params_set_format(pcm, hw_params, format);
|
|
if (err < 0) { WARN("Could not set format to %d bpp\n", pwfx->wBitsPerSample); goto err; }
|
|
|
|
/* Alsa's rate resampling is only used if the application specifically requests
|
|
* a buffer at a certain frequency, else it is better to disable it due to unwanted
|
|
* side effects, which may include: Less granular pointer, changing buffer sizes, etc
|
|
*/
|
|
#if SND_LIB_VERSION >= 0x010009
|
|
snd_pcm_hw_params_set_rate_resample(pcm, hw_params, 0);
|
|
#endif
|
|
|
|
err = snd_pcm_hw_params_set_rate_near(pcm, hw_params, &rate, NULL);
|
|
if (err < 0) { rate = pwfx->nSamplesPerSec; WARN("Could not set rate\n"); goto err; }
|
|
|
|
if (!ALSA_NearMatch(rate, pwfx->nSamplesPerSec))
|
|
{
|
|
WARN("Could not set sound rate to %d, but instead to %d\n", pwfx->nSamplesPerSec, rate);
|
|
pwfx->nSamplesPerSec = rate;
|
|
pwfx->nAvgBytesPerSec = rate * pwfx->nBlockAlign;
|
|
/* Let DirectSound detect this */
|
|
}
|
|
|
|
snd_pcm_hw_params_set_periods_integer(pcm, hw_params);
|
|
snd_pcm_hw_params_set_buffer_time_near(pcm, hw_params, &buffer_time, NULL);
|
|
buffer_time = 10000;
|
|
snd_pcm_hw_params_set_period_time_near(pcm, hw_params, &buffer_time, NULL);
|
|
|
|
err = snd_pcm_hw_params_get_period_size(hw_params, &psize, NULL);
|
|
buffer_time = 16;
|
|
snd_pcm_hw_params_set_periods_near(pcm, hw_params, &buffer_time, NULL);
|
|
|
|
if (!This->mmap)
|
|
{
|
|
HeapFree(GetProcessHeap(), 0, This->mmap_buffer);
|
|
This->mmap_buffer = NULL;
|
|
}
|
|
|
|
err = snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
|
|
if (err >= 0)
|
|
This->mmap = 1;
|
|
else
|
|
{
|
|
This->mmap = 0;
|
|
err = snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
|
|
}
|
|
|
|
err = snd_pcm_hw_params(pcm, hw_params);
|
|
err = snd_pcm_sw_params(pcm, This->sw_params);
|
|
snd_pcm_prepare(pcm);
|
|
|
|
/* ALSA needs at least 3 buffers to work successfully */
|
|
This->mmap_commitahead = 3 * psize;
|
|
while (This->mmap_commitahead <= 512)
|
|
This->mmap_commitahead += psize;
|
|
|
|
if (This->pcm)
|
|
{
|
|
snd_pcm_drop(This->pcm);
|
|
snd_pcm_close(This->pcm);
|
|
}
|
|
This->pcm = pcm;
|
|
snd_pcm_prepare(This->pcm);
|
|
DSDB_CreateMMAP(This);
|
|
return S_OK;
|
|
|
|
err:
|
|
if (err < 0)
|
|
WARN("Failed to apply changes: %s\n", snd_strerror(err));
|
|
|
|
if (!This->pcm)
|
|
This->pcm = pcm;
|
|
else
|
|
snd_pcm_close(pcm);
|
|
|
|
if (This->pcm)
|
|
snd_pcm_hw_params_current(This->pcm, This->hw_params);
|
|
|
|
return DSERR_BADFORMAT;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverBufferImpl_SetFormat(PIDSDRIVERBUFFER iface, LPWAVEFORMATEX pwfx)
|
|
{
|
|
IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
|
|
HRESULT hr = S_OK;
|
|
|
|
TRACE("(%p, %p)\n", iface, pwfx);
|
|
|
|
/* **** */
|
|
EnterCriticalSection(&This->pcm_crst);
|
|
hr = SetFormat(This, pwfx);
|
|
/* **** */
|
|
LeaveCriticalSection(&This->pcm_crst);
|
|
|
|
if (hr == DS_OK)
|
|
return S_FALSE;
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverBufferImpl_SetFrequency(PIDSDRIVERBUFFER iface, DWORD dwFreq)
|
|
{
|
|
/* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */
|
|
FIXME("(%p,%d): stub\n",iface,dwFreq);
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverBufferImpl_SetVolumePan(PIDSDRIVERBUFFER iface, PDSVOLUMEPAN pVolPan)
|
|
{
|
|
IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
|
|
FIXME("(%p,%p): stub\n",This,pVolPan);
|
|
/* TODO: Bring volume control back */
|
|
return DS_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverBufferImpl_SetPosition(PIDSDRIVERBUFFER iface, DWORD dwNewPos)
|
|
{
|
|
/* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */
|
|
/* I don't even think alsa allows this */
|
|
FIXME("(%p,%d): stub\n",iface,dwNewPos);
|
|
return DSERR_UNSUPPORTED;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverBufferImpl_GetPosition(PIDSDRIVERBUFFER iface,
|
|
LPDWORD lpdwPlay, LPDWORD lpdwWrite)
|
|
{
|
|
IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
|
|
snd_pcm_uframes_t hw_pptr, hw_wptr;
|
|
snd_pcm_state_t state;
|
|
|
|
/* **** */
|
|
EnterCriticalSection(&This->pcm_crst);
|
|
|
|
if (!This->pcm)
|
|
{
|
|
FIXME("Bad pointer for pcm: %p\n", This->pcm);
|
|
LeaveCriticalSection(&This->pcm_crst);
|
|
return DSERR_GENERIC;
|
|
}
|
|
|
|
if (!lpdwPlay && !lpdwWrite)
|
|
CommitAll(This);
|
|
|
|
state = snd_pcm_state(This->pcm);
|
|
|
|
if (state != SND_PCM_STATE_PREPARED && state != SND_PCM_STATE_RUNNING)
|
|
{
|
|
CheckXRUN(This);
|
|
state = snd_pcm_state(This->pcm);
|
|
}
|
|
if (state == SND_PCM_STATE_RUNNING)
|
|
{
|
|
snd_pcm_sframes_t used = This->mmap_buflen_frames - snd_pcm_avail_update(This->pcm);
|
|
|
|
if (used < 0)
|
|
{
|
|
WARN("Underrun: %ld / %ld\n", used, snd_pcm_avail_update(This->pcm));
|
|
if (This->mmap)
|
|
{
|
|
snd_pcm_forward(This->pcm, -used);
|
|
This->mmap_pos += -used;
|
|
This->mmap_pos %= This->mmap_buflen_frames;
|
|
}
|
|
used = 0;
|
|
}
|
|
|
|
if (This->mmap_pos > used)
|
|
hw_pptr = This->mmap_pos - used;
|
|
else
|
|
hw_pptr = This->mmap_buflen_frames + This->mmap_pos - used;
|
|
hw_pptr %= This->mmap_buflen_frames;
|
|
|
|
TRACE("At position: %ld (%ld) - Used %ld\n", hw_pptr, This->mmap_pos, used);
|
|
}
|
|
else hw_pptr = This->mmap_pos;
|
|
hw_wptr = This->mmap_pos;
|
|
|
|
LeaveCriticalSection(&This->pcm_crst);
|
|
/* **** */
|
|
|
|
if (lpdwPlay)
|
|
*lpdwPlay = snd_pcm_frames_to_bytes(This->pcm, hw_pptr);
|
|
if (lpdwWrite)
|
|
*lpdwWrite = snd_pcm_frames_to_bytes(This->pcm, hw_wptr);
|
|
|
|
TRACE("hw_pptr=0x%08x, hw_wptr=0x%08x playpos=%d, writepos=%d\n", (unsigned int)hw_pptr, (unsigned int)hw_wptr, lpdwPlay?*lpdwPlay:-1, lpdwWrite?*lpdwWrite:-1);
|
|
return DS_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverBufferImpl_Play(PIDSDRIVERBUFFER iface, DWORD dwRes1, DWORD dwRes2, DWORD dwFlags)
|
|
{
|
|
IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
|
|
TRACE("(%p,%x,%x,%x)\n",iface,dwRes1,dwRes2,dwFlags);
|
|
|
|
/* **** */
|
|
EnterCriticalSection(&This->pcm_crst);
|
|
snd_pcm_start(This->pcm);
|
|
/* **** */
|
|
LeaveCriticalSection(&This->pcm_crst);
|
|
return DS_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverBufferImpl_Stop(PIDSDRIVERBUFFER iface)
|
|
{
|
|
const snd_pcm_channel_area_t *areas;
|
|
snd_pcm_uframes_t avail;
|
|
snd_pcm_format_t format;
|
|
IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
|
|
TRACE("(%p)\n",iface);
|
|
|
|
/* **** */
|
|
EnterCriticalSection(&This->pcm_crst);
|
|
avail = This->mmap_buflen_frames;
|
|
snd_pcm_drop(This->pcm);
|
|
snd_pcm_prepare(This->pcm);
|
|
avail = snd_pcm_avail_update(This->pcm);
|
|
snd_pcm_hw_params_get_format(This->hw_params, &format);
|
|
if (This->mmap)
|
|
{
|
|
snd_pcm_mmap_begin(This->pcm, &areas, &This->mmap_pos, &avail);
|
|
snd_pcm_format_set_silence(format, areas->addr, This->mmap_buflen_frames);
|
|
snd_pcm_mmap_commit(This->pcm, This->mmap_pos, 0);
|
|
}
|
|
else
|
|
{
|
|
snd_pcm_format_set_silence(format, This->mmap_buffer, This->mmap_buflen_frames);
|
|
snd_pcm_writei(This->pcm, This->mmap_buffer, This->mmap_buflen_frames);
|
|
This->mmap_pos = 0;
|
|
}
|
|
|
|
/* **** */
|
|
LeaveCriticalSection(&This->pcm_crst);
|
|
return DS_OK;
|
|
}
|
|
|
|
static const IDsDriverBufferVtbl dsdbvt =
|
|
{
|
|
IDsDriverBufferImpl_QueryInterface,
|
|
IDsDriverBufferImpl_AddRef,
|
|
IDsDriverBufferImpl_Release,
|
|
IDsDriverBufferImpl_Lock,
|
|
IDsDriverBufferImpl_Unlock,
|
|
IDsDriverBufferImpl_SetFormat,
|
|
IDsDriverBufferImpl_SetFrequency,
|
|
IDsDriverBufferImpl_SetVolumePan,
|
|
IDsDriverBufferImpl_SetPosition,
|
|
IDsDriverBufferImpl_GetPosition,
|
|
IDsDriverBufferImpl_Play,
|
|
IDsDriverBufferImpl_Stop
|
|
};
|
|
|
|
static HRESULT WINAPI IDsDriverImpl_QueryInterface(PIDSDRIVER iface, REFIID riid, LPVOID *ppobj)
|
|
{
|
|
/* IDsDriverImpl *This = (IDsDriverImpl *)iface; */
|
|
FIXME("(%p): stub!\n",iface);
|
|
return DSERR_UNSUPPORTED;
|
|
}
|
|
|
|
static ULONG WINAPI IDsDriverImpl_AddRef(PIDSDRIVER iface)
|
|
{
|
|
IDsDriverImpl *This = (IDsDriverImpl *)iface;
|
|
ULONG refCount = InterlockedIncrement(&This->ref);
|
|
|
|
TRACE("(%p)->(ref before=%u)\n",This, refCount - 1);
|
|
|
|
return refCount;
|
|
}
|
|
|
|
static ULONG WINAPI IDsDriverImpl_Release(PIDSDRIVER iface)
|
|
{
|
|
IDsDriverImpl *This = (IDsDriverImpl *)iface;
|
|
ULONG refCount = InterlockedDecrement(&This->ref);
|
|
|
|
TRACE("(%p)->(ref before=%u)\n",This, refCount + 1);
|
|
|
|
if (refCount)
|
|
return refCount;
|
|
|
|
HeapFree(GetProcessHeap(), 0, This);
|
|
return 0;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverImpl_GetDriverDesc(PIDSDRIVER iface, PDSDRIVERDESC pDesc)
|
|
{
|
|
IDsDriverImpl *This = (IDsDriverImpl *)iface;
|
|
TRACE("(%p,%p)\n",iface,pDesc);
|
|
*pDesc = WOutDev[This->wDevID].ds_desc;
|
|
pDesc->dwFlags = DSDDESC_DONTNEEDSECONDARYLOCK | DSDDESC_DONTNEEDWRITELEAD;
|
|
pDesc->dnDevNode = WOutDev[This->wDevID].waveDesc.dnDevNode;
|
|
pDesc->wVxdId = 0;
|
|
pDesc->wReserved = 0;
|
|
pDesc->ulDeviceNum = This->wDevID;
|
|
pDesc->dwHeapType = DSDHEAP_NOHEAP;
|
|
pDesc->pvDirectDrawHeap = NULL;
|
|
pDesc->dwMemStartAddress = 0xDEAD0000;
|
|
pDesc->dwMemEndAddress = 0xDEAF0000;
|
|
pDesc->dwMemAllocExtra = 0;
|
|
pDesc->pvReserved1 = NULL;
|
|
pDesc->pvReserved2 = NULL;
|
|
return DS_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverImpl_Open(PIDSDRIVER iface)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
IDsDriverImpl *This = (IDsDriverImpl *)iface;
|
|
int err=0;
|
|
snd_pcm_t *pcm = NULL;
|
|
snd_pcm_hw_params_t *hw_params;
|
|
|
|
/* While this is not really needed, it is a good idea to do this,
|
|
* to see if sound can be initialized */
|
|
|
|
hw_params = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof());
|
|
|
|
if (!hw_params)
|
|
{
|
|
hr = DSERR_OUTOFMEMORY;
|
|
goto unalloc;
|
|
}
|
|
|
|
err = snd_pcm_open(&pcm, WOutDev[This->wDevID].pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
|
if (err < 0) goto err;
|
|
err = snd_pcm_hw_params_any(pcm, hw_params);
|
|
if (err < 0) goto err;
|
|
err = snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
|
|
if (err < 0)
|
|
err = snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
|
|
if (err < 0) goto err;
|
|
|
|
TRACE("Success\n");
|
|
snd_pcm_close(pcm);
|
|
goto unalloc;
|
|
|
|
err:
|
|
hr = DSERR_GENERIC;
|
|
FIXME("Failed to open device: %s\n", snd_strerror(err));
|
|
if (pcm)
|
|
snd_pcm_close(pcm);
|
|
unalloc:
|
|
HeapFree(GetProcessHeap(), 0, hw_params);
|
|
if (hr != S_OK)
|
|
WARN("--> %08x\n", hr);
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverImpl_Close(PIDSDRIVER iface)
|
|
{
|
|
IDsDriverImpl *This = (IDsDriverImpl *)iface;
|
|
TRACE("(%p) stub, harmless\n",This);
|
|
return DS_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverImpl_GetCaps(PIDSDRIVER iface, PDSDRIVERCAPS pCaps)
|
|
{
|
|
IDsDriverImpl *This = (IDsDriverImpl *)iface;
|
|
TRACE("(%p,%p)\n",iface,pCaps);
|
|
*pCaps = WOutDev[This->wDevID].ds_caps;
|
|
return DS_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverImpl_CreateSoundBuffer(PIDSDRIVER iface,
|
|
LPWAVEFORMATEX pwfx,
|
|
DWORD dwFlags, DWORD dwCardAddress,
|
|
LPDWORD pdwcbBufferSize,
|
|
LPBYTE *ppbBuffer,
|
|
LPVOID *ppvObj)
|
|
{
|
|
IDsDriverImpl *This = (IDsDriverImpl *)iface;
|
|
IDsDriverBufferImpl** ippdsdb = (IDsDriverBufferImpl**)ppvObj;
|
|
HRESULT err;
|
|
|
|
TRACE("(%p,%p,%x,%x)\n",iface,pwfx,dwFlags,dwCardAddress);
|
|
/* we only support primary buffers... for now */
|
|
if (!(dwFlags & DSBCAPS_PRIMARYBUFFER))
|
|
return DSERR_UNSUPPORTED;
|
|
if (This->primary)
|
|
return DSERR_ALLOCATED;
|
|
|
|
*ippdsdb = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDsDriverBufferImpl));
|
|
if (*ippdsdb == NULL)
|
|
return DSERR_OUTOFMEMORY;
|
|
|
|
(*ippdsdb)->hw_params = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof());
|
|
(*ippdsdb)->sw_params = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_sw_params_sizeof());
|
|
if (!(*ippdsdb)->hw_params || !(*ippdsdb)->sw_params)
|
|
{
|
|
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->sw_params);
|
|
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->hw_params);
|
|
return DSERR_OUTOFMEMORY;
|
|
}
|
|
(*ippdsdb)->lpVtbl = &dsdbvt;
|
|
(*ippdsdb)->ref = 1;
|
|
(*ippdsdb)->drv = This;
|
|
InitializeCriticalSection(&(*ippdsdb)->pcm_crst);
|
|
(*ippdsdb)->pcm_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ALSA_DSOUTPUT.pcm_crst");
|
|
|
|
/* SetFormat has to re-initialize pcm here anyway */
|
|
err = SetFormat(*ippdsdb, pwfx);
|
|
if (FAILED(err))
|
|
{
|
|
WARN("Error occurred: %08x\n", err);
|
|
goto err;
|
|
}
|
|
|
|
if (dwFlags & DSBCAPS_PRIMARYBUFFER)
|
|
This->primary = *ippdsdb;
|
|
|
|
*pdwcbBufferSize = (*ippdsdb)->mmap_buflen_bytes;
|
|
*ppbBuffer = (*ippdsdb)->mmap_buffer;
|
|
|
|
/* buffer is ready to go */
|
|
TRACE("buffer created at %p\n", *ippdsdb);
|
|
return err;
|
|
|
|
err:
|
|
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->sw_params);
|
|
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->hw_params);
|
|
HeapFree(GetProcessHeap(), 0, *ippdsdb);
|
|
*ippdsdb = NULL;
|
|
return err;
|
|
}
|
|
|
|
static HRESULT WINAPI IDsDriverImpl_DuplicateSoundBuffer(PIDSDRIVER iface,
|
|
PIDSDRIVERBUFFER pBuffer,
|
|
LPVOID *ppvObj)
|
|
{
|
|
IDsDriverImpl *This = (IDsDriverImpl *)iface;
|
|
FIXME("(%p,%p): stub\n",This,pBuffer);
|
|
return DSERR_INVALIDCALL;
|
|
}
|
|
|
|
static const IDsDriverVtbl dsdvt =
|
|
{
|
|
IDsDriverImpl_QueryInterface,
|
|
IDsDriverImpl_AddRef,
|
|
IDsDriverImpl_Release,
|
|
IDsDriverImpl_GetDriverDesc,
|
|
IDsDriverImpl_Open,
|
|
IDsDriverImpl_Close,
|
|
IDsDriverImpl_GetCaps,
|
|
IDsDriverImpl_CreateSoundBuffer,
|
|
IDsDriverImpl_DuplicateSoundBuffer
|
|
};
|
|
|
|
DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv)
|
|
{
|
|
IDsDriverImpl** idrv = (IDsDriverImpl**)drv;
|
|
|
|
TRACE("driver created\n");
|
|
|
|
*idrv = HeapAlloc(GetProcessHeap(),0,sizeof(IDsDriverImpl));
|
|
if (!*idrv)
|
|
return MMSYSERR_NOMEM;
|
|
(*idrv)->lpVtbl = &dsdvt;
|
|
(*idrv)->ref = 1;
|
|
|
|
(*idrv)->wDevID = wDevID;
|
|
(*idrv)->primary = NULL;
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc)
|
|
{
|
|
*desc = WOutDev[wDevID].ds_desc;
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
#endif /* HAVE_ALSA */
|