/* coveraudiod -- plays sounds while using surface touch covers Copyright (C) 2014 Javier S. Pedro 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 3 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, see . */ #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); }