With the help of many peoples, here comes my 2nd attempt.
BTW, the code added to the IDsDriverBufferImpl_SetVolumePan function is
copied from one of Robert Reif’s patches.
Index: dlls/winmm/winealsa/audio.c
===================================================================
RCS file: /home/wine/wine/dlls/winmm/winealsa/audio.c,v
retrieving revision 1.20
diff -u -r1.20 audio.c
--- dlls/winmm/winealsa/audio.c 22 Sep 2003 21:13:33 -0000 1.20
+++ dlls/winmm/winealsa/audio.c 4 Oct 2003 02:50:23 -0000
@@ -395,6 +395,156 @@
}
+typedef struct ALSA_DMA_TEST {
+ snd_pcm_t *handle; /* playback handle */
+ snd_pcm_access_t access; /* access mode */
+ snd_pcm_format_t format; /* sample format */
+ UINT uRate; /* stream rate */
+ UINT uChannels; /* count of channels */
+ /* ring buffer length in us. Don't set too small a value to this
+ * parameter. Otherwise we have to recover ALSA from underrun state
+ * in critical environments. */
+ UINT uBufferTime;
+ UINT uPeriodTime; /* period time in us */
+ snd_pcm_uframes_t period_size; /* period buffer size */
+
+ UINT uLoops; /* loop counter */
+ int err; /* error code */
+ const snd_pcm_channel_area_t *pSavedAreas;/* a value that might be constant */
+ LPVOID pSavedAddress; /* a value that might be constant */
+}ALSA_DMA_TEST;
+
+/**************************************************************************
+ * ALSA_DMACallback [internal]
+ */
+void ALSA_DMACallback(snd_async_handler_t *ahandler)
+{
+ int err;
+ snd_pcm_sframes_t size, commitres;
+ snd_pcm_uframes_t offset, frames;
+ const snd_pcm_channel_area_t *areas;
+ ALSA_DMA_TEST *pParam = snd_async_handler_get_callback_private(ahandler);
+
+#define EXIT_ON_ERROR(f) do { if ((err = (f)) < 0) { pParam->err = err; return; } }while(0);
+ EXIT_ON_ERROR( size = snd_pcm_avail_update(pParam->handle) );
+ while (size >= (snd_pcm_sframes_t)pParam->period_size) {
+ frames = size;
+ EXIT_ON_ERROR( snd_pcm_mmap_begin(pParam->handle, &areas, &offset, &frames) );
+ /* We don't care about what the data are, just a test */
+ commitres = snd_pcm_mmap_commit(pParam->handle, offset, frames);
+ if (areas != pParam->pSavedAreas || areas->addr != pParam->pSavedAddress) {
+ EXIT_ON_ERROR( -EFAULT );
+ }
+ EXIT_ON_ERROR( commitres );
+ size -= commitres;
+ pParam->uLoops++;
+ }
+#undef EXIT_ON_ERROR
+}
+
+/**************************************************************************
+ * ALSA_IsDMAAvailable [internal]
+ * IDsDriverBufferImpl will use an undocumented feature of
+ * snd_pcm_mmap_begin, which it will return a constant address up to now.
+ * In order to avoid segment fault when we access the buffer, we must
+ * confirm that the buffer won't be reallocated.
+ */
+BOOL ALSA_IsDMAAvailable()
+{
+ int i, err;
+ snd_pcm_t *handle = NULL;
+ snd_pcm_state_t state;
+ snd_pcm_sframes_t size;
+ snd_pcm_uframes_t offset, frames;
+ snd_async_handler_t *ahandler = NULL;
+ snd_pcm_hw_params_t *hwparams = NULL;
+ snd_pcm_sw_params_t *swparams = NULL;
+ ALSA_DMA_TEST param;
+
+#define DMA_TEST_FLAG_RESULT 0x00000001
+#define DMA_TEST_FLAG_TESTED 0x00000002
+ static DWORD dwTestFlag = 0;
+ if (dwTestFlag & DMA_TEST_FLAG_TESTED) {
+ return (dwTestFlag & DMA_TEST_FLAG_RESULT);
+ }
+
+ snd_pcm_hw_params_alloca(&hwparams);
+ snd_pcm_sw_params_alloca(&swparams);
+
+#define EXIT_ON_ERROR(f, txt) do { if ((err = (f)) < 0) { WARN(txt ": %s.\n", snd_strerror(err)); goto ALSA_IsDMAAvailable_End; } }while(0);
+ EXIT_ON_ERROR( snd_pcm_open(&handle, "hw", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK), "open pcm" );
+
+ param.handle = handle;
+ param.access = SND_PCM_ACCESS_MMAP_INTERLEAVED;
+ param.format = SND_PCM_FORMAT_U8;
+ param.uChannels = 2;
+ param.uRate = 22050;
+ param.uBufferTime = 500000;
+ param.uPeriodTime = 10000;
+
+ EXIT_ON_ERROR( snd_pcm_hw_params_any(handle, hwparams), "hw params" );
+ EXIT_ON_ERROR( snd_pcm_hw_params_set_access(handle, hwparams, param.access), "access mode" );
+ EXIT_ON_ERROR( snd_pcm_hw_params_set_format(handle, hwparams, param.format), "pcm format" );
+ EXIT_ON_ERROR( snd_pcm_hw_params_set_channels(handle, hwparams, param.uChannels), "channels" );
+ EXIT_ON_ERROR( snd_pcm_hw_params_set_rate_near(handle, hwparams, param.uRate, 0), "rate" );
+ EXIT_ON_ERROR( snd_pcm_hw_params_set_buffer_time_near(handle, hwparams, param.uBufferTime, 0), "buffer time" );
+ EXIT_ON_ERROR( snd_pcm_hw_params_set_period_time_near(handle, hwparams, param.uPeriodTime, 0), "period time" );
+ param.period_size = snd_pcm_hw_params_get_period_size(hwparams, 0);
+ EXIT_ON_ERROR( snd_pcm_hw_params(handle, hwparams), "install hardware configuration" );
+
+ EXIT_ON_ERROR( snd_pcm_sw_params_current(handle, swparams), "sw params" );
+ EXIT_ON_ERROR( snd_pcm_sw_params(handle, swparams), "install software configuration" );
+
+ EXIT_ON_ERROR( snd_async_add_pcm_handler(&ahandler, handle, ALSA_DMACallback, ¶m), "async handle" );
+
+ /* Start testing */
+ for (i = 0; i < 5; i++) {/* Check if snd_pcm_drop will cause the buffer to be reallocated */
+ state = snd_pcm_state(handle);
+ if (state == SND_PCM_STATE_SETUP) {
+ EXIT_ON_ERROR( snd_pcm_prepare(handle), "prepare pcm" );
+ state = snd_pcm_state(handle);
+ }
+ if (state == SND_PCM_STATE_PREPARED) {
+ EXIT_ON_ERROR( snd_pcm_avail_update(handle), "avail update" );
+ /* Use up the whole buffer. If it is subjected to be reallocated,
+ * we'll get the new address when we call snd_pcm_mmap_begin again.
+ */
+ frames = snd_pcm_hw_params_get_buffer_size(hwparams);
+ EXIT_ON_ERROR( snd_pcm_mmap_begin(handle, ¶m.pSavedAreas, &offset, &frames), "mmap begin" );
+ param.pSavedAddress = param.pSavedAreas->addr;
+ snd_pcm_format_set_silence(param.format, param.pSavedAddress, frames);
+ EXIT_ON_ERROR( (size = snd_pcm_mmap_commit(handle, offset, frames)), "mmap commit" );
+ if (size != (snd_pcm_sframes_t)frames) EXIT_ON_ERROR(-errno, "mmap commit" );
+
+ EXIT_ON_ERROR( snd_pcm_start(handle), "start pcm" );
+ }
+ else EXIT_ON_ERROR( -EPIPE, "unexpected state" );
+
+ param.err = 0;
+ param.uLoops = 0;
+ /* Wait util snd_pcm_mmap_begin called */
+ while(0 == param.uLoops && 0 == param.err) {
+ usleep(param.uPeriodTime);
+ }
+ EXIT_ON_ERROR( param.err, "callback" );
+ EXIT_ON_ERROR( snd_pcm_drop(handle), "stop pcm" );
+ }
+ dwTestFlag |= DMA_TEST_FLAG_RESULT;
+
+ snd_async_del_handler(ahandler);
+ snd_pcm_drop(handle);
+
+ALSA_IsDMAAvailable_End:
+ if (NULL != handle) snd_pcm_close(handle);
+
+ dwTestFlag |= DMA_TEST_FLAG_TESTED;
+
+ return (dwTestFlag & DMA_TEST_FLAG_RESULT);
+#undef EXIT_ON_ERROR
+#undef DMA_TEST_FLAG_RESULT
+#undef DMA_TEST_FLAG_TESTED
+}
+
/******************************************************************
@@ -516,6 +666,8 @@
snd_pcm_close(h);
ALSA_InitializeVolumeCtl(wwo);
+
+ ALSA_IsDMAAvailable();
return 0;
}
@@ -1175,12 +1327,12 @@
snd_pcm_close(pcm);
return WAVERR_BADFORMAT;
}
-
+
EXIT_ON_ERROR( snd_pcm_hw_params_set_buffer_time_near(pcm, hw_params, buffer_time, 0), MMSYSERR_INVALPARAM, "unable to set buffer time");
EXIT_ON_ERROR( snd_pcm_hw_params_set_period_time_near(pcm, hw_params, period_time, 0), MMSYSERR_INVALPARAM, "unable to set period time");
EXIT_ON_ERROR( snd_pcm_hw_params(pcm, hw_params), MMSYSERR_INVALPARAM, "unable to set hw params for playback");
-
+
period_size = snd_pcm_hw_params_get_period_size(hw_params, 0);
buffer_size = snd_pcm_hw_params_get_buffer_size(hw_params);
@@ -1703,13 +1855,13 @@
if ( !pdbi->mmap_buffer || !wwo->hw_params || !wwo->p_handle)
return;
+ DSDB_CheckXRUN(pdbi);
+
channels = snd_pcm_hw_params_get_channels(wwo->hw_params);
format = snd_pcm_hw_params_get_format(wwo->hw_params);
period_size = snd_pcm_hw_params_get_period_size(wwo->hw_params, 0);
avail = snd_pcm_avail_update(wwo->p_handle);
- DSDB_CheckXRUN(pdbi);
-
TRACE("avail=%d format=%s channels=%d\n", (int)avail, snd_pcm_format_name(format), channels );
while (avail >= period_size)
@@ -1724,7 +1876,10 @@
EnterCriticalSection(&pdbi->mmap_crst);
snd_pcm_mmap_begin(wwo->p_handle, &areas, &ofs, &frames);
- snd_pcm_areas_copy(areas, ofs, pdbi->mmap_areas, ofs, channels, frames, format);
+ if (!ALSA_IsDMAAvailable())
+ snd_pcm_areas_copy(areas, ofs, pdbi->mmap_areas, ofs, channels, frames, format);
+ else if (pdbi->mmap_areas != areas || pdbi->mmap_buffer != areas->addr)
+ FIXME("DMA failed. Please reimplement it.\n");/* Shouldn't reach here */
err = snd_pcm_mmap_commit(wwo->p_handle, ofs, frames);
LeaveCriticalSection(&pdbi->mmap_crst);
@@ -1734,7 +1889,7 @@
avail = snd_pcm_avail_update(wwo->p_handle);
}
- }
+}
static void DSDB_PCMCallback(snd_async_handler_t *ahandler)
{
@@ -1745,7 +1900,7 @@
}
static int DSDB_CreateMMAP(IDsDriverBufferImpl* pdbi)
- {
+{
WINE_WAVEOUT * wwo = &(WOutDev[pdbi->drv->wDevID]);
snd_pcm_format_t format = snd_pcm_hw_params_get_format(wwo->hw_params);
snd_pcm_uframes_t frames = snd_pcm_hw_params_get_buffer_size(wwo->hw_params);
@@ -1754,6 +1909,7 @@
unsigned int bits_per_frame = bits_per_sample * channels;
snd_pcm_channel_area_t * a;
unsigned int c;
+ snd_pcm_uframes_t ofs;
int err;
if (TRACE_ON(wave))
@@ -1764,45 +1920,61 @@
pdbi->mmap_buflen_frames = frames;
pdbi->mmap_buflen_bytes = snd_pcm_frames_to_bytes( wwo->p_handle, frames );
- pdbi->mmap_buffer = HeapAlloc(GetProcessHeap(),0,pdbi->mmap_buflen_bytes);
- if (!pdbi->mmap_buffer)
- return DSERR_OUTOFMEMORY;
+ if (!ALSA_IsDMAAvailable()) {
+ pdbi->mmap_buffer = HeapAlloc(GetProcessHeap(),0,pdbi->mmap_buflen_bytes);
+ if (!pdbi->mmap_buffer)
+ return DSERR_OUTOFMEMORY;
+
+ pdbi->mmap_areas = HeapAlloc(GetProcessHeap(),0,channels*sizeof(snd_pcm_channel_area_t));
+ if (!pdbi->mmap_areas)
+ return DSERR_OUTOFMEMORY;
+ a = pdbi->mmap_areas;
+ for (c = 0; c < channels; c++, a++)
+ {
+ a->addr = pdbi->mmap_buffer;
+ a->first = bits_per_sample * c;
+ a->step = bits_per_frame;
+ TRACE("Area %d: addr=%p first=%d step=%d\n", c, a->addr, a->first, a->step);
+ }
+ }
+ else {/* DMA */
+ err = snd_pcm_mmap_begin(wwo->p_handle, (const snd_pcm_channel_area_t**)&pdbi->mmap_areas, &ofs, &frames);
+ snd_pcm_mmap_commit(wwo->p_handle, ofs, 0L);
+ if (err < 0) {
+ ERR("Create mmap buffer failed : %s\n", snd_strerror(err));
+ return DSERR_GENERIC;
+ }
+ pdbi->mmap_buffer = pdbi->mmap_areas->addr;
+ }
snd_pcm_format_set_silence(format, pdbi->mmap_buffer, frames );
TRACE("created mmap buffer of %ld frames (%ld bytes) at %p\n",
frames, pdbi->mmap_buflen_bytes, pdbi->mmap_buffer);
- pdbi->mmap_areas = HeapAlloc(GetProcessHeap(),0,channels*sizeof(snd_pcm_channel_area_t));
- if (!pdbi->mmap_areas)
- return DSERR_OUTOFMEMORY;
-
- a = pdbi->mmap_areas;
- for (c = 0; c < channels; c++, a++)
- {
- a->addr = pdbi->mmap_buffer;
- a->first = bits_per_sample * c;
- a->step = bits_per_frame;
- TRACE("Area %d: addr=%p first=%d step=%d\n", c, a->addr, a->first, a->step);
- }
-
InitializeCriticalSection(&pdbi->mmap_crst);
err = snd_async_add_pcm_handler(&pdbi->mmap_async_handler, wwo->p_handle, DSDB_PCMCallback, pdbi);
if ( err < 0 )
- {
- ERR("add_pcm_handler failed. reason: %s\n", snd_strerror(err));
+ {
+ ERR("add_pcm_handler failed. reason: %s\n", snd_strerror(err));
+ HeapFree(GetProcessHeap(), 0, pdbi->mmap_areas);
+ HeapFree(GetProcessHeap(), 0, pdbi->mmap_buffer);
+ pdbi->mmap_areas = NULL;
+ pdbi->mmap_buffer = NULL;
return DSERR_GENERIC;
- }
+ }
return DS_OK;
- }
+}
static void DSDB_DestroyMMAP(IDsDriverBufferImpl* pdbi)
{
TRACE("mmap buffer %p destroyed\n", pdbi->mmap_buffer);
- HeapFree(GetProcessHeap(), 0, pdbi->mmap_areas);
- HeapFree(GetProcessHeap(), 0, pdbi->mmap_buffer);
+ if (!ALSA_IsDMAAvailable()) {
+ HeapFree(GetProcessHeap(), 0, pdbi->mmap_areas);
+ HeapFree(GetProcessHeap(), 0, pdbi->mmap_buffer);
+ }
pdbi->mmap_areas = NULL;
pdbi->mmap_buffer = NULL;
DeleteCriticalSection(&pdbi->mmap_crst);
@@ -1873,8 +2045,17 @@
static HRESULT WINAPI IDsDriverBufferImpl_SetVolumePan(PIDSDRIVERBUFFER iface, PDSVOLUMEPAN pVolPan)
{
- /* ICOM_THIS(IDsDriverBufferImpl,iface); */
- FIXME("(%p,%p): stub!\n",iface,pVolPan);
+ DWORD vol;
+ ICOM_THIS(IDsDriverBufferImpl,iface);
+ TRACE("(%p,%p)\n",This,pVolPan);
+
+ vol = pVolPan->dwTotalLeftAmpFactor | (pVolPan->dwTotalRightAmpFactor << 16);
+
+ if (wodSetVolume(This->drv->wDevID, vol) != MMSYSERR_NOERROR) {
+ WARN("wodSetVolume failed\n");
+ return DSERR_INVALIDPARAM;
+ }
+
return DS_OK;
}
@@ -1928,13 +2109,15 @@
if ( state == SND_PCM_STATE_SETUP )
{
err = snd_pcm_prepare(wwo->p_handle);
- state = snd_pcm_state(wwo->p_handle);
+ if (err < 0) ERR("prepare pcm: %s\n", snd_strerror(err));
+ state = snd_pcm_state(wwo->p_handle);
}
if ( state == SND_PCM_STATE_PREPARED )
- {
+ {
DSDB_MMAPCopy(This);
err = snd_pcm_start(wwo->p_handle);
- }
+ if (err < 0) ERR("start pcm: %s\n", snd_strerror(err));
+ }
return DS_OK;
}