/* * Copyright (C) 2008-2021 Kim Woelders * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies of the Software, its documentation and marketing & publicity * materials, and acknowledgment shall be given in the documentation, materials * and software packages that this Software was used. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include "sound.h" #include "util.h" #ifdef USE_MODULES #define Estrdup strdup #endif #define DEBUG_PA 0 #if DEBUG_PA #define D2printf(fmt...) if(EDebug(EDBUG_TYPE_SOUND)>1)Eprintf(fmt) #define D3printf(fmt...) if(EDebug(EDBUG_TYPE_SOUND)>2)Eprintf(fmt) #define D4printf(fmt...) if(EDebug(EDBUG_TYPE_SOUND)>3)Eprintf(fmt) #else #define D2printf(fmt...) #define D3printf(fmt...) #define D4printf(fmt...) #endif struct _sample { SoundSampleData ssd; char *name; unsigned int written; }; static pa_mainloop *pa_mloop = NULL; static pa_mainloop_api *mainloop_api = NULL; static pa_context *pa_ctx = NULL; static int pa_block = 0; static void _sound_pulse_Exit(void); static int dispatch(int block) { int err, rc; D3printf("%s: beg\n", __func__); rc = 1234; pa_block = block; for (;;) { err = pa_mainloop_iterate(pa_mloop, pa_block, &rc); D4printf("%s: run err=%d rc=%d block=%d\n", __func__, err, rc, pa_block); if (err < 0 || (err == 0 && !pa_block)) break; } if (err < 0) _sound_pulse_Exit(); D3printf("%s: end\n", __func__); return err; } static void context_op_callback(pa_context *pac __UNUSED__, int success __UNUSED__, void *userdata __UNUSED__) { D2printf("%s: succ=%d %s\n", __func__, success, (success) ? "" : pa_strerror(pa_context_errno(pac))); pa_block = 0; } static void context_drain_complete(pa_context *pac __UNUSED__, void *userdata __UNUSED__) { D2printf("%s\n", __func__); } static void context_drain(pa_context *pac) { pa_operation *op; D2printf("%s\n", __func__); op = pa_context_drain(pac, context_drain_complete, NULL); if (op) pa_operation_unref(op); pa_block = 0; } static void stream_state_callback(pa_stream *pas, void *userdata __UNUSED__) { D2printf("%s: state=%d\n", __func__, pa_stream_get_state(pas)); switch (pa_stream_get_state(pas)) { case PA_STREAM_CREATING: /* 1 */ case PA_STREAM_READY: /* 2 */ break; case PA_STREAM_TERMINATED: /* 4 */ context_drain(pa_stream_get_context(pas)); break; case PA_STREAM_FAILED: /* 3 */ default: Eprintf("PA failure: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(pas)))); pa_block = 0; break; } } static void stream_write_callback(pa_stream *pas, size_t length, void *userdata) { D2printf("%s: state=%d length=%u\n", __func__, pa_stream_get_state(pas), (unsigned int)length); Sample *s = (Sample *) userdata; unsigned int left; left = s->ssd.size - s->written; length = (left > length) ? length : left; pa_stream_write(pas, s->ssd.data, length, NULL, 0, PA_SEEK_RELATIVE); s->written += length; D2printf("%s: size=%d written=%d\n", __func__, s->ssd.size, s->written); if (s->written >= s->ssd.size) { pa_stream_set_write_callback(pas, NULL, NULL); pa_stream_finish_upload(pas); } } static void context_state_callback(pa_context *pac, void *userdata __UNUSED__) { D2printf("%s: state=%d\n", __func__, pa_context_get_state(pac)); switch (pa_context_get_state(pac)) { case PA_CONTEXT_CONNECTING: /* 1 */ case PA_CONTEXT_AUTHORIZING: /* 2 */ case PA_CONTEXT_SETTING_NAME: /* 3 */ break; case PA_CONTEXT_READY: /* 4 */ pa_block = 0; break; case PA_CONTEXT_TERMINATED: /* 6 */ break; case PA_CONTEXT_FAILED: /* 5 */ default: Eprintf("PA failure: %s\n", pa_strerror(pa_context_errno(pac))); pa_mainloop_quit(pa_mloop, 1); break; } } static void _sound_pulse_Destroy(Sample *s) { pa_operation *op; D2printf("%s beg: %s\n", __func__, s->name); if (pa_ctx && s->name) { op = pa_context_remove_sample(pa_ctx, s->name, context_op_callback, NULL); if (op) pa_operation_unref(op); dispatch(-1); } D2printf("%s end\n", __func__); Efree(s->name); Efree(s->ssd.data); Efree(s); } static Sample * _sound_pulse_Load(const char *file) { Sample *s; pa_sample_spec sample_spec; pa_stream *sample_stream = NULL; int err; char *p; if (!pa_ctx) return NULL; s = ECALLOC(Sample, 1); if (!s) return NULL; err = SoundSampleGetData(file, &s->ssd); if (err) { Efree(s); return NULL; } s->name = Estrdup(file); if (!s->name) goto bail_out; for (p = s->name; *p != '\0'; p++) if (*p == '/') *p = '_'; switch (s->ssd.bit_per_sample) { case 8: sample_spec.format = PA_SAMPLE_U8; break; default: sample_spec.format = PA_SAMPLE_S16NE; break; } sample_spec.rate = s->ssd.rate; sample_spec.channels = s->ssd.channels; sample_stream = pa_stream_new(pa_ctx, s->name, &sample_spec, NULL); if (!sample_stream) goto bail_out; pa_stream_set_state_callback(sample_stream, stream_state_callback, NULL); pa_stream_set_write_callback(sample_stream, stream_write_callback, s); pa_stream_connect_upload(sample_stream, s->ssd.size); err = dispatch(-1); if (err) goto bail_out; EFREE_NULL(s->ssd.data); pa_stream_unref(sample_stream); return s; bail_out: if (sample_stream) pa_stream_unref(sample_stream); _sound_pulse_Destroy(s); return NULL; } static void _sound_pulse_Play(Sample *s) { pa_operation *op; D2printf("%s beg: %s\n", __func__, s->name); if (!pa_ctx) return; op = pa_context_play_sample(pa_ctx, s->name, NULL, PA_VOLUME_NORM, context_op_callback, NULL); if (op) pa_operation_unref(op); dispatch(-1); D2printf("%s end\n", __func__); } static void _sound_pulse_Exit(void) { D2printf("%s\n", __func__); #if 0 if (stream) pa_stream_unref(stream); #endif if (pa_ctx) { pa_context_disconnect(pa_ctx); pa_context_unref(pa_ctx); pa_ctx = NULL; } if (pa_mloop) { pa_mainloop_quit(pa_mloop, 0); pa_mainloop_free(pa_mloop); pa_mloop = NULL; } } static int _sound_pulse_Init(void) { int err; /* Set up a new main loop */ pa_mloop = pa_mainloop_new(); if (!pa_mloop) { Eprintf("pa_mainloop_new() failed.\n"); goto quit; } mainloop_api = pa_mainloop_get_api(pa_mloop); /* Create a new connection context */ pa_ctx = pa_context_new(mainloop_api, "e16"); if (!pa_ctx) { Eprintf("pa_context_new() failed.\n"); goto quit; } pa_context_set_state_callback(pa_ctx, context_state_callback, NULL); /* Connect the context */ err = pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL); if (err) Eprintf("pa_context_connect(): %s\n", pa_strerror(err)); err = dispatch(-1); if (err) goto quit; done: return !pa_ctx; quit: _sound_pulse_Exit(); goto done; } __EXPORT__ extern const SoundOps SoundOps_pulse; const SoundOps SoundOps_pulse = { _sound_pulse_Init, _sound_pulse_Exit, _sound_pulse_Load, _sound_pulse_Destroy, _sound_pulse_Play, };