[alsa-devel] Alsa-OSS Duplex bug (revisited)

Takashi Iwai tiwai at suse.de
Wed May 30 19:05:05 CEST 2007


At Wed, 30 May 2007 08:47:14 +0200,
Clemens Ladisch wrote:
> 
> Alan Horstmann wrote:
> > On Tuesday 29 May 2007 17:55, you wrote:
> > > While I see the advantage by your hack (small and backward
> > > compatible), I feel that it's too hackish -- it introduces an
> > > incompatible way of the existing ioctl.
> > 
> > Do you mean in that ioctls in general are not meant to work like that, in 
> > effect passing 2 numbers rather than one?
> 
> No, packing two values into one numbers is done with other ioctls too,
> e.g., SNDCTL_DSP_SETFRAGMENT.
> 
> The problem I see is that your patch changes the OSS API in a way that
> is incompatible with every implementation other than ALSA's.  The whole
> purpose of providing the OSS API in the first place is to be compatible
> with other implementations.

Exactly.

> > > After a quick thought, another possible fix would be to let apps open
> > > each direction separately.  For that,
> > >
> > > - add some way to make the given PCM stream to non-fullduplex
> > >   (proc or module options?)
> > 
> > Are you thinking that if an option were set, Alsa-OSS would create separate 
> > devices for capture and playback rather than a single duplex device? 
> 
> No; just a way to disable DSP_CAP_DUPLEX and/or SNDCTL_DSP_SETDUPLEX.
> When these do not work, applications are forced to open the playback
> and capture devices separately, e.g.:
> 
> fd_playback = open("/dev/dsp", O_WRONLY);
> fd_capture = open("/dev/dsp", O_RDONLY);

I thought of a similar hack but it seems that many apps don't check /
set DUPLEX capability.  So, this might not work on many apps.

