winealsa: improve sound quality for IDsDriverImpl

Jerry Jenkins Jerry_J_Jenkins at hotmail.com
Fri Oct 3 23:53:47 CDT 2003


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.


-------------- next part --------------
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, &param), "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, &param.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;
 }
 



More information about the wine-patches mailing list