Switch to side-by-side view

--- a
+++ b/src/alsadirect.cpp
@@ -0,0 +1,283 @@
+/* Copyright (C) 2014 J.F.Dockes
+ *	 This program is free software; you can redistribute it and/or modify
+ *	 it under the terms of the GNU General Public License as published by
+ *	 the Free Software Foundation; either version 2 of the License, or
+ *	 (at your option) any later version.
+ *
+ *	 This program is distributed in the hope that it will be useful,
+ *	 but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	 GNU General Public License for more details.
+ *
+ *	 You should have received a copy of the GNU General Public License
+ *	 along with this program; if not, write to the
+ *	 Free Software Foundation, Inc.,
+ *	 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+#include "config.h"
+
+#include <string.h>
+#include <sys/types.h>
+
+#include <iostream>
+#include <queue>
+#include <alsa/asoundlib.h>
+
+#include <samplerate.h>
+
+#include "log.h"
+#include "rcvqueue.h"
+
+using namespace std;
+
+#ifndef MIN
+#define MIN(A, B) ((A) < (B) ? (A) : (B))
+#endif
+
+// 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 + 40;
+
+static WorkQueue<AudioMessage*> alsaqueue("alsaqueue", qs);
+static snd_pcm_t *pcm;
+
+static void *alsawriter(void *p)
+{
+    if (!alsaqueue.waitminsz(qit)) {
+        LOGERR("alsawriter: waitminsz failed\n");
+        return (void *)1;
+    }
+    while (true) {
+        AudioMessage *tsk = 0;
+        size_t qsz;
+        if (!alsaqueue.take(&tsk, &qsz)) {
+            // TBD: reset alsa?
+            alsaqueue.workerExit();
+            return (void*)1;
+        }
+
+        // Bufs 
+        snd_pcm_uframes_t frames = 
+            tsk->m_bytes / (tsk->m_chans * (tsk->m_bits/8));
+        snd_pcm_sframes_t ret =  snd_pcm_writei(pcm, tsk->m_buf, frames);
+        if (ret != int(frames)) {
+            LOGERR("snd-cm_writei(" << frames <<" frames) failed: ret: " <<
+                   ret << endl);
+            if (ret < 0)
+                snd_pcm_prepare(pcm);
+//            return (void *)1;
+        }
+    }
+}
+
+static bool alsa_init(AudioMessage *tsk)
+{
+    snd_pcm_hw_params_t *hw_params;
+    int err;
+//    static const string dev("plughw:CARD=PCH,DEV=0");
+    static const string dev("hw:2,0");
+    const char *cmd = "";
+    unsigned int actual_rate = tsk->m_freq;
+    int dir=0;
+
+    if ((err = snd_pcm_open(&pcm, dev.c_str(), 
+                            SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
+        LOGERR("alsa_init: snd_pcm_open " << dev << " " << 
+               snd_strerror(err) << endl);
+        return false;;
+    }
+		   
+    if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
+        LOGERR("alsa_init: snd_pcm_hw_params_malloc " << 
+               snd_strerror(err) << endl);
+        snd_pcm_close(pcm);
+        return false;
+    }
+
+    cmd = "snd_pcm_hw_params_any";
+    if ((err = snd_pcm_hw_params_any(pcm, hw_params)) < 0) {
+        goto error;
+    }
+	
+    cmd = "snd_pcm_hw_params_set_access";
+    if ((err = 
+         snd_pcm_hw_params_set_access(pcm, hw_params, 
+                                      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_FORMAT_S16_LE)) < 0) {
+        goto error;
+    }
+    cmd = "snd_pcm_hw_params_set_rate_near";
+    if ((err = snd_pcm_hw_params_set_rate_near(pcm, hw_params, 
+                                               &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;
+    }
+
+    cmd = "snd_pcm_hw_params";
+    if ((err = snd_pcm_hw_params(pcm, hw_params)) < 0) {
+        goto error;
+    }
+	
+    snd_pcm_hw_params_free(hw_params);
+    return true;
+
+error:
+    snd_pcm_hw_params_free(hw_params);
+    LOGERR("alsa_init: " << cmd << " " << snd_strerror(err) << endl);
+    return false;;
+}
+
+static void *audioEater(void *cls)
+{
+    AudioEater::Context *ctxt = (AudioEater::Context*)cls;
+
+    LOGDEB("alsaEater: queue " << ctxt->queue << endl);
+
+    WorkQueue<AudioMessage*> *queue = ctxt->queue;
+    delete ctxt;
+
+    bool qinit = false;
+    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?
+            queue->workerExit();
+            return (void*)1;
+        }
+
+        if (src_state == 0) {
+            if (!alsa_init(tsk)) {
+                queue->workerExit();
+                return (void *)1;
+            }
+            // BEST_QUALITY yields approx 25% cpu on a core i7
+            // 4770T. Obviously too much, actually might not be
+            // sustainable.
+            // MEDIUM_QUALITY is around 10%
+            // FASTEST is 4-5%. Given that this is process-wide, probably
+            // a couple % in fact.
+            // To be re-evaluated on the pi...
+            src_state = src_new(SRC_SINC_FASTEST, tsk->m_chans, &src_error);
+        }
+
+        if (qinit) {
+            float qs = alsaqueue.qsize();
+            float t = ((qt - qs) / qt);
+            float adj = t * t  / 10;
+            if (alsaqueue.qsize() < qt) {
+                samplerate_ratio =  1.0 + adj;
+                if (samplerate_ratio > 1.1)
+                    samplerate_ratio = 1.1;
+            } else {
+                samplerate_ratio -= adj;
+                if (samplerate_ratio < 0.9) 
+                    samplerate_ratio = 0.9;
+            }
+        }
+
+        unsigned int tot_samples = tsk->m_bytes / (tsk->m_bits/8);
+        if ((unsigned int)src_data.input_frames < tot_samples / tsk->m_chans) {
+            int bytes = tot_samples * sizeof(float);
+            src_data.data_in = (float *)realloc(src_data.data_in, bytes);
+            src_data.data_out = (float *)realloc(src_data.data_out, 2 * bytes);
+            src_data.input_frames = tot_samples / tsk->m_chans;
+            // Available space for output
+            src_data.output_frames = 2 * src_data.input_frames;
+        }
+        src_data.src_ratio = samplerate_ratio;
+        src_data.end_of_input = 0;
+        
+        switch (tsk->m_bits) {
+        case 16: {
+            const short *sp = (const short *)tsk->m_buf;
+            for (unsigned int i = 0; i < tot_samples; i++) {
+                src_data.data_in[i] = *sp++;
+            }
+            break;
+        }
+        case 24:
+        case 32:
+        default:
+            abort();
+        }
+        int ret = src_process(src_state, &src_data);
+        if (ret) {
+            LOGERR("src_process: " << src_strerror(ret) << endl);
+            continue;
+        }
+        {
+            static int cnt;
+            if (cnt++ == 100) {
+                LOGDEB("samplerate: " 
+                       " qsize " << alsaqueue.qsize() << 
+                       " ratio " << samplerate_ratio <<
+                       " in " << src_data.input_frames << 
+                       " consumed " << src_data.input_frames_used << 
+                       " out " << src_data.output_frames_gen << endl);
+                cnt = 0;
+            }
+        }
+        tot_samples =  src_data.output_frames_gen * tsk->m_chans;
+        if (src_data.output_frames_gen > src_data.input_frames) {
+            tsk->m_bytes = tot_samples * (tsk->m_bits / 8);
+            tsk->m_buf = (char *)realloc(tsk->m_buf, tsk->m_bytes);
+            if (!tsk->m_buf) 
+                abort();
+        }
+
+        // Output is always 16 bits lsb first for now. We should
+        // probably dither the lsb ?
+        tsk->m_bits = 16;
+        {
+#ifdef WORDS_BIGENDIAN
+            unsigned char *ocp = (unsigned char *)tsk->m_buf;
+            short val;
+            unsigned char *icp = (unsigned char *)&val;
+            for (unsigned int i = 0; i < tot_samples; i++) {
+                val = src_data.data_out[i];;
+                *ocp++ = icp[1];
+                *ocp++ = icp[0];
+            }
+            tsk->m_bytes = ocp - tsk->m_buf;
+#else
+            short *sp = (short *)tsk->m_buf;
+            for (unsigned int i = 0; i < tot_samples; i++) {
+                *sp++ = src_data.data_out[i];
+            }
+            tsk->m_bytes = (char *)sp - tsk->m_buf;
+#endif
+        }
+
+        if (!alsaqueue.put(tsk)) {
+            LOGERR("alsaEater: queue put failed\n");
+            return (void *)1;
+        }
+        if (alsaqueue.qsize() >= qit)
+            qinit = true;
+    }
+}
+
+AudioEater alsaAudioEater(AudioEater::BO_HOST, &audioEater);