vrshoot

annotate src/audio/stream.cc @ 0:b2f14e535253

initial commit
author John Tsiombikas <nuclear@member.fsf.org>
date Sat, 01 Feb 2014 19:58:19 +0200
parents
children
rev   line source
nuclear@0 1 #include <stdio.h>
nuclear@0 2 #include <stdint.h>
nuclear@0 3 #include <assert.h>
nuclear@0 4 #include "openal.h"
nuclear@0 5 #include "stream.h"
nuclear@0 6 #include "logger.h"
nuclear@0 7 #include "timer.h"
nuclear@0 8 #include "kiss_fft.h"
nuclear@0 9
nuclear@0 10 struct FFTState {
nuclear@0 11 kiss_fft_cfg kiss;
nuclear@0 12 kiss_fft_cpx *inbuf, *outbuf;
nuclear@0 13 int nsamples;
nuclear@0 14 };
nuclear@0 15
nuclear@0 16 static ALenum alformat(AudioStreamBuffer *buf)
nuclear@0 17 {
nuclear@0 18 return buf->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
nuclear@0 19 }
nuclear@0 20
nuclear@0 21 AudioStream::AudioStream()
nuclear@0 22 {
nuclear@0 23 alsrc = 0;
nuclear@0 24 poll_interval = 25;
nuclear@0 25 done = true;
nuclear@0 26 loop = false;
nuclear@0 27 volume = 1.0;
nuclear@0 28
nuclear@0 29 freqhist = 0;
nuclear@0 30
nuclear@0 31 // by default disable FFT processing
nuclear@0 32 use_fft = false;
nuclear@0 33
nuclear@0 34 pthread_mutex_init(&mutex, 0);
nuclear@0 35 }
nuclear@0 36
nuclear@0 37 AudioStream::~AudioStream()
nuclear@0 38 {
nuclear@0 39 stop();
nuclear@0 40 }
nuclear@0 41
nuclear@0 42 void AudioStream::enable_fft()
nuclear@0 43 {
nuclear@0 44 use_fft = true;
nuclear@0 45 }
nuclear@0 46
nuclear@0 47 void AudioStream::disable_fft()
nuclear@0 48 {
nuclear@0 49 use_fft = false;
nuclear@0 50 }
nuclear@0 51
nuclear@0 52 bool AudioStream::is_fft_enabled() const
nuclear@0 53 {
nuclear@0 54 return use_fft;
nuclear@0 55 }
nuclear@0 56
nuclear@0 57 bool AudioStream::open(const char *fname)
nuclear@0 58 {
nuclear@0 59 return false;
nuclear@0 60 }
nuclear@0 61
nuclear@0 62 void AudioStream::close()
nuclear@0 63 {
nuclear@0 64 }
nuclear@0 65
nuclear@0 66 void AudioStream::set_volume(float vol)
nuclear@0 67 {
nuclear@0 68 volume = vol;
nuclear@0 69
nuclear@0 70 pthread_mutex_lock(&mutex);
nuclear@0 71 if(alsrc) {
nuclear@0 72 alSourcef(alsrc, AL_GAIN, vol);
nuclear@0 73 }
nuclear@0 74 pthread_mutex_unlock(&mutex);
nuclear@0 75 }
nuclear@0 76
nuclear@0 77 float AudioStream::get_volume() const
nuclear@0 78 {
nuclear@0 79 return volume;
nuclear@0 80 }
nuclear@0 81
nuclear@0 82 static void *thread_func(void *arg)
nuclear@0 83 {
nuclear@0 84 AudioStream *astr = (AudioStream*)arg;
nuclear@0 85 astr->poll_loop();
nuclear@0 86 return 0;
nuclear@0 87 }
nuclear@0 88
nuclear@0 89 void AudioStream::play(AUDIO_PLAYMODE mode)
nuclear@0 90 {
nuclear@0 91 loop = (mode == AUDIO_PLAYMODE_LOOP);
nuclear@0 92 done = false;
nuclear@0 93
nuclear@0 94 if(pthread_create(&play_thread, 0, thread_func, this) != 0) {
nuclear@0 95 error_log("failed to create music playback thread\n");
nuclear@0 96 }
nuclear@0 97 }
nuclear@0 98
nuclear@0 99 void AudioStream::stop()
nuclear@0 100 {
nuclear@0 101 pthread_mutex_lock(&mutex);
nuclear@0 102
nuclear@0 103 if(alsrc) {
nuclear@0 104 done = true;
nuclear@0 105 alSourceStop(alsrc);
nuclear@0 106 printf("waiting for the music thread to stop\n");
nuclear@0 107 pthread_mutex_unlock(&mutex);
nuclear@0 108 pthread_join(play_thread, 0);
nuclear@0 109 } else {
nuclear@0 110 pthread_mutex_unlock(&mutex);
nuclear@0 111 }
nuclear@0 112 }
nuclear@0 113
nuclear@0 114 // gets an array of buffers and returns the index of the one matching id
nuclear@0 115 static inline int find_buffer(unsigned int id, unsigned int *barr, int num)
nuclear@0 116 {
nuclear@0 117 for(int i=0; i<num; i++) {
nuclear@0 118 if(barr[i] == id) {
nuclear@0 119 return i;
nuclear@0 120 }
nuclear@0 121 }
nuclear@0 122 return -1;
nuclear@0 123 }
nuclear@0 124
nuclear@0 125
nuclear@0 126 static int queued_idx_list[AUDIO_NUM_BUFFERS];
nuclear@0 127 static int queued_idx_head = 0;
nuclear@0 128 static int queued_idx_tail = 0;
nuclear@0 129
nuclear@0 130 #define BUFQ_UNQUEUE() \
nuclear@0 131 do { \
nuclear@0 132 queued_idx_tail = (queued_idx_tail + 1) % AUDIO_NUM_BUFFERS; \
nuclear@0 133 } while(0)
nuclear@0 134
nuclear@0 135
nuclear@0 136 #define BUFQ_QUEUE(idx) \
nuclear@0 137 do { \
nuclear@0 138 queued_idx_head = (queued_idx_head + 1) % AUDIO_NUM_BUFFERS; \
nuclear@0 139 queued_idx_list[queued_idx_head] = idx; \
nuclear@0 140 } while(0)
nuclear@0 141
nuclear@0 142 // thread function
nuclear@0 143 void AudioStream::poll_loop()
nuclear@0 144 {
nuclear@0 145 long prev_msec = -1000;
nuclear@0 146 unsigned int albuf[AUDIO_NUM_BUFFERS];
nuclear@0 147 int freqbins[AUDIO_NUM_BUFFERS][AUDIO_FFT_BINS];
nuclear@0 148
nuclear@0 149 pthread_mutex_lock(&mutex);
nuclear@0 150 alGenSources(1, &alsrc);
nuclear@0 151 alSourcei(alsrc, AL_LOOPING, AL_FALSE);
nuclear@0 152 alSourcef(alsrc, AL_GAIN, volume);
nuclear@0 153 alGenBuffers(AUDIO_NUM_BUFFERS, albuf);
nuclear@0 154 AudioStreamBuffer *buf = new AudioStreamBuffer;
nuclear@0 155
nuclear@0 156 FFTState fft;
nuclear@0 157 fft.kiss = kiss_fft_alloc(AUDIO_FFT_SAMPLES, 0, 0, 0);
nuclear@0 158 assert(fft.kiss);
nuclear@0 159 fft.inbuf = new kiss_fft_cpx[AUDIO_FFT_SAMPLES];
nuclear@0 160 fft.outbuf = new kiss_fft_cpx[AUDIO_FFT_SAMPLES];
nuclear@0 161 assert(fft.inbuf && fft.outbuf);
nuclear@0 162 fft.nsamples = AUDIO_FFT_SAMPLES;
nuclear@0 163
nuclear@0 164 // zero out the inbuf array to get rid of the imaginary parts
nuclear@0 165 memset(fft.inbuf, 0, AUDIO_FFT_SAMPLES * sizeof *fft.inbuf);
nuclear@0 166
nuclear@0 167 for(int i=0; i<AUDIO_NUM_BUFFERS; i++) {
nuclear@0 168 if(more_samples(buf)) {
nuclear@0 169 int bufsz = buf->num_samples * buf->channels * 2; // 2 is for 16bit samples
nuclear@0 170 alBufferData(albuf[i], alformat(buf), buf->samples, bufsz, buf->sample_rate);
nuclear@0 171
nuclear@0 172 if(alGetError()) {
nuclear@0 173 fprintf(stderr, "failed to load sample data into OpenAL buffer\n");
nuclear@0 174 }
nuclear@0 175
nuclear@0 176 alSourceQueueBuffers(alsrc, 1, albuf + i);
nuclear@0 177 BUFQ_QUEUE(i);
nuclear@0 178
nuclear@0 179 if(alGetError()) {
nuclear@0 180 fprintf(stderr, "failed to start streaming audio buffers\n");
nuclear@0 181 }
nuclear@0 182
nuclear@0 183 // also calculate the frequencies
nuclear@0 184 calc_freq(buf, freqbins[i], &fft);
nuclear@0 185 } else {
nuclear@0 186 break;
nuclear@0 187 }
nuclear@0 188 }
nuclear@0 189
nuclear@0 190 // start playback
nuclear@0 191 alSourcePlay(alsrc);
nuclear@0 192 while(!done) {
nuclear@0 193 // XXX this doesn't work
nuclear@0 194 /*
nuclear@0 195 // first let's figure out which buffer is currently playing
nuclear@0 196 int cur_buf;
nuclear@0 197 alGetSourcei(alsrc, AL_BUFFER, &cur_buf);
nuclear@0 198 int cur_buf_idx = find_buffer(cur_buf, albuf, AUDIO_NUM_BUFFERS);
nuclear@0 199
nuclear@0 200 // make the fft histogram pointer point to the correct frequency bin array
nuclear@0 201 freqhist = cur_buf_idx != -1 ? freqbins[cur_buf_idx] : 0;
nuclear@0 202 if(!freqhist) {
nuclear@0 203 debug_log("skata\n");
nuclear@0 204 }
nuclear@0 205 */
nuclear@0 206
nuclear@0 207 /* find out how many (if any) of the queued buffers are
nuclear@0 208 * done, and free to be reused.
nuclear@0 209 */
nuclear@0 210 int num_buf_done;
nuclear@0 211 alGetSourcei(alsrc, AL_BUFFERS_PROCESSED, &num_buf_done);
nuclear@0 212 for(int i=0; i<num_buf_done; i++) {
nuclear@0 213 int err;
nuclear@0 214 // unqueue a buffer...
nuclear@0 215 unsigned int buf_id;
nuclear@0 216 alSourceUnqueueBuffers(alsrc, 1, &buf_id);
nuclear@0 217
nuclear@0 218 if((err = alGetError())) {
nuclear@0 219 fprintf(stderr, "failed to unqueue used buffer (error: %x)\n", err);
nuclear@0 220 num_buf_done = i;
nuclear@0 221 break;
nuclear@0 222 }
nuclear@0 223 BUFQ_UNQUEUE();
nuclear@0 224
nuclear@0 225 // find out which one of our al buffers we just unqueued
nuclear@0 226 int bidx = find_buffer(buf_id, albuf, AUDIO_NUM_BUFFERS);
nuclear@0 227 assert(bidx != -1);
nuclear@0 228
nuclear@0 229 int looping;
nuclear@0 230
nuclear@0 231 alGetSourcei(alsrc, AL_LOOPING, &looping);
nuclear@0 232 assert(looping == AL_FALSE);
nuclear@0 233 /*if((unsigned int)cur_buf == buf_id) {
nuclear@0 234 continue;
nuclear@0 235 }*/
nuclear@0 236
nuclear@0 237 // if there are more data, fill it up and requeue it
nuclear@0 238 if(more_samples(buf)) {
nuclear@0 239 int bufsz = buf->num_samples * buf->channels * 2; // 2 is for 16bit samples
nuclear@0 240 alBufferData(buf_id, alformat(buf), buf->samples, bufsz, buf->sample_rate);
nuclear@0 241 if((err = alGetError())) {
nuclear@0 242 fprintf(stderr, "failed to load sample data into OpenAL buffer (error: %x)\n", err);
nuclear@0 243 }
nuclear@0 244
nuclear@0 245 alSourceQueueBuffers(alsrc, 1, &buf_id);
nuclear@0 246 if(alGetError()) {
nuclear@0 247 fprintf(stderr, "failed to start streaming audio buffers\n");
nuclear@0 248 }
nuclear@0 249 BUFQ_QUEUE(bidx);
nuclear@0 250
nuclear@0 251 // also calculate the frequencies if required
nuclear@0 252 if(use_fft) {
nuclear@0 253 calc_freq(buf, freqbins[bidx], &fft);
nuclear@0 254 }
nuclear@0 255 } else {
nuclear@0 256 // no more data...
nuclear@0 257 if(loop) {
nuclear@0 258 rewind();
nuclear@0 259 } else {
nuclear@0 260 done = true;
nuclear@0 261 }
nuclear@0 262 }
nuclear@0 263 }
nuclear@0 264 if(use_fft) {
nuclear@0 265 freqhist = freqbins[queued_idx_list[queued_idx_tail]];
nuclear@0 266 }
nuclear@0 267
nuclear@0 268 if(num_buf_done) {
nuclear@0 269 // make sure playback didn't stop
nuclear@0 270 int state;
nuclear@0 271 alGetSourcei(alsrc, AL_SOURCE_STATE, &state);
nuclear@0 272 if(state != AL_PLAYING) {
nuclear@0 273 alSourcePlay(alsrc);
nuclear@0 274 }
nuclear@0 275 }
nuclear@0 276
nuclear@0 277 pthread_mutex_unlock(&mutex);
nuclear@0 278 long msec = get_time_msec();
nuclear@0 279 long dt = msec - prev_msec;
nuclear@0 280 prev_msec = msec;
nuclear@0 281
nuclear@0 282 if(dt < poll_interval - 5) {
nuclear@0 283 sleep_msec(poll_interval - dt);
nuclear@0 284 } else {
nuclear@0 285 sched_yield();
nuclear@0 286 }
nuclear@0 287 pthread_mutex_lock(&mutex);
nuclear@0 288 }
nuclear@0 289
nuclear@0 290
nuclear@0 291 // done with the data, wait for the source to stop playing before cleanup
nuclear@0 292 int state;
nuclear@0 293 while(alGetSourcei(alsrc, AL_SOURCE_STATE, &state), state == AL_PLAYING) {
nuclear@0 294 sched_yield();
nuclear@0 295 }
nuclear@0 296
nuclear@0 297 freqhist = 0;
nuclear@0 298
nuclear@0 299 alDeleteBuffers(AUDIO_NUM_BUFFERS, albuf);
nuclear@0 300 alDeleteSources(1, &alsrc);
nuclear@0 301 alsrc = 0;
nuclear@0 302 pthread_mutex_unlock(&mutex);
nuclear@0 303
nuclear@0 304 delete buf;
nuclear@0 305
nuclear@0 306 delete [] fft.inbuf;
nuclear@0 307 delete [] fft.outbuf;
nuclear@0 308 kiss_fft_free(fft.kiss);
nuclear@0 309 }
nuclear@0 310
nuclear@0 311 int AudioStream::freq_count(int bin) const
nuclear@0 312 {
nuclear@0 313 if(!freqhist || !use_fft || bin < 0 || bin >= AUDIO_BUFFER_SAMPLES) {
nuclear@0 314 return 0;
nuclear@0 315 }
nuclear@0 316 return freqhist[bin];
nuclear@0 317 }
nuclear@0 318
nuclear@0 319 #define NORM_FACTOR (1.0f / (float)AUDIO_FFT_SAMPLES)
nuclear@0 320 float AudioStream::freq_normalized(int bin) const
nuclear@0 321 {
nuclear@0 322 // TODO remove the fudge factor
nuclear@0 323 return freq_count(bin) * NORM_FACTOR * 0.25;
nuclear@0 324 }
nuclear@0 325
nuclear@0 326 // frequency range in hertz
nuclear@0 327 int AudioStream::freq_count(int range_start, int range_end) const
nuclear@0 328 {
nuclear@0 329 // NOTE this will probably be something like sampling freq / num-bins Hz per bin...
nuclear@0 330 return 0; // TODO
nuclear@0 331 }
nuclear@0 332
nuclear@0 333 // TODO ok this might be inefficient, copying the data around a lot, optimize later
nuclear@0 334 void AudioStream::calc_freq(AudioStreamBuffer *buf, int *bins, FFTState *fft)
nuclear@0 335 {
nuclear@0 336 kiss_fft_cpx *inptr = fft->inbuf;
nuclear@0 337 int16_t *samples = (int16_t*)buf->samples;
nuclear@0 338 for(int i=0; i<AUDIO_BUFFER_SAMPLES; i++) {
nuclear@0 339
nuclear@0 340 inptr->i = 0;
nuclear@0 341 if(i < buf->num_samples) {
nuclear@0 342 int left = samples[i * 2];
nuclear@0 343 int right = samples[i * 2 + 1];
nuclear@0 344
nuclear@0 345 (inptr++)->r = (left + right) / 2;
nuclear@0 346 } else {
nuclear@0 347 (inptr++)->r = 0;
nuclear@0 348 }
nuclear@0 349 }
nuclear@0 350
nuclear@0 351 kiss_fft(fft->kiss, fft->inbuf, fft->outbuf);
nuclear@0 352
nuclear@0 353 // then copy all the relevant data to the bins array
nuclear@0 354 int num_out_samples = AUDIO_BUFFER_SAMPLES / 2;
nuclear@0 355 int samples_per_bin = num_out_samples / AUDIO_FFT_BINS;
nuclear@0 356
nuclear@0 357 long abins[AUDIO_FFT_BINS];
nuclear@0 358
nuclear@0 359 int prev_bidx = -1;
nuclear@0 360 // ignore the DC bin (0)
nuclear@0 361 for(int i=1; i<num_out_samples; i++) {
nuclear@0 362 int bidx = i * AUDIO_FFT_BINS / num_out_samples;
nuclear@0 363 float x = fft->outbuf[i].r;
nuclear@0 364 float y = fft->outbuf[i].i;
nuclear@0 365 int val = x * x + y * y;
nuclear@0 366
nuclear@0 367 if(bidx != prev_bidx) {
nuclear@0 368 abins[bidx] = val;
nuclear@0 369 prev_bidx = bidx;
nuclear@0 370 } else {
nuclear@0 371 abins[bidx] += val;
nuclear@0 372 }
nuclear@0 373 }
nuclear@0 374
nuclear@0 375 for(int i=0; i<AUDIO_FFT_BINS; i++) {
nuclear@0 376 long res = abins[i] / (long)samples_per_bin;
nuclear@0 377 bins[i] = res;
nuclear@0 378 assert(bins[i] == res);
nuclear@0 379 }
nuclear@0 380 }