> > > - change portaudio to open each direction separately, O_RDONLY and
> > >   O_WRONLY at first, then use O_RDWR as fallback
> 
> This would be the preferred way.  After all, this is the only way that
> is possible with the existing OSS API when you want to use different
> sample formats, and it is recommended in all cases (see
> <http://manuals.opensound.com/developer/full_duplex.html>).

Right.  For example, in the case of audacity, the change would be like
the following patch (untested).


Takashi

--- lib-src/portaudio/pa_unix_oss/pa_unix.c-dist	2007-05-30 17:52:10.000000000 +0200
+++ lib-src/portaudio/pa_unix_oss/pa_unix.c	2007-05-30 18:22:09.000000000 +0200
@@ -720,12 +720,45 @@ int Pa_GetMinNumBuffers( int framesPerBu
 }
 
 /*******************************************************************/
+
+static int do_open(internalPortAudioDevice *pad, int flags, int verbose)
+{
+    int fd = open(pad->pad_DeviceName, flags);
+    if (fd < 0)
+    {
+	if (verbose)
+	    ERR_RPT(("PaHost_OpenStream: could not open %s for %s\n",
+		     pad->pad_DeviceName,
+		     (flags == O_RDONLY ? "O_RDONLY" :
+		      (flags == O_WRONLY ? "O_WRONLY" : "O_RDWR"))));
+    }
+    return fd;
+}
+
+static int open_device(internalPortAudioDevice *pad, int flags, int verbose)
+{
+    int fd;
+
+    /* dmazzoni: test it first in nonblocking mode to
+       make sure the device is not busy */
+    fd = do_open(pad->pad_DeviceName, flags|O_NONBLOCK, verbose);
+    if (fd < 0)
+	return BAD_DEVICE_ID;
+    close(fd);
+
+    fd = do_open(pad->pad_DeviceName, flags, verbose);
+    if (fd < 0)
+	return BAD_DEVICE_ID;
+    return fd;
+}
+
 PaError PaHost_OpenStream( internalPortAudioStream   *past )
 {
     PaError          result = paNoError;
     PaHostSoundControl *pahsc;
     unsigned int     minNumBuffers;
     internalPortAudioDevice *pad;
+    int full_duplex;
     DBUG(("PaHost_OpenStream() called.\n" ));
 
     /* Allocate and initialize host data. */
@@ -778,102 +811,89 @@ PaError PaHost_OpenStream( internalPortA
 
     /* ------------------------- OPEN DEVICE -----------------------*/
 
-    /* just output */
-    if (past->past_OutputDeviceID == past->past_InputDeviceID)
-    {
-
-        if ((past->past_NumOutputChannels > 0) && (past->past_NumInputChannels > 0) )
-        {
-            pad = Pa_GetInternalDevice( past->past_OutputDeviceID );
-            DBUG(("PaHost_OpenStream: attempt to open %s for O_RDWR\n", pad->pad_DeviceName ));
-
-            /* dmazzoni: test it first in nonblocking mode to
-               make sure the device is not busy */
-            pahsc->pahsc_InputHandle = open(pad->pad_DeviceName,O_RDWR|O_NONBLOCK);
-            if(pahsc->pahsc_InputHandle==-1)
-            {
-                ERR_RPT(("PaHost_OpenStream: could not open %s for O_RDWR\n", pad->pad_DeviceName ));
-                result = paHostError;
-                goto error;
-            }
-            close(pahsc->pahsc_InputHandle);
-
-            pahsc->pahsc_OutputHandle = pahsc->pahsc_InputHandle =
-                                            open(pad->pad_DeviceName,O_RDWR);
-            if(pahsc->pahsc_InputHandle==-1)
-            {
-                ERR_RPT(("PaHost_OpenStream: could not open %s for O_RDWR\n", pad->pad_DeviceName ));
-                result = paHostError;
-                goto error;
-            }
-            Pa_SetLatency( pahsc->pahsc_OutputHandle,
-                           past->past_NumUserBuffers, past->past_FramesPerUserBuffer,
-                           past->past_NumOutputChannels );
-            result = Pa_SetupDeviceFormat( pahsc->pahsc_OutputHandle,
-                                           past->past_NumOutputChannels, (int)past->past_SampleRate );
-        }
-    }
+    if (past->past_OutputDeviceID == past->past_InputDeviceID &&
+        past->past_NumOutputChannels > 0 &&
+	past->past_NumInputChannels > 0)
+	full_duplex = 1;
     else
+	full_duplex = 0;
+
+    /* try to open each direction separately, at first */
+    if (past->past_NumOutputChannels > 0)
     {
-        if (past->past_NumOutputChannels > 0)
-        {
-            pad = Pa_GetInternalDevice( past->past_OutputDeviceID );
-            DBUG(("PaHost_OpenStream: attempt to open %s for O_WRONLY\n", pad->pad_DeviceName ));
-            /* dmazzoni: test it first in nonblocking mode to
-               make sure the device is not busy */
-            pahsc->pahsc_OutputHandle = open(pad->pad_DeviceName,O_WRONLY|O_NONBLOCK);
-            if(pahsc->pahsc_OutputHandle==-1)
-            {
-                ERR_RPT(("PaHost_OpenStream: could not open %s for O_WRONLY\n", pad->pad_DeviceName ));
-                result = paHostError;
-                goto error;
-            }
-            close(pahsc->pahsc_OutputHandle);
-
-            pahsc->pahsc_OutputHandle = open(pad->pad_DeviceName,O_WRONLY);
-            if(pahsc->pahsc_OutputHandle==-1)
-            {
-                ERR_RPT(("PaHost_OpenStream: could not open %s for O_WRONLY\n", pad->pad_DeviceName ));
-                result = paHostError;
-                goto error;
-            }
-            Pa_SetLatency( pahsc->pahsc_OutputHandle,
-                           past->past_NumUserBuffers, past->past_FramesPerUserBuffer,
-                           past->past_NumOutputChannels );
-            result = Pa_SetupOutputDeviceFormat( pahsc->pahsc_OutputHandle,
-                                           past->past_NumOutputChannels, (int)past->past_SampleRate );
-        }
+	pad = Pa_GetInternalDevice( past->past_OutputDeviceID );
+	DBUG(("PaHost_OpenStream: attempt to open %s for O_WRONLY\n", pad->pad_DeviceName ));
+	pahsc->pahsc_OutputHandle = open_device(pad, O_WRONLY,
+						!full_duplex);
+	if (pahsc->pahsc_OutputHandle < 0)
+	{
+	    result = paHostError;
+	    goto check_duplex;
+	}
+	Pa_SetLatency( pahsc->pahsc_OutputHandle,
+		       past->past_NumUserBuffers, past->past_FramesPerUserBuffer,
+		       past->past_NumOutputChannels );
+	result = Pa_SetupOutputDeviceFormat( pahsc->pahsc_OutputHandle,
+					     past->past_NumOutputChannels, (int)past->past_SampleRate );
+	if (result < 0)
+	{
+	    close(pahsc->pahsc_OutputHandle);
+	    pahsc->pahsc_OutputHandle = BAD_DEVICE_ID;
+	    goto check_duplex;
+	}
+    }
+
+    if (past->past_NumInputChannels > 0)
+    {
+	pad = Pa_GetInternalDevice( past->past_InputDeviceID );
+
+	DBUG(("PaHost_OpenStream: attempt to open %s for O_RDONLY\n", pad->pad_DeviceName ));
+	pahsc->pahsc_InputHandle = open_device(pad, O_RDONLY,
+					       !full_duplex);
+	if (pahsc->pahsc_InputHandle < 0)
+	{
+	    result = paHostError;
+	    goto check_duplex;
+	}
+	Pa_SetLatency( pahsc->pahsc_InputHandle, /* DH20010115 - was OutputHandle! */
+		       past->past_NumUserBuffers, past->past_FramesPerUserBuffer,
+		       past->past_NumInputChannels );
+	result = Pa_SetupInputDeviceFormat( pahsc->pahsc_InputHandle,
+					    past->past_NumInputChannels, (int)past->past_SampleRate );
+	if (result < 0)
+	{
+	    close(pahsc->pahsc_InputHandle);
+	    pahsc->pahsc_InputHandle = BAD_DEVICE_ID;
+	    goto check_duplex;
+	}
+    }
+
+ check_duplex:
+    /* if opening one of the direction fails for full-duplex,
+     * try to open a single device with O_RDWR
+     */
+    if (full_duplex &&
+	(phasc->phasc_OutputHandle < 0 || phasc->phasc_InputHandle < 0))
+    {
+	pad = Pa_GetInternalDevice( past->past_OutputDeviceID );
+	DBUG(("PaHost_OpenStream: attempt to open %s for O_RDWR\n", pad->pad_DeviceName ));
 
-        if (past->past_NumInputChannels > 0)
-        {
-            pad = Pa_GetInternalDevice( past->past_InputDeviceID );
-            DBUG(("PaHost_OpenStream: attempt to open %s for O_RDONLY\n", pad->pad_DeviceName ));
-            /* dmazzoni: test it first in nonblocking mode to
-               make sure the device is not busy */
-            pahsc->pahsc_InputHandle = open(pad->pad_DeviceName,O_RDONLY|O_NONBLOCK);
-            if(pahsc->pahsc_InputHandle==-1)
-            {
-                ERR_RPT(("PaHost_OpenStream: could not open %s for O_RDONLY\n", pad->pad_DeviceName ));
-                result = paHostError;
-                goto error;
-            }
-            close(pahsc->pahsc_InputHandle);
-
-            pahsc->pahsc_InputHandle = open(pad->pad_DeviceName,O_RDONLY);
-            if(pahsc->pahsc_InputHandle==-1)
-            {
-                ERR_RPT(("PaHost_OpenStream: could not open %s for O_RDONLY\n", pad->pad_DeviceName ));
-                result = paHostError;
-                goto error;
-            }
-            Pa_SetLatency( pahsc->pahsc_InputHandle, /* DH20010115 - was OutputHandle! */
-                           past->past_NumUserBuffers, past->past_FramesPerUserBuffer,
-                           past->past_NumInputChannels );
-            result = Pa_SetupInputDeviceFormat( pahsc->pahsc_InputHandle,
-                                           past->past_NumInputChannels, (int)past->past_SampleRate );
-        }
+	pahsc->pahsc_OutputHandle = open_device(pad, O_RDWR, 1);
+	if (phasc->pahsc_OutputHandle < 0)
+	{
+	    result = paHostError;
+	    goto error;
+	}
+	phasc->phasc_InputHandle = phasc->phasc_OutputHandle;
+	Pa_SetLatency( pahsc->pahsc_OutputHandle,
+		       past->past_NumUserBuffers, past->past_FramesPerUserBuffer,
+		       past->past_NumOutputChannels );
+	result = Pa_SetupDeviceFormat( pahsc->pahsc_OutputHandle,
+				       past->past_NumOutputChannels, (int)past->past_SampleRate );
+	    
     }
-
+    if (result < 0)
+	goto error;
 
     DBUG(("PaHost_OpenStream: SUCCESS - result = %d\n", result ));
     return result;


More information about the Alsa-devel mailing list