aboutsummaryrefslogtreecommitdiff
path: root/audio.c
diff options
context:
space:
mode:
authorJavier <dev.git@javispedro.com>2014-08-15 20:49:23 +0200
committerJavier <dev.git@javispedro.com>2014-08-15 20:49:23 +0200
commit9694ec75ab1464b3699ef367f3800e60bef0cc7a (patch)
tree645c4d0fc9b8c71162cdf9f4ac572904b0bc4079 /audio.c
downloadcoveraudiod-9694ec75ab1464b3699ef367f3800e60bef0cc7a.tar.gz
coveraudiod-9694ec75ab1464b3699ef367f3800e60bef0cc7a.zip
initial import
Diffstat (limited to 'audio.c')
-rw-r--r--audio.c225
1 files changed, 225 insertions, 0 deletions
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 <stdio.h>
+
+#include <pulse/glib-mainloop.h>
+#include <pulse/pulseaudio.h>
+#include <sndfile.h>
+
+#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);
+}