356 lines
8.6 KiB
C
356 lines
8.6 KiB
C
/*
|
|
* 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 <fcntl.h>
|
|
#include <pulse/pulseaudio.h>
|
|
#include <sys/stat.h>
|
|
|
|
#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,
|
|
};
|