Switch to side-by-side view

--- a/src/alsadirect.cpp
+++ b/src/alsadirect.cpp
@@ -34,15 +34,14 @@
 #define MIN(A, B) ((A) < (B) ? (A) : (B))
 #endif
 
-static const snd_pcm_uframes_t periodsize = 32768; /* Periodsize (bytes) */
+static snd_pcm_uframes_t periodsize = 16384; /* Periodsize (bytes) */
+static unsigned int periods = 2;       /* Number of periods */
 
 // The queue for audio blocks ready for alsa
-static const unsigned int qs = 200;
-static const unsigned int qt = qs/2;
-// the 40 value should be computed from the alsa buffer size. It's
-// there becausee we have a jump on the first alsa write (alsa buffer
-// is empty).
-static const unsigned int qit = qs/2 + periodsize/1024;
+static const unsigned int qs = 100;
+// Queue size target including alsa buffers. This gets recomputed as
+// soon as we have the actual bit/chans params
+static unsigned int qstarg = qs/2;
 
 static WorkQueue<AudioMessage*> alsaqueue("alsaqueue", qs);
 static snd_pcm_t *pcm;
@@ -52,8 +51,9 @@
 {
     while (true) {
         if (!qinit) {
-            if (!alsaqueue.waitminsz(qit)) {
+            if (!alsaqueue.waitminsz(qstarg)) {
                 LOGERR("alsawriter: waitminsz failed\n");
+                alsaqueue.workerExit();
                 return (void *)1;
             }
         }
@@ -83,12 +83,11 @@
 
 static bool alsa_init(const string& dev, AudioMessage *tsk)
 {
-    snd_pcm_hw_params_t *hw_params;
+    snd_pcm_hw_params_t *hwparams;
     int err;
     const char *cmd = "";
+    int dir=0;
     unsigned int actual_rate = tsk->m_freq;
-    int dir=0;
-    int periods = 2;       /* Number of periods */
 
     if ((err = snd_pcm_open(&pcm, dev.c_str(), 
                             SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
@@ -96,8 +95,7 @@
                snd_strerror(err) << endl);
         return false;;
     }
-		   
-    if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
+    if ((err = snd_pcm_hw_params_malloc(&hwparams)) < 0) {
         LOGERR("alsa_init: snd_pcm_hw_params_malloc " << 
                snd_strerror(err) << endl);
         snd_pcm_close(pcm);
@@ -105,92 +103,128 @@
     }
 
     cmd = "snd_pcm_hw_params_any";
-    if ((err = snd_pcm_hw_params_any(pcm, hw_params)) < 0) {
-        goto error;
-    }
-	
+    if ((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0) {
+        goto error;
+    }
     cmd = "snd_pcm_hw_params_set_access";
     if ((err = 
-         snd_pcm_hw_params_set_access(pcm, hw_params, 
+         snd_pcm_hw_params_set_access(pcm, hwparams, 
                                       SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
         goto error;
     }
 
     cmd = "snd_pcm_hw_params_set_format";
     if ((err = 
-         snd_pcm_hw_params_set_format(pcm, hw_params, 
+         snd_pcm_hw_params_set_format(pcm, hwparams, 
                                       SND_PCM_FORMAT_S16_LE)) < 0) {
         goto error;
     }
+    cmd = "snd_pcm_hw_params_set_channels";
+    if ((err = snd_pcm_hw_params_set_channels(pcm, hwparams, 
+                                              tsk->m_chans)) < 0) {
+        goto error;
+    }
     cmd = "snd_pcm_hw_params_set_rate_near";
-    if ((err = snd_pcm_hw_params_set_rate_near(pcm, hw_params, 
+    if ((err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, 
                                                &actual_rate, &dir)) < 0) {
         goto error;
     }
 
-    cmd = "snd_pcm_hw_params_set_channels";
-    if ((err = snd_pcm_hw_params_set_channels(pcm, hw_params, 
-                                              tsk->m_chans)) < 0) {
-        goto error;
-    }
-    /* Set number of periods. Periods used to be called fragments. */ 
+    unsigned int periodsmin, periodsmax;
+    snd_pcm_hw_params_get_periods_min(hwparams, &periodsmin, &dir);
+    snd_pcm_hw_params_get_periods_max(hwparams, &periodsmax, &dir);
+    LOGDEB("Alsa: periods min " << periodsmin << 
+           " max " << periodsmax << endl);
+    periods = 2;
+    if (periods < periodsmin || periods > periodsmax)
+        periods = periodsmin;
     cmd = "snd_pcm_hw_params_set_periods";
-    if (snd_pcm_hw_params_set_periods(pcm, hw_params, periods, 0) < 0) {
-        goto error;
-    }
-  
-    /* Set buffer size (in frames). The resulting latency is given by */
-    /* latency = periodsize * periods / (rate * bytes_per_frame)     */
+    if ((err = snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0)) < 0) {
+        goto error;
+    }
+    snd_pcm_uframes_t bsmax, bsmin;
+    snd_pcm_hw_params_get_buffer_size_min(hwparams, &bsmin);
+    snd_pcm_hw_params_get_buffer_size_max(hwparams, &bsmax);
+    unsigned int bufferframes;
+    bufferframes = periodsize * periods / (2*tsk->m_chans);
+    if (bufferframes < bsmin || bufferframes > bsmax) {
+        bufferframes = bsmin;
+        periodsize = bufferframes / periods * (2 * tsk->m_chans);
+    }
     cmd = "snd_pcm_hw_params_set_buffer_size";
-    if (snd_pcm_hw_params_set_buffer_size(pcm, hw_params, 
-                                          (periodsize * periods)>>2) < 0) {
+    LOGDEB("Alsa: set buffer_size: min " << bsmin << " max " << bsmax << 
+           " val " << bufferframes << endl);
+    if ((err = snd_pcm_hw_params_set_buffer_size(pcm, hwparams, bufferframes)) 
+        < 0) {
         goto error;
     }
   
     cmd = "snd_pcm_hw_params";
-    if ((err = snd_pcm_hw_params(pcm, hw_params)) < 0) {
+    if ((err = snd_pcm_hw_params(pcm, hwparams)) < 0) {
         goto error;
     }
 	
-    snd_pcm_hw_params_free(hw_params);
+    snd_pcm_hw_params_free(hwparams);
     return true;
 
 error:
-    snd_pcm_hw_params_free(hw_params);
-    LOGERR("alsa_init: " << cmd << " " << snd_strerror(err) << endl);
-    return false;;
+    LOGERR("alsa_init: " << cmd << " error:" << snd_strerror(err) << endl);
+    snd_pcm_hw_params_free(hwparams);
+    return false;
+}
+
+// Current in-driver delay in samples
+static int alsadelay()
+{
+    snd_pcm_sframes_t delay;
+    if (snd_pcm_delay(pcm, &delay) >= 0) {
+        return delay;
+    } else {
+        return 0;
+    }
 }
 
 static void *audioEater(void *cls)
 {
+    LOGDEB("audioEater: alsadirect\n");
     AudioEater::Context *ctxt = (AudioEater::Context*)cls;
-
-    LOGDEB("alsaEater: queue " << ctxt->queue << endl);
 
     WorkQueue<AudioMessage*> *queue = ctxt->queue;
     string alsadevice = ctxt->alsadevice;
 
     delete ctxt;
+    ctxt = 0;
 
     qinit = false;
+
+    float samplerate_ratio = 1.0;
+
     int src_error = 0;
     SRC_STATE *src_state = 0;
     SRC_DATA src_data;
     memset(&src_data, 0, sizeof(src_data));
+
     alsaqueue.start(1, alsawriter, 0);
-    float samplerate_ratio = 1.0;
 
     while (true) {
         AudioMessage *tsk = 0;
         size_t qsz;
         if (!queue->take(&tsk, &qsz)) {
-            // TBD: reset alsa?
+            LOGDEB("audioEater: alsadirect: queue take failed\n");
+            alsaqueue.setTerminateAndWait();
             queue->workerExit();
             return (void*)1;
         }
 
+        if (tsk->m_bytes == 0 || tsk->m_chans == 0 || tsk->m_bits == 0) {
+            LOGDEB("Zero buf\n");
+            continue;
+        }
+
+        int bufframes = 441;
         if (src_state == 0) {
             if (!alsa_init(alsadevice, tsk)) {
+                alsaqueue.setTerminateAndWait();
                 queue->workerExit();
                 return (void *)1;
             }
@@ -203,13 +237,20 @@
             // To be re-evaluated on the pi... FASTEST is 30% CPU on a Pi 2
             // with USB audio. Curiously it's 25-30% on a Pi1 with i2s audio.
             src_state = src_new(SRC_SINC_FASTEST, tsk->m_chans, &src_error);
-        }
-
+
+            // This is constant for a given stream (depends on fe, buffers 
+            // are 10mS)
+            bufframes = tsk->m_bytes / (tsk->m_chans * (tsk->m_bits/8));
+            // period size is in bytes
+            LOGDEB("audioEater: alsadirect: qstarg " << qstarg << endl);
+        }
+
+        float qs = alsaqueue.qsize();
         if (qinit) {
-            float qs = alsaqueue.qsize();
-            float t = ((qt - qs) / qt);
-            float adj = t * t  / 10;
-            if (alsaqueue.qsize() < qt) {
+            qs = alsaqueue.qsize() + alsadelay() / bufframes;
+            float t = ((qstarg - qs) / qstarg);
+            float adj = t * t;
+            if (qs < qstarg) {
                 samplerate_ratio =  1.0 + adj;
                 if (samplerate_ratio > 1.1)
                     samplerate_ratio = 1.1;
@@ -256,7 +297,9 @@
             static int cnt;
             if (cnt++ == 100) {
                 LOGDEB("samplerate: " 
-                       " qsize " << alsaqueue.qsize() << 
+                       " qstarg " << qstarg <<
+                       " iqsz " << alsaqueue.qsize() <<
+                       " qsize " << int(qs) << 
                        " ratio " << samplerate_ratio <<
                        " in " << src_data.input_frames << 
                        " consumed " << src_data.input_frames_used << 
@@ -297,6 +340,7 @@
 
         if (!alsaqueue.put(tsk)) {
             LOGERR("alsaEater: queue put failed\n");
+            queue->workerExit();
             return (void *)1;
         }
     }