vrshoot

diff 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
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/audio/stream.cc	Sat Feb 01 19:58:19 2014 +0200
     1.3 @@ -0,0 +1,380 @@
     1.4 +#include <stdio.h>
     1.5 +#include <stdint.h>
     1.6 +#include <assert.h>
     1.7 +#include "openal.h"
     1.8 +#include "stream.h"
     1.9 +#include "logger.h"
    1.10 +#include "timer.h"
    1.11 +#include "kiss_fft.h"
    1.12 +
    1.13 +struct FFTState {
    1.14 +	kiss_fft_cfg kiss;
    1.15 +	kiss_fft_cpx *inbuf, *outbuf;
    1.16 +	int nsamples;
    1.17 +};
    1.18 +
    1.19 +static ALenum alformat(AudioStreamBuffer *buf)
    1.20 +{
    1.21 +	return buf->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
    1.22 +}
    1.23 +
    1.24 +AudioStream::AudioStream()
    1.25 +{
    1.26 +	alsrc = 0;
    1.27 +	poll_interval = 25;
    1.28 +	done = true;
    1.29 +	loop = false;
    1.30 +	volume = 1.0;
    1.31 +
    1.32 +	freqhist = 0;
    1.33 +
    1.34 +	// by default disable FFT processing
    1.35 +	use_fft = false;
    1.36 +
    1.37 +	pthread_mutex_init(&mutex, 0);
    1.38 +}
    1.39 +
    1.40 +AudioStream::~AudioStream()
    1.41 +{
    1.42 +	stop();
    1.43 +}
    1.44 +
    1.45 +void AudioStream::enable_fft()
    1.46 +{
    1.47 +	use_fft = true;
    1.48 +}
    1.49 +
    1.50 +void AudioStream::disable_fft()
    1.51 +{
    1.52 +	use_fft = false;
    1.53 +}
    1.54 +
    1.55 +bool AudioStream::is_fft_enabled() const
    1.56 +{
    1.57 +	return use_fft;
    1.58 +}
    1.59 +
    1.60 +bool AudioStream::open(const char *fname)
    1.61 +{
    1.62 +	return false;
    1.63 +}
    1.64 +
    1.65 +void AudioStream::close()
    1.66 +{
    1.67 +}
    1.68 +
    1.69 +void AudioStream::set_volume(float vol)
    1.70 +{
    1.71 +	volume = vol;
    1.72 +
    1.73 +	pthread_mutex_lock(&mutex);
    1.74 +	if(alsrc) {
    1.75 +		alSourcef(alsrc, AL_GAIN, vol);
    1.76 +	}
    1.77 +	pthread_mutex_unlock(&mutex);
    1.78 +}
    1.79 +
    1.80 +float AudioStream::get_volume() const
    1.81 +{
    1.82 +	return volume;
    1.83 +}
    1.84 +
    1.85 +static void *thread_func(void *arg)
    1.86 +{
    1.87 +	AudioStream *astr = (AudioStream*)arg;
    1.88 +	astr->poll_loop();
    1.89 +	return 0;
    1.90 +}
    1.91 +
    1.92 +void AudioStream::play(AUDIO_PLAYMODE mode)
    1.93 +{
    1.94 +	loop = (mode == AUDIO_PLAYMODE_LOOP);
    1.95 +	done = false;
    1.96 +
    1.97 +	if(pthread_create(&play_thread, 0, thread_func, this) != 0) {
    1.98 +		error_log("failed to create music playback thread\n");
    1.99 +	}
   1.100 +}
   1.101 +
   1.102 +void AudioStream::stop()
   1.103 +{
   1.104 +	pthread_mutex_lock(&mutex);
   1.105 +
   1.106 +	if(alsrc) {
   1.107 +		done = true;
   1.108 +		alSourceStop(alsrc);
   1.109 +		printf("waiting for the music thread to stop\n");
   1.110 +		pthread_mutex_unlock(&mutex);
   1.111 +		pthread_join(play_thread, 0);
   1.112 +	} else {
   1.113 +		pthread_mutex_unlock(&mutex);
   1.114 +	}
   1.115 +}
   1.116 +
   1.117 +// gets an array of buffers and returns the index of the one matching id
   1.118 +static inline int find_buffer(unsigned int id, unsigned int *barr, int num)
   1.119 +{
   1.120 +	for(int i=0; i<num; i++) {
   1.121 +		if(barr[i] == id) {
   1.122 +			return i;
   1.123 +		}
   1.124 +	}
   1.125 +	return -1;
   1.126 +}
   1.127 +
   1.128 +
   1.129 +static int queued_idx_list[AUDIO_NUM_BUFFERS];
   1.130 +static int queued_idx_head = 0;
   1.131 +static int queued_idx_tail = 0;
   1.132 +
   1.133 +#define BUFQ_UNQUEUE() \
   1.134 +	do { \
   1.135 +		queued_idx_tail = (queued_idx_tail + 1) % AUDIO_NUM_BUFFERS; \
   1.136 +	} while(0)
   1.137 +
   1.138 +
   1.139 +#define BUFQ_QUEUE(idx)	\
   1.140 +	do { \
   1.141 +		queued_idx_head = (queued_idx_head + 1) % AUDIO_NUM_BUFFERS; \
   1.142 +		queued_idx_list[queued_idx_head] = idx; \
   1.143 +	} while(0)
   1.144 +
   1.145 +// thread function
   1.146 +void AudioStream::poll_loop()
   1.147 +{
   1.148 +	long prev_msec = -1000;
   1.149 +	unsigned int albuf[AUDIO_NUM_BUFFERS];
   1.150 +	int freqbins[AUDIO_NUM_BUFFERS][AUDIO_FFT_BINS];
   1.151 +
   1.152 +	pthread_mutex_lock(&mutex);
   1.153 +	alGenSources(1, &alsrc);
   1.154 +	alSourcei(alsrc, AL_LOOPING, AL_FALSE);
   1.155 +	alSourcef(alsrc, AL_GAIN, volume);
   1.156 +	alGenBuffers(AUDIO_NUM_BUFFERS, albuf);
   1.157 +	AudioStreamBuffer *buf = new AudioStreamBuffer;
   1.158 +
   1.159 +	FFTState fft;
   1.160 +	fft.kiss = kiss_fft_alloc(AUDIO_FFT_SAMPLES, 0, 0, 0);
   1.161 +	assert(fft.kiss);
   1.162 +	fft.inbuf = new kiss_fft_cpx[AUDIO_FFT_SAMPLES];
   1.163 +	fft.outbuf = new kiss_fft_cpx[AUDIO_FFT_SAMPLES];
   1.164 +	assert(fft.inbuf && fft.outbuf);
   1.165 +	fft.nsamples = AUDIO_FFT_SAMPLES;
   1.166 +
   1.167 +	// zero out the inbuf array to get rid of the imaginary parts
   1.168 +	memset(fft.inbuf, 0, AUDIO_FFT_SAMPLES * sizeof *fft.inbuf);
   1.169 +
   1.170 +	for(int i=0; i<AUDIO_NUM_BUFFERS; i++) {
   1.171 +		if(more_samples(buf)) {
   1.172 +			int bufsz = buf->num_samples * buf->channels * 2;       // 2 is for 16bit samples
   1.173 +			alBufferData(albuf[i], alformat(buf), buf->samples, bufsz, buf->sample_rate);
   1.174 +
   1.175 +			if(alGetError()) {
   1.176 +				fprintf(stderr, "failed to load sample data into OpenAL buffer\n");
   1.177 +			}
   1.178 +
   1.179 +			alSourceQueueBuffers(alsrc, 1, albuf + i);
   1.180 +			BUFQ_QUEUE(i);
   1.181 +
   1.182 +			if(alGetError()) {
   1.183 +				fprintf(stderr, "failed to start streaming audio buffers\n");
   1.184 +			}
   1.185 +
   1.186 +			// also calculate the frequencies
   1.187 +			calc_freq(buf, freqbins[i], &fft);
   1.188 +		} else {
   1.189 +			break;
   1.190 +		}
   1.191 +	}
   1.192 +
   1.193 +	// start playback
   1.194 +	alSourcePlay(alsrc);
   1.195 +	while(!done) {
   1.196 +		// XXX this doesn't work
   1.197 +		/*
   1.198 +		// first let's figure out which buffer is currently playing
   1.199 +		int cur_buf;
   1.200 +		alGetSourcei(alsrc, AL_BUFFER, &cur_buf);
   1.201 +		int cur_buf_idx = find_buffer(cur_buf, albuf, AUDIO_NUM_BUFFERS);
   1.202 +
   1.203 +		// make the fft histogram pointer point to the correct frequency bin array
   1.204 +		freqhist = cur_buf_idx != -1 ? freqbins[cur_buf_idx] : 0;
   1.205 +		if(!freqhist) {
   1.206 +			debug_log("skata\n");
   1.207 +		}
   1.208 +		*/
   1.209 +
   1.210 +		/* find out how many (if any) of the queued buffers are
   1.211 +		* done, and free to be reused.
   1.212 +		*/
   1.213 +		int num_buf_done;
   1.214 +		alGetSourcei(alsrc, AL_BUFFERS_PROCESSED, &num_buf_done);
   1.215 +		for(int i=0; i<num_buf_done; i++) {
   1.216 +			int err;
   1.217 +			// unqueue a buffer...
   1.218 +			unsigned int buf_id;
   1.219 +			alSourceUnqueueBuffers(alsrc, 1, &buf_id);
   1.220 +
   1.221 +			if((err = alGetError())) {
   1.222 +				fprintf(stderr, "failed to unqueue used buffer (error: %x)\n", err);
   1.223 +				num_buf_done = i;
   1.224 +				break;
   1.225 +			}
   1.226 +			BUFQ_UNQUEUE();
   1.227 +
   1.228 +			// find out which one of our al buffers we just unqueued
   1.229 +			int bidx = find_buffer(buf_id, albuf, AUDIO_NUM_BUFFERS);
   1.230 +			assert(bidx != -1);
   1.231 +
   1.232 +			int looping;
   1.233 +
   1.234 +			alGetSourcei(alsrc, AL_LOOPING, &looping);
   1.235 +			assert(looping == AL_FALSE);
   1.236 +			/*if((unsigned int)cur_buf == buf_id) {
   1.237 +				continue;
   1.238 +			}*/
   1.239 +
   1.240 +			// if there are more data, fill it up and requeue it
   1.241 +			if(more_samples(buf)) {
   1.242 +				int bufsz = buf->num_samples * buf->channels * 2;       // 2 is for 16bit samples
   1.243 +				alBufferData(buf_id, alformat(buf), buf->samples, bufsz, buf->sample_rate);
   1.244 +				if((err = alGetError())) {
   1.245 +					fprintf(stderr, "failed to load sample data into OpenAL buffer (error: %x)\n", err);
   1.246 +				}
   1.247 +
   1.248 +				alSourceQueueBuffers(alsrc, 1, &buf_id);
   1.249 +				if(alGetError()) {
   1.250 +					fprintf(stderr, "failed to start streaming audio buffers\n");
   1.251 +				}
   1.252 +				BUFQ_QUEUE(bidx);
   1.253 +
   1.254 +				// also calculate the frequencies if required
   1.255 +				if(use_fft) {
   1.256 +					calc_freq(buf, freqbins[bidx], &fft);
   1.257 +				}
   1.258 +			} else {
   1.259 +				// no more data...
   1.260 +				if(loop) {
   1.261 +					rewind();
   1.262 +				} else {
   1.263 +					done = true;
   1.264 +				}
   1.265 +			}
   1.266 +		}
   1.267 +		if(use_fft) {
   1.268 +			freqhist = freqbins[queued_idx_list[queued_idx_tail]];
   1.269 +		}
   1.270 +
   1.271 +		if(num_buf_done) {
   1.272 +			// make sure playback didn't stop
   1.273 +			int state;
   1.274 +			alGetSourcei(alsrc, AL_SOURCE_STATE, &state);
   1.275 +			if(state != AL_PLAYING) {
   1.276 +				alSourcePlay(alsrc);
   1.277 +			}
   1.278 +		}
   1.279 +
   1.280 +		pthread_mutex_unlock(&mutex);
   1.281 +		long msec = get_time_msec();
   1.282 +		long dt = msec - prev_msec;
   1.283 +		prev_msec = msec;
   1.284 +
   1.285 +		if(dt < poll_interval - 5) {
   1.286 +			sleep_msec(poll_interval - dt);
   1.287 +		} else {
   1.288 +			sched_yield();
   1.289 +		}
   1.290 +		pthread_mutex_lock(&mutex);
   1.291 +	}
   1.292 +
   1.293 +
   1.294 +	// done with the data, wait for the source to stop playing before cleanup
   1.295 +	int state;
   1.296 +	while(alGetSourcei(alsrc, AL_SOURCE_STATE, &state), state == AL_PLAYING) {
   1.297 +		sched_yield();
   1.298 +	}
   1.299 +
   1.300 +	freqhist = 0;
   1.301 +
   1.302 +	alDeleteBuffers(AUDIO_NUM_BUFFERS, albuf);
   1.303 +	alDeleteSources(1, &alsrc);
   1.304 +	alsrc = 0;
   1.305 +	pthread_mutex_unlock(&mutex);
   1.306 +
   1.307 +	delete buf;
   1.308 +
   1.309 +	delete [] fft.inbuf;
   1.310 +	delete [] fft.outbuf;
   1.311 +	kiss_fft_free(fft.kiss);
   1.312 +}
   1.313 +
   1.314 +int AudioStream::freq_count(int bin) const
   1.315 +{
   1.316 +	if(!freqhist || !use_fft || bin < 0 || bin >= AUDIO_BUFFER_SAMPLES) {
   1.317 +		return 0;
   1.318 +	}
   1.319 +	return freqhist[bin];
   1.320 +}
   1.321 +
   1.322 +#define NORM_FACTOR		(1.0f / (float)AUDIO_FFT_SAMPLES)
   1.323 +float AudioStream::freq_normalized(int bin) const
   1.324 +{
   1.325 +	// TODO remove the fudge factor
   1.326 +	return freq_count(bin) * NORM_FACTOR * 0.25;
   1.327 +}
   1.328 +
   1.329 +// frequency range in hertz
   1.330 +int AudioStream::freq_count(int range_start, int range_end) const
   1.331 +{
   1.332 +	// NOTE this will probably be something like sampling freq / num-bins Hz per bin...
   1.333 +	return 0;	// TODO
   1.334 +}
   1.335 +
   1.336 +// TODO ok this might be inefficient, copying the data around a lot, optimize later
   1.337 +void AudioStream::calc_freq(AudioStreamBuffer *buf, int *bins, FFTState *fft)
   1.338 +{
   1.339 +	kiss_fft_cpx *inptr = fft->inbuf;
   1.340 +	int16_t *samples = (int16_t*)buf->samples;
   1.341 +	for(int i=0; i<AUDIO_BUFFER_SAMPLES; i++) {
   1.342 +
   1.343 +		inptr->i = 0;
   1.344 +		if(i < buf->num_samples) {
   1.345 +			int left = samples[i * 2];
   1.346 +			int right = samples[i * 2 + 1];
   1.347 +
   1.348 +			(inptr++)->r = (left + right) / 2;
   1.349 +		} else {
   1.350 +			(inptr++)->r = 0;
   1.351 +		}
   1.352 +	}
   1.353 +
   1.354 +	kiss_fft(fft->kiss, fft->inbuf, fft->outbuf);
   1.355 +
   1.356 +	// then copy all the relevant data to the bins array
   1.357 +	int num_out_samples = AUDIO_BUFFER_SAMPLES / 2;
   1.358 +	int samples_per_bin = num_out_samples / AUDIO_FFT_BINS;
   1.359 +
   1.360 +	long abins[AUDIO_FFT_BINS];
   1.361 +
   1.362 +	int prev_bidx = -1;
   1.363 +	// ignore the DC bin (0)
   1.364 +	for(int i=1; i<num_out_samples; i++) {
   1.365 +		int bidx = i * AUDIO_FFT_BINS / num_out_samples;
   1.366 +		float x = fft->outbuf[i].r;
   1.367 +		float y = fft->outbuf[i].i;
   1.368 +		int val = x * x + y * y;
   1.369 +
   1.370 +		if(bidx != prev_bidx) {
   1.371 +			abins[bidx] = val;
   1.372 +			prev_bidx = bidx;
   1.373 +		} else {
   1.374 +			abins[bidx] += val;
   1.375 +		}
   1.376 +	}
   1.377 +
   1.378 +	for(int i=0; i<AUDIO_FFT_BINS; i++) {
   1.379 +		long res = abins[i] / (long)samples_per_bin;
   1.380 +		bins[i] = res;
   1.381 +		assert(bins[i] == res);
   1.382 +	}
   1.383 +}