mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-10-29 05:40:23 -04:00
tests: Refactor common client code from passthrough test
This code can be reused for other tests that need to connect to the PA server and load a NULL sink. The idea is to grow this out with basic primitives that various tests need to make it easier to add more unit tests for various situations.
This commit is contained in:
parent
01c26546fa
commit
52a9d87053
4 changed files with 436 additions and 224 deletions
|
|
@ -238,7 +238,7 @@ if get_option('daemon')
|
|||
daemon_tests = [
|
||||
[ 'extended-test', 'extended-test.c',
|
||||
[ check_dep, libm_dep, libpulse_dep ] ],
|
||||
[ 'passthrough-test', 'passthrough-test.c',
|
||||
[ 'passthrough-test', [ 'passthrough-test.c', 'test-util.c' ],
|
||||
[ check_dep, libpulse_dep, libpulsecommon_dep ] ],
|
||||
[ 'sync-playback', 'sync-playback.c',
|
||||
[ check_dep, libm_dep, libpulse_dep ] ],
|
||||
|
|
|
|||
|
|
@ -19,234 +19,62 @@
|
|||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <check.h>
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
#include <pulsecore/core-util.h>
|
||||
#include "test-util.h"
|
||||
|
||||
#define SINK_NAME "passthrough-test"
|
||||
|
||||
#define RATE 48000
|
||||
#define CHANNELS 6
|
||||
|
||||
#define WAIT_FOR_OPERATION(o) \
|
||||
do { \
|
||||
while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { \
|
||||
pa_threaded_mainloop_wait(mainloop); \
|
||||
} \
|
||||
\
|
||||
fail_unless(pa_operation_get_state(o) == PA_OPERATION_DONE); \
|
||||
pa_operation_unref(o); \
|
||||
} while (false)
|
||||
|
||||
static pa_threaded_mainloop *mainloop = NULL;
|
||||
static pa_context *context = NULL;
|
||||
static pa_mainloop_api *mainloop_api = NULL;
|
||||
static uint32_t module_idx = PA_INVALID_INDEX;
|
||||
static int sink_num = 0;
|
||||
static char sink_name[256] = { 0, };
|
||||
static pa_test_context *ctx = NULL;
|
||||
static uint32_t sink_idx = PA_INVALID_INDEX;
|
||||
static const char *bname = NULL;
|
||||
|
||||
/* This is called whenever the context status changes */
|
||||
static void context_state_callback(pa_context *c, void *userdata) {
|
||||
fail_unless(c != NULL);
|
||||
|
||||
switch (pa_context_get_state(c)) {
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
case PA_CONTEXT_AUTHORIZING:
|
||||
case PA_CONTEXT_SETTING_NAME:
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_READY:
|
||||
fprintf(stderr, "Connection established.\n");
|
||||
pa_threaded_mainloop_signal(mainloop, false);
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
mainloop_api->quit(mainloop_api, 0);
|
||||
pa_threaded_mainloop_signal(mainloop, false);
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_FAILED:
|
||||
mainloop_api->quit(mainloop_api, 0);
|
||||
pa_threaded_mainloop_signal(mainloop, false);
|
||||
fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c)));
|
||||
fail();
|
||||
break;
|
||||
|
||||
default:
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
static void module_index_cb(pa_context *c, uint32_t idx, void *userdata) {
|
||||
fail_unless(idx != PA_INVALID_INDEX);
|
||||
|
||||
module_idx = idx;
|
||||
|
||||
pa_threaded_mainloop_signal(mainloop, false);
|
||||
}
|
||||
/* We fill in fake AC3 data in terms of the corresponding PCM sample spec (S16LE, 2ch, at the given rate) */
|
||||
int16_t data[RATE * 2] = { 0, }; /* one second space */
|
||||
|
||||
static void success_cb(pa_context *c, int success, void *userdata) {
|
||||
fail_unless(success != 0);
|
||||
|
||||
pa_threaded_mainloop_signal(mainloop, false);
|
||||
pa_threaded_mainloop_signal(ctx->mainloop, false);
|
||||
}
|
||||
|
||||
static void passthrough_teardown() {
|
||||
pa_operation *o;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
|
||||
if (module_idx != PA_INVALID_INDEX) {
|
||||
o = pa_context_unload_module(context, module_idx, success_cb, NULL);
|
||||
WAIT_FOR_OPERATION(o);
|
||||
}
|
||||
|
||||
pa_context_disconnect(context);
|
||||
pa_context_unref(context);
|
||||
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
|
||||
pa_threaded_mainloop_stop(mainloop);
|
||||
pa_threaded_mainloop_free(mainloop);
|
||||
pa_test_context_free(ctx);
|
||||
}
|
||||
|
||||
static void passthrough_setup() {
|
||||
char modargs[128];
|
||||
pa_operation *o;
|
||||
int r;
|
||||
ctx = pa_test_context_new(bname);
|
||||
|
||||
/* Set up a new main loop */
|
||||
mainloop = pa_threaded_mainloop_new();
|
||||
fail_unless(mainloop != NULL);
|
||||
|
||||
mainloop_api = pa_threaded_mainloop_get_api(mainloop);
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
|
||||
pa_threaded_mainloop_start(mainloop);
|
||||
|
||||
context = pa_context_new(mainloop_api, bname);
|
||||
fail_unless(context != NULL);
|
||||
|
||||
pa_context_set_state_callback(context, context_state_callback, NULL);
|
||||
|
||||
/* Connect the context */
|
||||
r = pa_context_connect(context, NULL, 0, NULL);
|
||||
fail_unless(r == 0);
|
||||
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
|
||||
fail_unless(pa_context_get_state(context) == PA_CONTEXT_READY);
|
||||
|
||||
pa_snprintf(sink_name, sizeof(sink_name), "%s-%d", SINK_NAME, sink_num);
|
||||
pa_snprintf(modargs, sizeof(modargs), "sink_name='%s' formats='ac3-iec61937, format.rate=\"[32000, 44100, 48000]\" format.channels=\"6\"; pcm'", sink_name);
|
||||
|
||||
o = pa_context_load_module(context, "module-null-sink", modargs, module_index_cb, NULL);
|
||||
WAIT_FOR_OPERATION(o);
|
||||
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void nop_free_cb(void *p) {}
|
||||
|
||||
static void underflow_cb(struct pa_stream *s, void *userdata) {
|
||||
fprintf(stderr, "Stream finished\n");
|
||||
pa_threaded_mainloop_signal(mainloop, false);
|
||||
}
|
||||
|
||||
/* This routine is called whenever the stream state changes */
|
||||
static void stream_state_callback(pa_stream *s, void *userdata) {
|
||||
/* We fill in fake AC3 data in terms of the corresponding PCM sample spec (S16LE, 2ch, at the given rate) */
|
||||
int16_t data[RATE * 2] = { 0, }; /* one second space */
|
||||
|
||||
fail_unless(s != NULL);
|
||||
|
||||
switch (pa_stream_get_state(s)) {
|
||||
case PA_STREAM_UNCONNECTED:
|
||||
case PA_STREAM_CREATING:
|
||||
break;
|
||||
|
||||
case PA_STREAM_TERMINATED:
|
||||
pa_threaded_mainloop_signal(mainloop, false);
|
||||
break;
|
||||
|
||||
case PA_STREAM_READY: {
|
||||
int r;
|
||||
|
||||
r = pa_stream_write(s, data, sizeof(data), nop_free_cb, 0, PA_SEEK_ABSOLUTE);
|
||||
fail_unless(r == 0);
|
||||
|
||||
/* Be notified when this stream is drained */
|
||||
pa_stream_set_underflow_callback(s, underflow_cb, userdata);
|
||||
|
||||
pa_threaded_mainloop_signal(mainloop, false);
|
||||
break;
|
||||
}
|
||||
|
||||
case PA_STREAM_FAILED:
|
||||
fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
|
||||
pa_threaded_mainloop_signal(mainloop, false);
|
||||
break;
|
||||
|
||||
default:
|
||||
fail();
|
||||
}
|
||||
sink_idx = pa_test_context_load_null_sink(ctx,
|
||||
"formats='ac3-iec61937, format.rate=\"[32000, 44100, 48000]\" format.channels=\"6\"; pcm'");
|
||||
}
|
||||
|
||||
static pa_stream* connect_stream() {
|
||||
int r;
|
||||
pa_stream *s;
|
||||
pa_format_info *formats[1];
|
||||
pa_format_info *format;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
|
||||
formats[0] = pa_format_info_new();
|
||||
formats[0]->encoding = PA_ENCODING_AC3_IEC61937;
|
||||
format = pa_format_info_new();
|
||||
format->encoding = PA_ENCODING_AC3_IEC61937;
|
||||
/* We set rate and channels to test that negotiation actually works. This
|
||||
* must correspond to the rate and channels we configure module-null-sink
|
||||
* for above. */
|
||||
pa_format_info_set_rate(formats[0], RATE);
|
||||
pa_format_info_set_channels(formats[0], CHANNELS);
|
||||
pa_format_info_set_rate(format, RATE);
|
||||
pa_format_info_set_channels(format, CHANNELS);
|
||||
|
||||
s = pa_stream_new_extended(context, "passthrough test", formats, 1, NULL);
|
||||
s = pa_test_context_create_stream(ctx, "passthrough test", sink_idx, format, PA_STREAM_NOFLAGS, data, sizeof(data));
|
||||
fail_unless(s != NULL);
|
||||
|
||||
pa_stream_set_state_callback(s, stream_state_callback, NULL);
|
||||
r = pa_stream_connect_playback(s, sink_name, NULL, PA_STREAM_NOFLAGS, NULL, NULL);
|
||||
|
||||
fail_unless(r == 0);
|
||||
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
|
||||
fail_unless(pa_stream_get_state(s) == PA_STREAM_READY);
|
||||
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
pa_format_info_free(format);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static void disconnect_stream(pa_stream *s) {
|
||||
int r;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
|
||||
r = pa_stream_disconnect(s);
|
||||
fail_unless(r == 0);
|
||||
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
fail_unless(pa_stream_get_state(s) == PA_STREAM_TERMINATED);
|
||||
|
||||
pa_stream_unref(s);
|
||||
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
pa_test_context_destroy_stream(ctx, s);
|
||||
}
|
||||
|
||||
START_TEST (passthrough_playback_test) {
|
||||
|
|
@ -256,36 +84,22 @@ START_TEST (passthrough_playback_test) {
|
|||
|
||||
stream = connect_stream();
|
||||
|
||||
/* Wait for underflow_cb() */
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
/* Wait for the stream to be drained */
|
||||
pa_threaded_mainloop_lock(ctx->mainloop);
|
||||
pa_threaded_mainloop_wait(ctx->mainloop);
|
||||
fail_unless(pa_stream_get_state(stream) == PA_STREAM_READY);
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
pa_threaded_mainloop_unlock(ctx->mainloop);
|
||||
|
||||
disconnect_stream(stream);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
|
||||
static bool check_sink_volume(const pa_sink_info *sink_info, void *userdata) {
|
||||
pa_cvolume *v = (pa_cvolume *) userdata;
|
||||
|
||||
if (eol)
|
||||
return;
|
||||
pa_assert(v);
|
||||
|
||||
*v = i->volume;
|
||||
|
||||
pa_threaded_mainloop_signal(mainloop, false);
|
||||
}
|
||||
|
||||
static void get_sink_volume(pa_cvolume *v) {
|
||||
pa_operation *o;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
|
||||
o = pa_context_get_sink_info_by_name(context, sink_name, sink_info_cb, v);
|
||||
WAIT_FOR_OPERATION(o);
|
||||
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
return pa_cvolume_equal(&sink_info->volume, v);
|
||||
}
|
||||
|
||||
START_TEST (passthrough_volume_test) {
|
||||
|
|
@ -296,28 +110,28 @@ START_TEST (passthrough_volume_test) {
|
|||
pa_operation *o;
|
||||
pa_cvolume volume, tmp;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
pa_threaded_mainloop_lock(ctx->mainloop);
|
||||
|
||||
pa_cvolume_set(&volume, 2, PA_VOLUME_NORM / 2);
|
||||
o = pa_context_set_sink_volume_by_name(context, sink_name, &volume, success_cb, NULL);
|
||||
WAIT_FOR_OPERATION(o);
|
||||
o = pa_context_set_sink_volume_by_index(ctx->context, sink_idx, &volume, success_cb, NULL);
|
||||
WAIT_FOR_OPERATION(ctx, o);
|
||||
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
pa_threaded_mainloop_unlock(ctx->mainloop);
|
||||
|
||||
stream = connect_stream();
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
/* Wait for the stream to be drained */
|
||||
pa_threaded_mainloop_lock(ctx->mainloop);
|
||||
pa_threaded_mainloop_wait(ctx->mainloop);
|
||||
fail_unless(PA_STREAM_IS_GOOD(pa_stream_get_state(stream)));
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
pa_threaded_mainloop_unlock(ctx->mainloop);
|
||||
|
||||
get_sink_volume(&tmp);
|
||||
fail_unless(pa_cvolume_is_norm(&tmp));
|
||||
pa_cvolume_set(&tmp, 2, PA_VOLUME_NORM);
|
||||
fail_unless(pa_test_context_check_sink(ctx, sink_idx, check_sink_volume, &tmp));
|
||||
|
||||
disconnect_stream(stream);
|
||||
|
||||
get_sink_volume(&tmp);
|
||||
fail_unless(pa_cvolume_equal(&volume, &tmp));
|
||||
fail_unless(pa_test_context_check_sink(ctx, sink_idx, check_sink_volume, &volume));
|
||||
}
|
||||
END_TEST
|
||||
|
||||
|
|
@ -333,7 +147,6 @@ int main(int argc, char *argv[]) {
|
|||
tc = tcase_create("passthrough");
|
||||
tcase_add_checked_fixture(tc, passthrough_setup, passthrough_teardown);
|
||||
tcase_add_test(tc, passthrough_playback_test);
|
||||
sink_num++;
|
||||
tcase_add_test(tc, passthrough_volume_test);
|
||||
tcase_set_timeout(tc, 5);
|
||||
suite_add_tcase(s, tc);
|
||||
|
|
|
|||
325
src/tests/test-util.c
Normal file
325
src/tests/test-util.c
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
PulseAudio is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
PulseAudio 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 Lesser General Public License
|
||||
along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include "test-util.h"
|
||||
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/macro.h>
|
||||
|
||||
/* This is called whenever the context status changes */
|
||||
static void context_state_callback(pa_context *c, void *userdata) {
|
||||
pa_test_context *ctx = (pa_test_context *) userdata;
|
||||
|
||||
pa_assert(c);
|
||||
pa_assert(ctx);
|
||||
|
||||
switch (pa_context_get_state(c)) {
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
case PA_CONTEXT_AUTHORIZING:
|
||||
case PA_CONTEXT_SETTING_NAME:
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_READY:
|
||||
pa_log_info("Connection established.\n");
|
||||
pa_threaded_mainloop_signal(ctx->mainloop, false);
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
ctx->mainloop_api->quit(ctx->mainloop_api, 0);
|
||||
pa_threaded_mainloop_signal(ctx->mainloop, false);
|
||||
break;
|
||||
|
||||
case PA_CONTEXT_FAILED:
|
||||
ctx->mainloop_api->quit(ctx->mainloop_api, 0);
|
||||
pa_threaded_mainloop_signal(ctx->mainloop, false);
|
||||
pa_log_error("Context error: %s\n", pa_strerror(pa_context_errno(c)));
|
||||
pa_assert_not_reached();
|
||||
break;
|
||||
|
||||
default:
|
||||
pa_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
static void success_cb(pa_context *c, int success, void *userdata) {
|
||||
pa_test_context *ctx = (pa_test_context *) userdata;
|
||||
|
||||
pa_assert(c);
|
||||
pa_assert(ctx);
|
||||
pa_assert(success != 0);
|
||||
|
||||
pa_threaded_mainloop_signal(ctx->mainloop, false);
|
||||
}
|
||||
|
||||
pa_test_context* pa_test_context_new(const char *name) {
|
||||
pa_test_context *ctx;
|
||||
int r;
|
||||
|
||||
pa_assert(name);
|
||||
|
||||
ctx = pa_xnew0(pa_test_context, 1);
|
||||
|
||||
ctx->modules = pa_idxset_new(NULL, NULL);
|
||||
|
||||
/* Set up a new main loop */
|
||||
ctx->mainloop = pa_threaded_mainloop_new();
|
||||
pa_assert(ctx->mainloop);
|
||||
|
||||
ctx->mainloop_api = pa_threaded_mainloop_get_api(ctx->mainloop);
|
||||
|
||||
pa_threaded_mainloop_lock(ctx->mainloop);
|
||||
|
||||
pa_threaded_mainloop_start(ctx->mainloop);
|
||||
|
||||
ctx->context = pa_context_new(ctx->mainloop_api, name);
|
||||
pa_assert(ctx->context);
|
||||
|
||||
pa_context_set_state_callback(ctx->context, context_state_callback, ctx);
|
||||
|
||||
/* Connect the context */
|
||||
r = pa_context_connect(ctx->context, NULL, 0, NULL);
|
||||
pa_assert(r == 0);
|
||||
|
||||
pa_threaded_mainloop_wait(ctx->mainloop);
|
||||
|
||||
pa_assert(pa_context_get_state(ctx->context) == PA_CONTEXT_READY);
|
||||
|
||||
pa_threaded_mainloop_unlock(ctx->mainloop);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void pa_test_context_free(pa_test_context *ctx) {
|
||||
void *module;
|
||||
uint32_t idx;
|
||||
|
||||
pa_threaded_mainloop_lock(ctx->mainloop);
|
||||
|
||||
PA_IDXSET_FOREACH(module, ctx->modules, idx) {
|
||||
pa_operation *o;
|
||||
|
||||
o = pa_context_unload_module(ctx->context, PA_PTR_TO_UINT32(module), success_cb, ctx);
|
||||
|
||||
WAIT_FOR_OPERATION(ctx, o);
|
||||
}
|
||||
|
||||
pa_idxset_free(ctx->modules, NULL);
|
||||
|
||||
pa_context_disconnect(ctx->context);
|
||||
pa_context_unref(ctx->context);
|
||||
|
||||
pa_threaded_mainloop_unlock(ctx->mainloop);
|
||||
|
||||
pa_threaded_mainloop_stop(ctx->mainloop);
|
||||
pa_threaded_mainloop_free(ctx->mainloop);
|
||||
|
||||
pa_xfree(ctx);
|
||||
}
|
||||
|
||||
static void module_index_cb(pa_context *c, uint32_t idx, void *userdata) {
|
||||
pa_test_context *ctx = (pa_test_context *) userdata;
|
||||
|
||||
pa_assert(c);
|
||||
pa_assert(ctx);
|
||||
pa_assert(idx != PA_INVALID_INDEX);
|
||||
|
||||
ctx->module_idx = idx;
|
||||
|
||||
pa_threaded_mainloop_signal(ctx->mainloop, false);
|
||||
}
|
||||
|
||||
static void lookup_module_sink_idx(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
|
||||
pa_test_context *ctx = (pa_test_context *) userdata;
|
||||
|
||||
pa_assert(ctx);
|
||||
|
||||
if (!i || eol) {
|
||||
pa_threaded_mainloop_signal(ctx->mainloop, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (i->owner_module == ctx->module_idx)
|
||||
ctx->sink_idx = i->index;
|
||||
}
|
||||
|
||||
uint32_t pa_test_context_load_null_sink(pa_test_context *ctx, const char *modargs) {
|
||||
pa_operation *o;
|
||||
|
||||
pa_assert(ctx);
|
||||
|
||||
pa_threaded_mainloop_lock(ctx->mainloop);
|
||||
|
||||
/* Load the module */
|
||||
ctx->module_idx = PA_INVALID_INDEX;
|
||||
o = pa_context_load_module(ctx->context, "module-null-sink", modargs, module_index_cb, ctx);
|
||||
WAIT_FOR_OPERATION(ctx, o);
|
||||
|
||||
pa_assert(ctx->module_idx != PA_INVALID_INDEX);
|
||||
pa_idxset_put(ctx->modules, PA_UINT32_TO_PTR(ctx->module_idx), NULL);
|
||||
|
||||
/* Look up the sink index corresponding to the module */
|
||||
ctx->sink_idx = PA_INVALID_INDEX;
|
||||
o = pa_context_get_sink_info_list(ctx->context, lookup_module_sink_idx, ctx);
|
||||
WAIT_FOR_OPERATION(ctx, o);
|
||||
|
||||
pa_threaded_mainloop_unlock(ctx->mainloop);
|
||||
|
||||
pa_assert(ctx->sink_idx != PA_INVALID_INDEX);
|
||||
|
||||
return ctx->sink_idx;
|
||||
}
|
||||
|
||||
static void nop_free_cb(void *p) {}
|
||||
|
||||
static void underflow_cb(struct pa_stream *s, void *userdata) {
|
||||
pa_test_context *ctx = (pa_test_context *) userdata;
|
||||
|
||||
pa_assert(ctx);
|
||||
|
||||
pa_log_info("Stream finished\n");
|
||||
|
||||
pa_threaded_mainloop_signal(ctx->mainloop, false);
|
||||
}
|
||||
|
||||
/* This routine is called whenever the stream state changes */
|
||||
static void stream_state_callback(pa_stream *s, void *userdata) {
|
||||
pa_test_context *ctx = (pa_test_context *) userdata;
|
||||
|
||||
pa_assert(s);
|
||||
pa_assert(ctx);
|
||||
|
||||
switch (pa_stream_get_state(s)) {
|
||||
case PA_STREAM_UNCONNECTED:
|
||||
case PA_STREAM_CREATING:
|
||||
break;
|
||||
|
||||
case PA_STREAM_TERMINATED:
|
||||
pa_threaded_mainloop_signal(ctx->mainloop, false);
|
||||
break;
|
||||
|
||||
case PA_STREAM_READY: {
|
||||
int r;
|
||||
|
||||
r = pa_stream_write(s, ctx->data, ctx->length, nop_free_cb, 0, PA_SEEK_ABSOLUTE);
|
||||
pa_assert(r == 0);
|
||||
|
||||
/* Be notified when this stream is drained */
|
||||
pa_stream_set_underflow_callback(s, underflow_cb, userdata);
|
||||
|
||||
pa_threaded_mainloop_signal(ctx->mainloop, false);
|
||||
break;
|
||||
}
|
||||
|
||||
case PA_STREAM_FAILED:
|
||||
pa_log_error("Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
|
||||
pa_threaded_mainloop_signal(ctx->mainloop, false);
|
||||
break;
|
||||
|
||||
default:
|
||||
pa_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
pa_stream* pa_test_context_create_stream(pa_test_context *ctx, const char *name, uint32_t sink_idx, pa_format_info *format,
|
||||
pa_stream_flags_t flags, void *data, size_t length) {
|
||||
int r;
|
||||
pa_stream *s;
|
||||
pa_format_info *formats[1];
|
||||
char sink_name[5];
|
||||
|
||||
pa_threaded_mainloop_lock(ctx->mainloop);
|
||||
|
||||
formats[0] = format;
|
||||
|
||||
ctx->data = data;
|
||||
ctx->length = length;
|
||||
|
||||
s = pa_stream_new_extended(ctx->context, name, formats, 1, NULL);
|
||||
pa_assert(s);
|
||||
|
||||
pa_snprintf(sink_name, sizeof(sink_name), "%u", sink_idx);
|
||||
|
||||
pa_stream_set_state_callback(s, stream_state_callback, ctx);
|
||||
r = pa_stream_connect_playback(s, sink_name, NULL, flags, NULL, NULL);
|
||||
|
||||
pa_assert(r == 0);
|
||||
|
||||
pa_threaded_mainloop_wait(ctx->mainloop);
|
||||
|
||||
pa_assert(pa_stream_get_state(s) == PA_STREAM_READY);
|
||||
|
||||
pa_threaded_mainloop_unlock(ctx->mainloop);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void pa_test_context_destroy_stream(pa_test_context *ctx, pa_stream *s) {
|
||||
int r;
|
||||
|
||||
pa_threaded_mainloop_lock(ctx->mainloop);
|
||||
|
||||
r = pa_stream_disconnect(s);
|
||||
pa_assert(r == 0);
|
||||
|
||||
pa_threaded_mainloop_wait(ctx->mainloop);
|
||||
pa_assert(pa_stream_get_state(s) == PA_STREAM_TERMINATED);
|
||||
|
||||
pa_stream_unref(s);
|
||||
|
||||
pa_threaded_mainloop_unlock(ctx->mainloop);
|
||||
}
|
||||
|
||||
struct sink_info_pred {
|
||||
pa_test_context *ctx;
|
||||
pa_test_sink_info_pred_t func;
|
||||
void *userdata;
|
||||
|
||||
bool ret;
|
||||
};
|
||||
|
||||
static void check_sink_info(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
|
||||
struct sink_info_pred *pred = (struct sink_info_pred *) userdata;
|
||||
|
||||
pa_assert(c);
|
||||
pa_assert(pred);
|
||||
|
||||
if (i)
|
||||
pred->ret = pred->func(i, pred->userdata);
|
||||
|
||||
pa_threaded_mainloop_signal(pred->ctx->mainloop, false);
|
||||
}
|
||||
|
||||
bool pa_test_context_check_sink(pa_test_context *ctx, uint32_t idx, pa_test_sink_info_pred_t predicate, void *userdata) {
|
||||
pa_operation *o;
|
||||
struct sink_info_pred pred = { ctx, predicate, userdata, false };
|
||||
|
||||
pa_assert(ctx);
|
||||
pa_assert(predicate);
|
||||
|
||||
pa_threaded_mainloop_lock(ctx->mainloop);
|
||||
|
||||
o = pa_context_get_sink_info_by_index(ctx->context, idx, check_sink_info, &pred);
|
||||
WAIT_FOR_OPERATION(ctx, o);
|
||||
|
||||
pa_threaded_mainloop_unlock(ctx->mainloop);
|
||||
|
||||
return pred.ret;
|
||||
}
|
||||
74
src/tests/test-util.h
Normal file
74
src/tests/test-util.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#ifndef footestutilhfoo
|
||||
#define footestutilhfoo
|
||||
|
||||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
PulseAudio is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
PulseAudio 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 Lesser General Public License
|
||||
along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
#include <pulsecore/idxset.h>
|
||||
#include <pulsecore/macro.h>
|
||||
|
||||
#define WAIT_FOR_OPERATION(ctx, o) \
|
||||
do { \
|
||||
while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { \
|
||||
pa_threaded_mainloop_wait(ctx->mainloop); \
|
||||
} \
|
||||
\
|
||||
pa_assert(pa_operation_get_state(o) == PA_OPERATION_DONE); \
|
||||
pa_operation_unref(o); \
|
||||
} while (false)
|
||||
|
||||
typedef struct pa_test_context {
|
||||
/* "Public" members */
|
||||
pa_threaded_mainloop *mainloop;
|
||||
pa_mainloop_api *mainloop_api;
|
||||
pa_context *context;
|
||||
|
||||
/* "Private" bookkeeping */
|
||||
pa_idxset *modules;
|
||||
uint32_t module_idx, sink_idx; /* only used for module -> sink index lookup */
|
||||
void *data;
|
||||
size_t length;
|
||||
} pa_test_context;
|
||||
|
||||
pa_test_context* pa_test_context_new(const char *name);
|
||||
void pa_test_context_free(pa_test_context *ctx);
|
||||
|
||||
/* Loads a null sink with provided params to test with */
|
||||
uint32_t pa_test_context_load_null_sink(pa_test_context *ctx, const char *modargs);
|
||||
|
||||
/* A stream is created and started. The function doesn't wait for the data to
|
||||
* be played back, playback will continue in the background. The data buffer
|
||||
* will be played only once, after which an underflow callback will call
|
||||
* pa_threaded_mainloop_signal() so pa_threaded_mainloop_wait() can be used to
|
||||
* wait for the stream to finish playing.
|
||||
*/
|
||||
pa_stream* pa_test_context_create_stream(pa_test_context *ctx, const char *name, uint32_t sink_idx, pa_format_info *format,
|
||||
pa_stream_flags_t flags, void *data, size_t length);
|
||||
/* Clean up the stream */
|
||||
void pa_test_context_destroy_stream(pa_test_context *ctx, pa_stream *s);
|
||||
|
||||
typedef bool (*pa_test_sink_info_pred_t)(const pa_sink_info *sink_info, void *userdata);
|
||||
|
||||
/* Test the current state of the sink by providing a predicate function which
|
||||
* can examine the sink's pa_sink_info for whatever condition is expected. */
|
||||
bool pa_test_context_check_sink(pa_test_context *ctx, uint32_t idx, pa_test_sink_info_pred_t predicate, void *userdata);
|
||||
|
||||
#endif /* footestutilhfoo */
|
||||
Loading…
Add table
Add a link
Reference in a new issue