Switch to unified view

a b/src/alsadirect.cpp
1
/* Copyright (C) 2014 J.F.Dockes
2
 *     This program is free software; you can redistribute it and/or modify
3
 *     it under the terms of the GNU General Public License as published by
4
 *     the Free Software Foundation; either version 2 of the License, or
5
 *     (at your option) any later version.
6
 *
7
 *     This program is distributed in the hope that it will be useful,
8
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *     GNU General Public License for more details.
11
 *
12
 *     You should have received a copy of the GNU General Public License
13
 *     along with this program; if not, write to the
14
 *     Free Software Foundation, Inc.,
15
 *     59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16
 */
17
#include "config.h"
18
19
#include <string.h>
20
#include <sys/types.h>
21
22
#include <iostream>
23
#include <queue>
24
#include <alsa/asoundlib.h>
25
26
#include <samplerate.h>
27
28
#include "log.h"
29
#include "rcvqueue.h"
30
31
using namespace std;
32
33
#ifndef MIN
34
#define MIN(A, B) ((A) < (B) ? (A) : (B))
35
#endif
36
37
// The queue for audio blocks ready for alsa
38
static const unsigned int qs = 200;
39
static const unsigned int qt = qs/2;
40
// the 40 value should be computed from the alsa buffer size. It's
41
// there becausee we have a jump on the first alsa write (alsa buffer
42
// is empty).
43
static const unsigned int qit = qs/2 + 40;
44
45
static WorkQueue<AudioMessage*> alsaqueue("alsaqueue", qs);
46
static snd_pcm_t *pcm;
47
48
static void *alsawriter(void *p)
49
{
50
    if (!alsaqueue.waitminsz(qit)) {
51
        LOGERR("alsawriter: waitminsz failed\n");
52
        return (void *)1;
53
    }
54
    while (true) {
55
        AudioMessage *tsk = 0;
56
        size_t qsz;
57
        if (!alsaqueue.take(&tsk, &qsz)) {
58
            // TBD: reset alsa?
59
            alsaqueue.workerExit();
60
            return (void*)1;
61
        }
62
63
        // Bufs 
64
        snd_pcm_uframes_t frames = 
65
            tsk->m_bytes / (tsk->m_chans * (tsk->m_bits/8));
66
        snd_pcm_sframes_t ret =  snd_pcm_writei(pcm, tsk->m_buf, frames);
67
        if (ret != int(frames)) {
68
            LOGERR("snd-cm_writei(" << frames <<" frames) failed: ret: " <<
69
                   ret << endl);
70
            if (ret < 0)
71
                snd_pcm_prepare(pcm);
72
//            return (void *)1;
73
        }
74
    }
75
}
76
77
static bool alsa_init(AudioMessage *tsk)
78
{
79
    snd_pcm_hw_params_t *hw_params;
80
    int err;
81
//    static const string dev("plughw:CARD=PCH,DEV=0");
82
    static const string dev("hw:2,0");
83
    const char *cmd = "";
84
    unsigned int actual_rate = tsk->m_freq;
85
    int dir=0;
86
87
    if ((err = snd_pcm_open(&pcm, dev.c_str(), 
88
                            SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
89
        LOGERR("alsa_init: snd_pcm_open " << dev << " " << 
90
               snd_strerror(err) << endl);
91
        return false;;
92
    }
93
         
94
    if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
95
        LOGERR("alsa_init: snd_pcm_hw_params_malloc " << 
96
               snd_strerror(err) << endl);
97
        snd_pcm_close(pcm);
98
        return false;
99
    }
100
101
    cmd = "snd_pcm_hw_params_any";
102
    if ((err = snd_pcm_hw_params_any(pcm, hw_params)) < 0) {
103
        goto error;
104
    }
105
  
106
    cmd = "snd_pcm_hw_params_set_access";
107
    if ((err = 
108
         snd_pcm_hw_params_set_access(pcm, hw_params, 
109
                                      SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
110
        goto error;
111
    }
112
113
    cmd = "snd_pcm_hw_params_set_format";
114
    if ((err = 
115
         snd_pcm_hw_params_set_format(pcm, hw_params, 
116
                                      SND_PCM_FORMAT_S16_LE)) < 0) {
117
        goto error;
118
    }
119
    cmd = "snd_pcm_hw_params_set_rate_near";
120
    if ((err = snd_pcm_hw_params_set_rate_near(pcm, hw_params, 
121
                                               &actual_rate, &dir)) < 0) {
122
        goto error;
123
    }
124
125
    cmd = "snd_pcm_hw_params_set_channels";
126
    if ((err = snd_pcm_hw_params_set_channels(pcm, hw_params, 
127
                                              tsk->m_chans)) < 0) {
128
        goto error;
129
    }
130
131
    cmd = "snd_pcm_hw_params";
132
    if ((err = snd_pcm_hw_params(pcm, hw_params)) < 0) {
133
        goto error;
134
    }
135
  
136
    snd_pcm_hw_params_free(hw_params);
137
    return true;
138
139
error:
140
    snd_pcm_hw_params_free(hw_params);
141
    LOGERR("alsa_init: " << cmd << " " << snd_strerror(err) << endl);
142
    return false;;
143
}
144
145
static void *audioEater(void *cls)
146
{
147
    AudioEater::Context *ctxt = (AudioEater::Context*)cls;
148
149
    LOGDEB("alsaEater: queue " << ctxt->queue << endl);
150
151
    WorkQueue<AudioMessage*> *queue = ctxt->queue;
152
    delete ctxt;
153
154
    bool qinit = false;
155
    int src_error = 0;
156
    SRC_STATE *src_state = 0;
157
    SRC_DATA src_data;
158
    memset(&src_data, 0, sizeof(src_data));
159
    alsaqueue.start(1, alsawriter, 0);
160
    float samplerate_ratio = 1.0;
161
162
    while (true) {
163
        AudioMessage *tsk = 0;
164
        size_t qsz;
165
        if (!queue->take(&tsk, &qsz)) {
166
            // TBD: reset alsa?
167
            queue->workerExit();
168
            return (void*)1;
169
        }
170
171
        if (src_state == 0) {
172
            if (!alsa_init(tsk)) {
173
                queue->workerExit();
174
                return (void *)1;
175
            }
176
            // BEST_QUALITY yields approx 25% cpu on a core i7
177
            // 4770T. Obviously too much, actually might not be
178
            // sustainable.
179
            // MEDIUM_QUALITY is around 10%
180
            // FASTEST is 4-5%. Given that this is process-wide, probably
181
            // a couple % in fact.
182
            // To be re-evaluated on the pi...
183
            src_state = src_new(SRC_SINC_FASTEST, tsk->m_chans, &src_error);
184
        }
185
186
        if (qinit) {
187
            float qs = alsaqueue.qsize();
188
            float t = ((qt - qs) / qt);
189
            float adj = t * t  / 10;
190
            if (alsaqueue.qsize() < qt) {
191
                samplerate_ratio =  1.0 + adj;
192
                if (samplerate_ratio > 1.1)
193
                    samplerate_ratio = 1.1;
194
            } else {
195
                samplerate_ratio -= adj;
196
                if (samplerate_ratio < 0.9) 
197
                    samplerate_ratio = 0.9;
198
            }
199
        }
200
201
        unsigned int tot_samples = tsk->m_bytes / (tsk->m_bits/8);
202
        if ((unsigned int)src_data.input_frames < tot_samples / tsk->m_chans) {
203
            int bytes = tot_samples * sizeof(float);
204
            src_data.data_in = (float *)realloc(src_data.data_in, bytes);
205
            src_data.data_out = (float *)realloc(src_data.data_out, 2 * bytes);
206
            src_data.input_frames = tot_samples / tsk->m_chans;
207
            // Available space for output
208
            src_data.output_frames = 2 * src_data.input_frames;
209
        }
210
        src_data.src_ratio = samplerate_ratio;
211
        src_data.end_of_input = 0;
212
        
213
        switch (tsk->m_bits) {
214
        case 16: {
215
            const short *sp = (const short *)tsk->m_buf;
216
            for (unsigned int i = 0; i < tot_samples; i++) {
217
                src_data.data_in[i] = *sp++;
218
            }
219
            break;
220
        }
221
        case 24:
222
        case 32:
223
        default:
224
            abort();
225
        }
226
        int ret = src_process(src_state, &src_data);
227
        if (ret) {
228
            LOGERR("src_process: " << src_strerror(ret) << endl);
229
            continue;
230
        }
231
        {
232
            static int cnt;
233
            if (cnt++ == 100) {
234
                LOGDEB("samplerate: " 
235
                       " qsize " << alsaqueue.qsize() << 
236
                       " ratio " << samplerate_ratio <<
237
                       " in " << src_data.input_frames << 
238
                       " consumed " << src_data.input_frames_used << 
239
                       " out " << src_data.output_frames_gen << endl);
240
                cnt = 0;
241
            }
242
        }
243
        tot_samples =  src_data.output_frames_gen * tsk->m_chans;
244
        if (src_data.output_frames_gen > src_data.input_frames) {
245
            tsk->m_bytes = tot_samples * (tsk->m_bits / 8);
246
            tsk->m_buf = (char *)realloc(tsk->m_buf, tsk->m_bytes);
247
            if (!tsk->m_buf) 
248
                abort();
249
        }
250
251
        // Output is always 16 bits lsb first for now. We should
252
        // probably dither the lsb ?
253
        tsk->m_bits = 16;
254
        {
255
#ifdef WORDS_BIGENDIAN
256
            unsigned char *ocp = (unsigned char *)tsk->m_buf;
257
            short val;
258
            unsigned char *icp = (unsigned char *)&val;
259
            for (unsigned int i = 0; i < tot_samples; i++) {
260
                val = src_data.data_out[i];;
261
                *ocp++ = icp[1];
262
                *ocp++ = icp[0];
263
            }
264
            tsk->m_bytes = ocp - tsk->m_buf;
265
#else
266
            short *sp = (short *)tsk->m_buf;
267
            for (unsigned int i = 0; i < tot_samples; i++) {
268
                *sp++ = src_data.data_out[i];
269
            }
270
            tsk->m_bytes = (char *)sp - tsk->m_buf;
271
#endif
272
        }
273
274
        if (!alsaqueue.put(tsk)) {
275
            LOGERR("alsaEater: queue put failed\n");
276
            return (void *)1;
277
        }
278
        if (alsaqueue.qsize() >= qit)
279
            qinit = true;
280
    }
281
}
282
283
AudioEater alsaAudioEater(AudioEater::BO_HOST, &audioEater);