From 9694ec75ab1464b3699ef367f3800e60bef0cc7a Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 15 Aug 2014 20:49:23 +0200 Subject: initial import --- audio.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 audio.c (limited to 'audio.c') diff --git a/audio.c b/audio.c new file mode 100644 index 0000000..6d38783 --- /dev/null +++ b/audio.c @@ -0,0 +1,225 @@ +#include + +#include +#include +#include + +#include "audio.h" + +static pa_context *context; + +#define PA_SAMPLE_NAME_PREFIX "coveraudiod-" +#define SAMPLE_FILES_PATH "/usr/share/coveraudiod/" + +struct sample_info { + const char *name; +} samples[NUM_SAMPLES] = { + {"key1"}, + {"key2"}, + {"key3"}, + {"key4"}, + {"key5"}, + {"key6"}, + {"mod1"}, + {"mod2"} +}; + +struct sample_loading_status { + const struct sample_info *sample; + SNDFILE *sndfile; + pa_stream *stream; + size_t nbytes; +}; + +static const char *build_file_name(const struct sample_info *sample) +{ + static char buffer[sizeof(SAMPLE_FILES_PATH) * 2]; + + snprintf(buffer, sizeof(buffer), "%s%s.%s", SAMPLE_FILES_PATH, sample->name, "wav"); + + return buffer; +} + +static const char *build_sample_name(const struct sample_info *sample) +{ + static char buffer[sizeof(PA_SAMPLE_NAME_PREFIX) * 2]; + + snprintf(buffer, sizeof(buffer), "%s%s", PA_SAMPLE_NAME_PREFIX, sample->name); + + return buffer; +} + +static void handle_stream_request(pa_stream *s, size_t nbytes, void *userdata) +{ + struct sample_loading_status *ldstatus = (struct sample_loading_status*) userdata; + + void *buffer; + + int err = pa_stream_begin_write(s, &buffer, &nbytes); + g_return_if_fail(err == 0); + + sf_count_t read = sf_read_raw(ldstatus->sndfile, buffer, nbytes); + + err = pa_stream_write(s, buffer, read, NULL, 0, PA_SEEK_RELATIVE); + if (err) { + g_warning("Failed to upload data to PulseAudio"); + pa_stream_disconnect(s); + } + + ldstatus->nbytes -= read; + + if (read < nbytes || ldstatus->nbytes <= 0) { + // EOF + pa_stream_finish_upload(s); + } +} + +static void handle_stream_notify(pa_stream *p, void *userdata) +{ + struct sample_loading_status *ldstatus = (struct sample_loading_status*) userdata; + + pa_stream_state_t status = pa_stream_get_state(p); + switch (status) { + case PA_STREAM_READY: + // Nothing to do, write request will be shortly called by PA. + break; + case PA_STREAM_FAILED: + g_warning("PulseAudio stream upload failed, closing"); + // Fallthrough + case PA_STREAM_TERMINATED: + pa_stream_unref(ldstatus->stream); + sf_close(ldstatus->sndfile); + free(ldstatus); + break; + default: + break; + } +} + +static bool load_sample(const struct sample_info *sample) +{ + struct sample_loading_status *ldstatus = calloc(1, sizeof(struct sample_loading_status)); + ldstatus->sample = sample; + + const char *file_name = build_file_name(sample); + SF_INFO info; + + ldstatus->sndfile = sf_open(file_name, SFM_READ, &info); + if (!ldstatus->sndfile) { + g_warning("Could not open sample: %s", file_name); + return false; + } + + pa_sample_spec spec; + spec.rate = info.samplerate; + spec.channels = info.channels; + + switch (info.format & SF_FORMAT_SUBMASK) { + case SF_FORMAT_PCM_16: + spec.format = PA_SAMPLE_S16NE; + break; + case SF_FORMAT_PCM_24: + spec.format = PA_SAMPLE_S24NE; + break; + case SF_FORMAT_PCM_32: + spec.format = PA_SAMPLE_S32NE; + break; + case SF_FORMAT_ULAW: + spec.format = PA_SAMPLE_ULAW; + break; + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + spec.format = PA_SAMPLE_FLOAT32NE; + break; + default: + g_warning("Unknown sample format: %s", file_name); + sf_close(ldstatus->sndfile); + free(ldstatus); + return false; + } + + g_return_val_if_fail(pa_sample_spec_valid(&spec), false); + + ldstatus->nbytes = pa_frame_size(&spec) * info.frames; + + pa_proplist *prop = pa_proplist_new(); + pa_proplist_sets(prop, PA_PROP_MEDIA_ROLE, "event"); + pa_proplist_sets(prop, PA_PROP_MEDIA_FILENAME, file_name); + + ldstatus->stream = pa_stream_new_with_proplist(context, build_sample_name(sample), + &spec, NULL, prop); + + pa_stream_set_state_callback(ldstatus->stream, handle_stream_notify, ldstatus); + pa_stream_set_write_callback(ldstatus->stream, handle_stream_request, ldstatus); + + int err = pa_stream_connect_upload(ldstatus->stream, ldstatus->nbytes); + if (err) { + g_warning("pa_stream_connect_upload failed"); + pa_stream_unref(ldstatus->stream); + sf_close(ldstatus->sndfile); + free(ldstatus); + return false; + } + + return true; +} + +static void load_samples() +{ + for (int i = 0; i < NUM_SAMPLES; i++) { + if (!load_sample(&samples[i])) { + return; + } + } +} + +static void handle_context_notify(pa_context *c, void *userdata) +{ + pa_context_state_t state = pa_context_get_state(c); + switch (state) { + case PA_CONTEXT_READY: + load_samples(); + break; + case PA_CONTEXT_FAILED: + g_warning("Cannot connect to PulseAudio"); + break; + default: + break; + } +} + +bool audio_init() +{ + pa_glib_mainloop *paloop = pa_glib_mainloop_new(NULL); + context = pa_context_new(pa_glib_mainloop_get_api(paloop), "coveraudiod"); + + if (!context) { + g_warning("Cannot create PulseAudio context"); + return false; + } + + pa_context_set_state_callback(context, handle_context_notify, NULL); + + int err = pa_context_connect(context, NULL, 0, NULL); + if (err) { + g_warning("Cannot connect to PulseAudio"); + return false; + } + + return true; +} + +void audio_close() +{ + pa_context_unref(context); +} + +void audio_play_sample(int num_sample) +{ + const char *sample_name = build_sample_name(&samples[num_sample]); + + pa_operation *op = pa_context_play_sample(context, sample_name, NULL, + PA_VOLUME_NORM, NULL, NULL); + + pa_operation_unref(op); +} -- cgit v1.2.3