/* * Copyright (C) 2006 Stas Sergeev * * The below copyright strings have to be distributed unchanged together * with this file. This prefix can not be modified or separated. */ /* * 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. */ /* * Purpose: Sound midlayer. Resampling, converting and all the other hard work. * * Author: Stas Sergeev. * * TODO: Add the ADPCM processing. * TODO: Add the PDM processing for PC-Speaker. */ #include "emu.h" #include "utilities.h" #include "ringbuf.h" #include "timers.h" #include "sound/sound.h" #include #include #include #include #include #define SND_BUFFER_SIZE 100000 /* enough to hold 1.1s of 44100/stereo */ #define BUFFER_DELAY 40000.0 #define MAX_BUFFER_DELAY (BUFFER_DELAY * 10) #define INIT_BUFFER_DELAY (BUFFER_DELAY * 2) #define MIN_BUFFER_DELAY (BUFFER_DELAY) #define MAX_BUFFER_PERIOD (MAX_BUFFER_DELAY - MIN_BUFFER_DELAY) #define MIN_GUARD_SIZE 1024 #define MIN_READ_GUARD_PERIOD (1000000 * MIN_GUARD_SIZE / (2 * 44100)) #define WR_BUFFER_LW (BUFFER_DELAY / 2) #define MIN_READ_DELAY (MIN_BUFFER_DELAY + MIN_READ_GUARD_PERIOD) enum { SNDBUF_STATE_INACTIVE, SNDBUF_STATE_PLAYING, SNDBUF_STATE_FLUSHING, SNDBUF_STATE_STALLED, }; struct sample { int format; double tstamp; unsigned char data[2]; }; static const struct sample mute_samp = { PCM_FORMAT_NONE, 0, {0, 0} }; struct stream { int channels; struct rng_s buffer; int buf_cnt; int state; int flags; int stretch:1; int prepared:1; double start_time; double stop_time; double stretch_per; double stretch_tot; /* for the raw channels heuristic */ double raw_speed_adj; double last_adj_time; double last_fillup; /* --- */ char *name; int id; }; #define MAX_STREAMS 10 #define MAX_PLAYERS 10 struct pcm_player_wr { struct pcm_player player; double time; int opened:1; int last_cnt[MAX_STREAMS]; int last_idx[MAX_STREAMS]; double last_tstamp[MAX_STREAMS]; }; struct pcm_struct { struct stream stream[MAX_STREAMS]; int num_streams; pthread_mutex_t strm_mtx; pthread_mutex_t time_mtx; struct pcm_player_wr players[MAX_PLAYERS]; int num_players; int playing; double time; } pcm; int pcm_init(void) { int i; S_printf("PCM: init\n"); pthread_mutex_init(&pcm.strm_mtx, NULL); pthread_mutex_init(&pcm.time_mtx, NULL); #ifdef USE_DL_PLUGINS #ifdef SDL_SUPPORT load_plugin("sdl"); #endif #ifdef USE_ALSA load_plugin("alsa"); #endif #ifdef USE_SNDFILE load_plugin("sndfile"); #endif #ifdef USE_FLUIDSYNTH load_plugin("fluidsynth"); #endif #endif for (i = 0; i < pcm.num_players; i++) { struct pcm_player_wr *p = &pcm.players[i]; if (p->player.open) p->opened = p->player.open(p->player.arg); else p->opened = 1; if (!p->opened) { S_printf("PCM: \"%s\" failed to open\n", p->player.name); } } return 1; } static void pcm_clear_stream(int strm_idx) { pcm.stream[strm_idx].buf_cnt += rng_count(&pcm.stream[strm_idx].buffer); rng_clear(&pcm.stream[strm_idx].buffer); } static void pcm_reset_stream(int strm_idx) { pcm_clear_stream(strm_idx); if (pcm.stream[strm_idx].state != SNDBUF_STATE_FLUSHING) pcm.stream[strm_idx].prepared = 0; pcm.stream[strm_idx].state = SNDBUF_STATE_INACTIVE; pcm.stream[strm_idx].stretch = 0; pcm.stream[strm_idx].stretch_per = 0; pcm.stream[strm_idx].stretch_tot = 0; } int pcm_allocate_stream(int channels, char *name, int id) { int index; if (pcm.num_streams >= MAX_STREAMS) { error("PCM: stream pool exhausted, max=%i\n", MAX_STREAMS); return -1; } index = pcm.num_streams++; rng_init(&pcm.stream[index].buffer, SND_BUFFER_SIZE, sizeof(struct sample)); pcm.stream[index].buf_cnt = 0; pcm.stream[index].channels = channels; pcm.stream[index].name = name; pcm.stream[index].id = id; pcm_reset_stream(index); S_printf("PCM: Stream %i allocated for \"%s\"\n", index, name); return index; } void pcm_set_flag(int strm_idx, int flag) { if (pcm.stream[strm_idx].flags & flag) return; S_printf("PCM: setting flag %x for stream %i (%s)\n", flag, strm_idx, pcm.stream[strm_idx].name); pcm.stream[strm_idx].flags |= flag; if (pcm.stream[strm_idx].flags & PCM_FLAG_RAW) pcm.stream[strm_idx].raw_speed_adj = 1.0; } void pcm_clear_flag(int strm_idx, int flag) { if (!(pcm.stream[strm_idx].flags & flag)) return; S_printf("PCM: clearing flag %x for stream %i (%s)\n", flag, strm_idx, pcm.stream[strm_idx].name); pcm.stream[strm_idx].flags &= ~flag; } int pcm_format_size(int format) { switch (format) { case PCM_FORMAT_U8: case PCM_FORMAT_S8: return 1; case PCM_FORMAT_U16_LE: case PCM_FORMAT_S16_LE: return 2; default: error("PCM: format %i is not supported\n", format); return 0; } } static int pcm_get_format8(int is_signed) { return is_signed ? PCM_FORMAT_S8 : PCM_FORMAT_U8; } static int pcm_get_format16(int is_signed) { return is_signed ? PCM_FORMAT_S16_LE : PCM_FORMAT_U16_LE; } int pcm_get_format(int is_16, int is_signed) { return is_16 ? pcm_get_format16(is_signed) : pcm_get_format8(is_signed); } static int cutoff(int val, int min, int max) { if (val < min) return min; if (val > max) return max; return val; } int pcm_samp_cutoff(int val, int format) { switch (format) { case PCM_FORMAT_U8: return cutoff(val, 0, UCHAR_MAX); case PCM_FORMAT_S8: return cutoff(val, SCHAR_MIN, SCHAR_MAX); case PCM_FORMAT_U16_LE: return cutoff(val, 0, USHRT_MAX); case PCM_FORMAT_S16_LE: return cutoff(val, SHRT_MIN, SHRT_MAX); default: error("PCM: format %i is not supported\n", format); return 0; } } #define UC2SS(v) ((*(unsigned char *)(v) - 128) * 256) #define SC2SS(v) (*(signed char *)(v) * 256) #define US2SS(v) (*(unsigned short *)(v) - 32768) #define SS2UC(v) ((unsigned char)(((v) + 32768) / 256)) #define SS2SC(v) ((signed char)((v) / 256)) #define SS2US(v) ((unsigned short)((v) + 32768)) static short sample_to_S16(void *data, int format) { switch (format) { case PCM_FORMAT_U8: return UC2SS(data); case PCM_FORMAT_S8: return SC2SS(data); case PCM_FORMAT_U16_LE: return US2SS(data); case PCM_FORMAT_S16_LE: return *(short *) data; default: error("PCM: format %i is not supported\n", format); return 0; } } static void S16_to_sample(short sample, sndbuf_t *buf, int format) { switch (format) { case PCM_FORMAT_U8: *buf = SS2UC(sample); break; case PCM_FORMAT_S8: *buf = SS2SC(sample); break; case PCM_FORMAT_U16_LE: *buf = SS2US(sample); break; case PCM_FORMAT_S16_LE: *buf = sample; break; default: error("PCM: format1 %i is not supported\n", format); } } static int count_active_streams(int id) { int i, ret = 0; for (i = 0; i < pcm.num_streams; i++) { if (id != PCM_ID_MAX && pcm.stream[i].id != id) continue; if (pcm.stream[i].state == SNDBUF_STATE_INACTIVE) continue; ret++; } return ret; } double pcm_frame_period_us(int rate) { return (1000000.0 / rate); } double pcm_frag_period(int size, struct player_params *params) { int samp_sz, nsamps; samp_sz = pcm_format_size(params->format); nsamps = size / samp_sz; return nsamps * pcm_frame_period_us(params->rate) / params->channels; } int pcm_frag_size(double period, struct player_params *params) { int nframes = period / pcm_frame_period_us(params->rate); int nsamps = nframes * params->channels; return nsamps * pcm_format_size(params->format); } static int peek_last_sample(int strm_idx, struct sample *samp) { int idx = rng_count(&pcm.stream[strm_idx].buffer); if (!idx) return 0; return rng_peek(&pcm.stream[strm_idx].buffer, idx - 1, samp); } void pcm_prepare_stream(int strm_idx) { long long now = GETusTIME(0); struct stream *s = &pcm.stream[strm_idx]; switch (s->state) { case SNDBUF_STATE_PLAYING: error("PCM: prepare playing stream %s\n", s->name); return; case SNDBUF_STATE_STALLED: error("PCM: prepare stalled stream %s\n", s->name); /* should never happen, but if we are here we reset stretches */ pcm_reset_stream(strm_idx); break; case SNDBUF_STATE_FLUSHING: /* very careful: because of poor syncing the stupid things happen. * Like, for instance, samples written in the future... */ if (now < s->stop_time) { S_printf("PCM: ERROR: sample in the future, %f now=%llu, %s\n", s->stop_time, now, s->name); now = s->stop_time; } break; } s->start_time = now; s->prepared = 1; } static void pcm_stream_stretch(int strm_idx) { long long now = GETusTIME(0); struct stream *s = &pcm.stream[strm_idx]; assert(s->state != SNDBUF_STATE_PLAYING && s->state != SNDBUF_STATE_INACTIVE); s->start_time = now; s->stretch = 1; } static double calc_buffer_fillup(int strm_idx, double time) { struct stream *s = &pcm.stream[strm_idx]; if (s->state != SNDBUF_STATE_PLAYING && s->state != SNDBUF_STATE_FLUSHING) return 0; return s->stop_time > time ? s->stop_time - time : 0; } static void pcm_start_output(int id) { int i; long long now = GETusTIME(0); for (i = 0; i < pcm.num_players; i++) { struct pcm_player_wr *p = &pcm.players[i]; if (p->player.id != id) continue; if (p->opened) { pcm_reset_player(i); p->player.start(p->player.arg); } } pcm.time = now - MAX_BUFFER_DELAY; pcm.playing |= (1 << id); S_printf("PCM: output started\n"); } static void pcm_stop_output(int id) { int i; for (i = 0; i < pcm.num_players; i++) { struct pcm_player_wr *p = &pcm.players[i]; if (id != PCM_ID_MAX && p->player.id != id) continue; if (p->opened) p->player.stop(p->player.arg); } pcm.playing &= ~(1 << id); S_printf("PCM: output stopped\n"); } static void pcm_handle_get(int strm_idx, double time) { double fillup = calc_buffer_fillup(strm_idx, time); if (debug_level('S') >= 9) S_printf("PCM: Buffer %i fillup=%f\n", strm_idx, fillup); switch (pcm.stream[strm_idx].state) { case SNDBUF_STATE_INACTIVE: error("PCM: getting data from inactive buffer (strm=%i)\n", strm_idx); break; case SNDBUF_STATE_PLAYING: #if 1 if (pcm.stream[strm_idx].flags & PCM_FLAG_RAW) { #define ADJ_PERIOD 2000000 double raw_delay = INIT_BUFFER_DELAY; double delta = (fillup - raw_delay) / (raw_delay * 320); if (time - pcm.stream[strm_idx].last_adj_time > ADJ_PERIOD) { /* of course this heuristic doesnt work, but we have to try... */ if ((fillup > raw_delay * 2 && fillup > pcm.stream[strm_idx].last_fillup) || (fillup < raw_delay / 1.5 && fillup < pcm.stream[strm_idx].last_fillup)) { pcm.stream[strm_idx].raw_speed_adj -= delta; // error("speed %f\n", pcm.stream[strm_idx].raw_speed_adj); } pcm.stream[strm_idx].last_fillup = fillup; pcm.stream[strm_idx].last_adj_time = time; } } #endif if (rng_count(&pcm.stream[strm_idx].buffer) < pcm.stream[strm_idx].channels * 2 && fillup == 0) { S_printf("PCM: ERROR: buffer on stream %i exhausted (%s)\n", strm_idx, pcm.stream[strm_idx].name); /* ditch the last sample here, if it is the only remaining */ pcm_clear_stream(strm_idx); } if (fillup == 0) { if (!(pcm.stream[strm_idx].flags & PCM_FLAG_RAW)) S_printf("PCM: ERROR: buffer on stream %i stalled (%s)\n", strm_idx, pcm.stream[strm_idx].name); pcm.stream[strm_idx].state = SNDBUF_STATE_STALLED; } if (pcm.stream[strm_idx].state == SNDBUF_STATE_PLAYING && !(pcm.stream[strm_idx].flags & PCM_FLAG_POST) && fillup < WR_BUFFER_LW) { S_printf("PCM: buffer fillup %f is too low, %s %i %f\n", fillup, pcm.stream[strm_idx].name, rng_count(&pcm.stream[strm_idx].buffer), time); } break; case SNDBUF_STATE_FLUSHING: if (rng_count(&pcm.stream[strm_idx].buffer) < pcm.stream[strm_idx].channels * 2 && fillup == 0) { pcm_reset_stream(strm_idx); S_printf("PCM: stream %s stopped\n", pcm.stream[strm_idx].name); } else if (fillup == 0 && !pcm.stream[strm_idx].stretch) { pcm_stream_stretch(strm_idx); S_printf("PCM: stream %s passed wr margin\n", pcm.stream[strm_idx].name); } break; case SNDBUF_STATE_STALLED: break; } } static void pcm_handle_write(int strm_idx, double time) { if (pcm.playing && time < pcm.time + MAX_BUFFER_PERIOD) { if (pcm.stream[strm_idx].state == SNDBUF_STATE_PLAYING) error("PCM: timing screwed up\n"); S_printf("PCM: timing screwed up, s=%s cur=%f pl=%f delta=%f\n", pcm.stream[strm_idx].name, time, pcm.time, pcm.time - time); } switch (pcm.stream[strm_idx].state) { case SNDBUF_STATE_STALLED: pcm.stream[strm_idx].stretch_tot += pcm.stream[strm_idx].stretch_per; S_printf("PCM: restarting stalled stream %s, str=%f strt=%f\n", pcm.stream[strm_idx].name, pcm.stream[strm_idx].stretch_per, pcm.stream[strm_idx].stretch_tot); pcm.stream[strm_idx].stretch_per = 0; break; case SNDBUF_STATE_FLUSHING: if (pcm.stream[strm_idx].stretch) S_printf("PCM: restarting stream %s\n", pcm.stream[strm_idx].name); else S_printf("PCM: resuming stream %s\n", pcm.stream[strm_idx].name); break; } if (pcm.stream[strm_idx].state != SNDBUF_STATE_PLAYING) { pcm.stream[strm_idx].state = SNDBUF_STATE_PLAYING; pcm.stream[strm_idx].stretch = 0; pcm.stream[strm_idx].prepared = 0; } } static void pcm_handle_flush(int strm_idx) { switch (pcm.stream[strm_idx].state) { case SNDBUF_STATE_INACTIVE: if (!(pcm.stream[strm_idx].flags & PCM_FLAG_RAW)) { error("PCM: attempt to flush inactive buffer (%s)\n", pcm.stream[strm_idx].name); } break; case SNDBUF_STATE_PLAYING: pcm.stream[strm_idx].state = SNDBUF_STATE_FLUSHING; break; case SNDBUF_STATE_STALLED: pcm_reset_stream(strm_idx); break; } } int pcm_flush(int strm_idx) { S_printf("PCM: flushing stream %i (%s)\n", strm_idx, pcm.stream[strm_idx].name); pcm_handle_flush(strm_idx); return 1; } static double get_stream_time(int strm_idx) { long long now = GETusTIME(0); double time = pcm.stream[strm_idx].start_time; double delta = now - time; switch (pcm.stream[strm_idx].state) { case SNDBUF_STATE_INACTIVE: if (pcm.stream[strm_idx].prepared) { user_tstamp: if (delta > MIN_BUFFER_DELAY) { error("PCM: too large delta on stream %s\n", pcm.stream[strm_idx].name); return now; } return time; } S_printf("PCM: ERROR: not prepared %s\n", pcm.stream[strm_idx].name); return now; case SNDBUF_STATE_STALLED: pcm.stream[strm_idx].stretch_per = now - MIN_BUFFER_DELAY - pcm.stream[strm_idx].stop_time; return now - MIN_BUFFER_DELAY; case SNDBUF_STATE_FLUSHING: if (pcm.stream[strm_idx].stretch) return now - fmod(delta, MIN_BUFFER_DELAY); if (pcm.stream[strm_idx].prepared && !(pcm.stream[strm_idx].flags & PCM_FLAG_SLTS)) goto user_tstamp; /* in SLoppy TimeStamp mode ignore user's timestamp */ /* no break */ case SNDBUF_STATE_PLAYING: return pcm.stream[strm_idx].stop_time; } return 0; } double pcm_get_stream_time(int strm_idx) { /* we allow user to write samples to the future to prevent * subsequent underflows */ return get_stream_time(strm_idx) - pcm.stream[strm_idx].stretch_tot; } double pcm_time_lock(int strm_idx) { /* well, yes, the lock needs to be per-stream... Go get it. :) */ pthread_mutex_lock(&pcm.time_mtx); return pcm_get_stream_time(strm_idx); } void pcm_time_unlock(int strm_idx) { pthread_mutex_unlock(&pcm.time_mtx); } static double pcm_calc_tstamp(int strm_idx) { double tstamp = get_stream_time(strm_idx); if (pcm.stream[strm_idx].flags & PCM_FLAG_RAW) { long long now = GETusTIME(0); if (tstamp < now) tstamp = now; } return tstamp; } void pcm_write_interleaved(sndbuf_t ptr[][SNDBUF_CHANS], int frames, int rate, int format, int nchans, int strm_idx) { int i, j; struct sample samp; double frame_per; assert(nchans <= pcm.stream[strm_idx].channels); if (pcm.stream[strm_idx].flags & PCM_FLAG_RAW) rate /= pcm.stream[strm_idx].raw_speed_adj; samp.format = format; samp.tstamp = 0; frame_per = pcm_frame_period_us(rate); pthread_mutex_lock(&pcm.strm_mtx); for (i = 0; i < frames; i++) { int l; struct sample s2; samp.tstamp = pcm_calc_tstamp(strm_idx); l = peek_last_sample(strm_idx, &s2); assert(!(l && samp.tstamp < s2.tstamp)); for (j = 0; j < pcm.stream[strm_idx].channels; j++) { int ch = j % nchans; memcpy(samp.data, &ptr[i][ch], pcm_format_size(format)); l = rng_put(&pcm.stream[strm_idx].buffer, &samp); if (!l) { error("Sound buffer %i overflowed (%s)\n", strm_idx, pcm.stream[strm_idx].name); pcm_clear_stream(strm_idx); rng_put(&pcm.stream[strm_idx].buffer, &samp); } } pcm_handle_write(strm_idx, samp.tstamp); pcm.stream[strm_idx].stop_time = samp.tstamp + frame_per; } if (!(pcm.playing & (1 << pcm.stream[strm_idx].id))) pcm_start_output(pcm.stream[strm_idx].id); pthread_mutex_unlock(&pcm.strm_mtx); } static void pcm_remove_samples(double time) { #define GUARD_SAMPS 1 int i; struct sample s; for (i = 0; i < pcm.num_streams; i++) { if (pcm.stream[i].state == SNDBUF_STATE_INACTIVE) continue; while (rng_count(&pcm.stream[i].buffer) >= pcm.stream[i].channels * (GUARD_SAMPS + 1)) { /* we leave GUARD_SAMPS samples below the timestamp untouched */ rng_peek(&pcm.stream[i].buffer, pcm.stream[i].channels * GUARD_SAMPS, &s); if (s.tstamp > time) break; pcm.stream[i].buf_cnt += pcm.stream[i].channels; rng_remove(&pcm.stream[i].buffer, pcm.stream[i].channels, NULL); } } } static void pcm_get_samples(double time, struct sample samp[MAX_STREAMS][SNDBUF_CHANS], int *idxs, int out_channels, int id) { int i, j; struct sample s[SNDBUF_CHANS], prev_s[SNDBUF_CHANS]; for (i = 0; i < pcm.num_streams; i++) { for (j = 0; j < out_channels; j++) samp[i][j] = mute_samp; if (pcm.stream[i].state == SNDBUF_STATE_INACTIVE || pcm.stream[i].id != id) continue; // S_printf("PCM: stream %i fillup: %i\n", i, rng_count(&pcm.stream[i].buffer)); for (j = 0; j < pcm.stream[i].channels; j++) { if (idxs[i] >= pcm.stream[i].channels) rng_peek(&pcm.stream[i].buffer, idxs[i] - pcm.stream[i].channels + j, &prev_s[j]); else prev_s[j] = mute_samp; } if (out_channels == 2 && pcm.stream[i].channels == 1) prev_s[1] = prev_s[0]; while (rng_count(&pcm.stream[i].buffer) - idxs[i] >= pcm.stream[i].channels) { for (j = 0; j < pcm.stream[i].channels; j++) rng_peek(&pcm.stream[i].buffer, idxs[i] + j, &s[j]); if (s[0].tstamp > time) { // S_printf("PCM: stream %i time=%lli, req_time=%lli\n", i, s.tstamp, time); memcpy(samp[i], prev_s, sizeof(struct sample) * out_channels); break; } memcpy(prev_s, s, sizeof(struct sample) * pcm.stream[i].channels); if (out_channels == 2 && pcm.stream[i].channels == 1) prev_s[1] = prev_s[0]; idxs[i] += pcm.stream[i].channels; } } } static void pcm_mix_samples(struct sample in[][SNDBUF_CHANS], sndbuf_t out[SNDBUF_CHANS], int channels, int format) { int i, j; int value[2] = { 0, 0 }; for (j = 0; j < channels; j++) { for (i = 0; i < pcm.num_streams; i++) { if (in[i][j].format == PCM_FORMAT_NONE) continue; value[j] += sample_to_S16(in[i][j].data, in[i][j].format); } S16_to_sample(pcm_samp_cutoff(value[j], PCM_FORMAT_S16_LE), &out[j], format); } } static void calc_idxs(struct pcm_player_wr *pl, int idxs[MAX_STREAMS]) { int i; for (i = 0; i < pcm.num_streams; i++) { if (pcm.stream[i].state == SNDBUF_STATE_INACTIVE) continue; assert(pcm.stream[i].buf_cnt >= pl->last_cnt[i]); if (pl->last_idx[i] > pcm.stream[i].buf_cnt - pl->last_cnt[i]) { struct sample s; idxs[i] = pl->last_idx[i] - (pcm.stream[i].buf_cnt - pl->last_cnt[i]); assert(idxs[i] <= rng_count(&pcm.stream[i].buffer)); rng_peek(&pcm.stream[i].buffer, idxs[i] - 1, &s); assert(pl->last_tstamp[i] == s.tstamp); } else { idxs[i] = 0; } } } static void save_idxs(struct pcm_player_wr *pl, int idxs[MAX_STREAMS]) { int i; for (i = 0; i < pcm.num_streams; i++) { if (pcm.stream[i].state == SNDBUF_STATE_INACTIVE) continue; assert(idxs[i] <= rng_count(&pcm.stream[i].buffer)); if (idxs[i] > 0) { struct sample s; rng_peek(&pcm.stream[i].buffer, idxs[i] - 1, &s); pl->last_tstamp[i] = s.tstamp; } pl->last_cnt[i] = pcm.stream[i].buf_cnt; pl->last_idx[i] = idxs[i]; } } int pcm_data_get_interleaved(sndbuf_t buf[][SNDBUF_CHANS], int nframes, struct player_params *params) { int idxs[MAX_STREAMS], out_idx, handle, id; long long now; double start_time, stop_time, frame_period, frag_period, time; struct sample samp[MAX_STREAMS][SNDBUF_CHANS]; now = GETusTIME(0); handle = params->handle; id = pcm.players[handle].player.id; start_time = pcm.players[handle].time; frag_period = nframes * pcm_frame_period_us(params->rate); stop_time = start_time + frag_period; if (start_time < now - MAX_BUFFER_DELAY) { error("PCM: \"%s\" too large delay, start=%f min=%f d=%f\n", pcm.players[handle].player.name, start_time, now - MAX_BUFFER_DELAY, now - MAX_BUFFER_DELAY - start_time); start_time = now - INIT_BUFFER_DELAY; stop_time = start_time + frag_period; } if (start_time > now - MIN_READ_DELAY) { error("PCM: \"%s\" too small delay, stop=%f max=%f d=%f\n", pcm.players[handle].player.name, stop_time, now - MIN_BUFFER_DELAY, stop_time - (now - MIN_BUFFER_DELAY)); start_time = now - INIT_BUFFER_DELAY; stop_time = start_time + frag_period; } if (stop_time > now - MIN_BUFFER_DELAY) { size_t new_nf; S_printf("PCM: \"%s\" too small delay, stop=%f max=%f d=%f\n", pcm.players[handle].player.name, stop_time, now - MIN_BUFFER_DELAY, stop_time - (now - MIN_BUFFER_DELAY)); stop_time = now - MIN_BUFFER_DELAY; frag_period = stop_time - start_time; new_nf = frag_period / pcm_frame_period_us(params->rate); assert(new_nf <= nframes); nframes = new_nf; } S_printf("PCM: going to process %i samps for %s (st=%f stp=%f d=%f)\n", nframes, pcm.players[handle].player.name, start_time, stop_time, now - start_time); pthread_mutex_lock(&pcm.strm_mtx); frame_period = pcm_frame_period_us(params->rate); time = start_time; calc_idxs(&pcm.players[handle], idxs); for (out_idx = 0; out_idx < nframes; out_idx++) { pcm_get_samples(time, samp, idxs, params->channels, id); pcm_mix_samples(samp, buf[out_idx], params->channels, params->format); time += frame_period; } if (fabs(time - stop_time) > frame_period) error("PCM: time=%f stop_time=%f p=%f\n", time, stop_time, frame_period); pcm.players[handle].time = stop_time; save_idxs(&pcm.players[handle], idxs); pthread_mutex_unlock(&pcm.strm_mtx); if (out_idx != nframes) error("PCM: requested=%i prepared=%i\n", nframes, out_idx); return out_idx; } size_t pcm_data_get(void *data, size_t size, struct player_params *params) { int i, j; sndbuf_t buf[size][SNDBUF_CHANS]; int ss = pcm_format_size(params->format); int fsz = params->channels * ss; int nframes = size / fsz; nframes = pcm_data_get_interleaved(buf, nframes, params); for (i = 0; i < nframes; i++) { for (j = 0; j < params->channels; j++) memcpy(data + (i * fsz + j * ss), &buf[i][j], ss); } return nframes * fsz; } static void pcm_advance_time(double stop_time) { int i; pthread_mutex_lock(&pcm.strm_mtx); pcm.time = stop_time; /* remove processed samples from input buffers (last sample stays) */ pcm_remove_samples(stop_time); for (i = 0; i < pcm.num_streams; i++) { if (pcm.stream[i].state == SNDBUF_STATE_INACTIVE) continue; if (debug_level('S') >= 9) S_printf("PCM: stream %i fillup2: %i\n", i, rng_count(&pcm.stream[i].buffer)); pcm_handle_get(i, stop_time + MAX_BUFFER_PERIOD); } for (i = 0; i < PCM_ID_MAX; i++) { if (!count_active_streams(i) && (pcm.playing & (1 << i))) pcm_stop_output(i); } pthread_mutex_unlock(&pcm.strm_mtx); } int pcm_register_player(struct pcm_player player) { S_printf("PCM: registering clocked player: %s\n", player.name); if (pcm.num_players >= MAX_PLAYERS) { error("PCM: attempt to register more than %i clocked player\n", MAX_PLAYERS); return 0; } pcm.players[pcm.num_players].player = player; return pcm.num_players++; } void pcm_reset_player(int handle) { long long now = GETusTIME(0); pcm.players[handle].time = now - INIT_BUFFER_DELAY; memset(pcm.players[handle].last_idx, 0, sizeof(pcm.players[handle].last_idx)); memset(pcm.players[handle].last_cnt, 0, sizeof(pcm.players[handle].last_cnt)); } void pcm_timer(void) { int i; long long now = GETusTIME(0); for (i = 0; i < pcm.num_players; i++) { struct pcm_player_wr *p = &pcm.players[i]; if (p->opened && p->player.timer) { double delta = now - INIT_BUFFER_DELAY - pcm.players[i].time; p->player.timer(delta, p->player.arg); } } pthread_mutex_lock(&pcm.time_mtx); pcm_advance_time(now - MAX_BUFFER_DELAY); pthread_mutex_unlock(&pcm.time_mtx); } void pcm_reset(void) { int i; S_printf("PCM: reset\n"); pthread_mutex_lock(&pcm.strm_mtx); if (pcm.playing) pcm_stop_output(PCM_ID_MAX); for (i = 0; i < pcm.num_streams; i++) pcm_reset_stream(i); pthread_mutex_unlock(&pcm.strm_mtx); } void pcm_done(void) { int i; pthread_mutex_lock(&pcm.strm_mtx); for (i = 0; i < pcm.num_streams; i++) { if (pcm.stream[i].state == SNDBUF_STATE_PLAYING || pcm.stream[i].state == SNDBUF_STATE_STALLED) pcm_flush(i); } if (pcm.playing) pcm_stop_output(PCM_ID_MAX); pthread_mutex_unlock(&pcm.strm_mtx); for (i = 0; i < pcm.num_players; i++) { struct pcm_player_wr *p = &pcm.players[i]; if (p->opened) { if (p->player.close) p->player.close(p->player.arg); p->opened = 0; } } for (i = 0; i < pcm.num_streams; i++) rng_destroy(&pcm.stream[i].buffer); pthread_mutex_destroy(&pcm.strm_mtx); pthread_mutex_destroy(&pcm.time_mtx); }