Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hardware initialization fails (Device or resource is busy) #63

Closed
hauptmech opened this issue Jul 15, 2018 · 6 comments
Closed

Hardware initialization fails (Device or resource is busy) #63

hauptmech opened this issue Jul 15, 2018 · 6 comments
Assignees
Labels

Comments

@hauptmech
Copy link

pyalsaaudio hardware initialization fails because it is written with a misunderstanding of how alsa hardware initialization works. It needs to be rewritten...

Issues #61, #60, #47, #46, #43, #34 are probably due to this.

From the alsa_lib documentation (ie code):

/** \brief Install one PCM hardware configuration chosen from a configuration space and #snd_pcm_prepare it
 * \param pcm PCM handle
 * \param params Configuration space definition container
 * \return 0 on success otherwise a negative error code
 *
 * The configuration is chosen fixing single parameters in this order:
 * first access, first format, first subformat, min channels, min rate, 
 * min period time, max buffer size, min tick time. If no mutually
 * compatible set of parameters can be chosen, a negative error code
 * will be returned.
 *
 * After this call, #snd_pcm_prepare() is called automatically and
 * the stream is brought to \c #SND_PCM_STATE_PREPARED state.
 *
 * The hardware parameters cannot be changed when the stream is
 * running (active). The software parameters can be changed
 * at any time.
 *
 * The configuration space will be updated to reflect the chosen
 * parameters.
 */
int snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)

pyalsaaudio is trying to set the hardware configuration multiple times, and getting -EBUSY (Device or resource busy) because the state of the audio device is SND_PCM_STATE_PREPARED and configuration is no longer possible.

Basically you must set all of the hardware configuration as you like it, and then call snd_pcm_hw_params() once and only once at the end.

I don't have time to offer a patch but the following diff shows my approach:

diff --git a/alsaaudio.c b/alsaaudio.c
index 76c0ffe..db81f96 100644
--- a/alsaaudio.c
+++ b/alsaaudio.c
@@ -360,13 +359,17 @@ alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     int pcmmode = 0;
     char *device = "default";
     char *card = NULL;
+    unsigned int rate = 48000;
+    unsigned int latency = 500000;
+    unsigned int channels = 2;
+    snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
     int cardidx = -1;
     char hw_device[128];
-    char *kw[] = { "type", "mode", "device", "cardindex", "card", NULL };
+    char *kw[] = { "type", "mode", "device", "cardindex", "card", "rate", "latency", "fmt", "channels", NULL };
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oisiz", kw,
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oisiziiii", kw,
                                      &pcmtypeobj, &pcmmode, &device,
-                                     &cardidx, &card))
+                                     &cardidx, &card, &rate, &latency, &format, &channels))
         return NULL;
 
     if (cardidx >= 0) {
@@ -410,18 +413,28 @@ alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     self->handle = 0;
     self->pcmtype = pcmtype;
     self->pcmmode = pcmmode;
-    self->channels = 2;
-    self->rate = 44100;
-    self->format = SND_PCM_FORMAT_S16_LE;
-    self->periodsize = 32;
+    self->channels = channels;
+    self->rate = rate;
+    self->format = format;
+
 
     res = snd_pcm_open(&(self->handle), device, self->pcmtype,
                        self->pcmmode);
 
-    if (res >= 0) {
-        res = alsapcm_setup(self);
+    if (res >= 0){
+     snd_pcm_hw_params_malloc(&(self->hwparams));
+    }
+    res = snd_pcm_set_params(self->handle, format, 
+      SND_PCM_ACCESS_RW_INTERLEAVED, channels, rate, 1, latency);
+    if (res) {
+            PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res), device);
     }
-    
+
+    //Populate framesize and periodsize
+    snd_pcm_hw_params_current(self->handle, self->hwparams);
+    self->framesize = channels * snd_pcm_hw_params_get_sbits(self->hwparams)/8;
+    snd_pcm_hw_params_get_period_size(self->hwparams,&self->periodsize,0);
+
     if (res >= 0) {
         self->cardname = strdup(device);
     }

@larsimmisch
Copy link
Owner

From the alsa_lib documentation (ie code)

Well, I think I understand what you're getting at. Good catch - I never managed to reproduce the E_BUSY (I must admit I didn't try hard) and this is a somewhat subtle problem..

Maybe I'll find some time the week after next to put something like this in. If you manage to create a patch, that would be much appreciated.

@larsimmisch larsimmisch self-assigned this Jul 15, 2018
@riverar
Copy link

riverar commented Aug 29, 2018

Hi @larsimmisch @hauptmech, any further work in this area?

Seems like the fundamental design and API needs changing, or perhaps don't finalize hw params (snd_pcm_hw_params) until the first read()? Not sure if that's ideal.

@robmsmt
Copy link

robmsmt commented Sep 19, 2018

I am having this same problem, I tried to implement the approach listed above however get the following:

alsaaudio.c: In function ‘alsapcm_new’:
alsaaudio.c:435:37: error: ‘alsapcm_t {aka struct <anonymous>}’ has no member named ‘hwparams’
      snd_pcm_hw_params_malloc(&(self->hwparams));

@hauptmech
Copy link
Author

The diff above was not complete, only meant to communicate the idea. Attached are my files.
pyalsaaudio.zip

@larsimmisch
Copy link
Owner

In the end, I decided to make rate, channels, 'formatandperiodsizekeyword arguments to thePCMconstructor and deprecatesetchannels, setrate, setformatandsetperiodsize`. I hope that is the best way to move forward.

@ossilator
Copy link
Collaborator

pedantically, nothing prevents reconfiguring a prepared stream, so @hauptmech's point is technically invalid, though i suppose bugs might exist. also, it's not very efficient.

but one could also avoid the problem without deprecating the old api, at the cost of a behavior change: switch to lazy setup, invoking it only upon the first read/write/etc., or when requested explicitly via a new function.

anyway, what's done is done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants