mirror of
				https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
				synced 2025-11-03 09:01:50 -05:00 
			
		
		
		
	tests: Add a unit test for passthrough support
This adds some basic infrastructure to test passthrough support. Right now, it just creates a passthrough stream and makes sure negotiation works. We'll add in more tests as we go along.
This commit is contained in:
		
							parent
							
								
									5fd17efc6c
								
							
						
					
					
						commit
						cda9a5f1d3
					
				
					 2 changed files with 296 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -299,6 +299,7 @@ TESTS_norun = \
 | 
			
		|||
TESTS_daemon = \
 | 
			
		||||
		connect-stress \
 | 
			
		||||
		extended-test \
 | 
			
		||||
		passthrough-test \
 | 
			
		||||
		interpol-test \
 | 
			
		||||
		sync-playback
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -478,6 +479,11 @@ extended_test_LDADD = $(AM_LDADD) libpulse.la
 | 
			
		|||
extended_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
 | 
			
		||||
extended_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBCHECK_LIBS)
 | 
			
		||||
 | 
			
		||||
passthrough_test_SOURCES = tests/passthrough-test.c
 | 
			
		||||
passthrough_test_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINOR@.la
 | 
			
		||||
passthrough_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
 | 
			
		||||
passthrough_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBCHECK_LIBS)
 | 
			
		||||
 | 
			
		||||
strlist_test_SOURCES = tests/strlist-test.c
 | 
			
		||||
strlist_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
 | 
			
		||||
strlist_test_LDADD = $(AM_LDADD) $(WINSOCK_LIBS) libpulsecore-@PA_MAJORMINOR@.la libpulse.la libpulsecommon-@PA_MAJORMINOR@.la
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										290
									
								
								src/tests/passthrough-test.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								src/tests/passthrough-test.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,290 @@
 | 
			
		|||
/***
 | 
			
		||||
  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 <stdio.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include <check.h>
 | 
			
		||||
 | 
			
		||||
#include <pulse/pulseaudio.h>
 | 
			
		||||
 | 
			
		||||
#include <pulsecore/core-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 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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void success_cb(pa_context *c, int success, void *userdata) {
 | 
			
		||||
    fail_unless(success != 0);
 | 
			
		||||
 | 
			
		||||
    pa_threaded_mainloop_signal(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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void passthrough_setup() {
 | 
			
		||||
    char modargs[128];
 | 
			
		||||
    pa_operation *o;
 | 
			
		||||
    int r;
 | 
			
		||||
 | 
			
		||||
    /* 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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static pa_stream* connect_stream() {
 | 
			
		||||
    int r;
 | 
			
		||||
    pa_stream *s;
 | 
			
		||||
    pa_format_info *formats[1];
 | 
			
		||||
 | 
			
		||||
    pa_threaded_mainloop_lock(mainloop);
 | 
			
		||||
 | 
			
		||||
    formats[0] = pa_format_info_new();
 | 
			
		||||
    formats[0]->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);
 | 
			
		||||
 | 
			
		||||
    s = pa_stream_new_extended(context, "passthrough test", formats, 1, NULL);
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
START_TEST (passthrough_playback_test) {
 | 
			
		||||
    /* Create a passthrough stream, and make sure format negotiation actually
 | 
			
		||||
     * works */
 | 
			
		||||
    pa_stream *stream;
 | 
			
		||||
 | 
			
		||||
    stream = connect_stream();
 | 
			
		||||
 | 
			
		||||
    /* Wait for underflow_cb() */
 | 
			
		||||
    pa_threaded_mainloop_lock(mainloop);
 | 
			
		||||
    pa_threaded_mainloop_wait(mainloop);
 | 
			
		||||
    fail_unless(pa_stream_get_state(stream) == PA_STREAM_READY);
 | 
			
		||||
    pa_threaded_mainloop_unlock(mainloop);
 | 
			
		||||
 | 
			
		||||
    disconnect_stream(stream);
 | 
			
		||||
}
 | 
			
		||||
END_TEST
 | 
			
		||||
 | 
			
		||||
int main(int argc, char *argv[]) {
 | 
			
		||||
    int failed = 0;
 | 
			
		||||
    Suite *s;
 | 
			
		||||
    TCase *tc;
 | 
			
		||||
    SRunner *sr;
 | 
			
		||||
 | 
			
		||||
    bname = argv[0];
 | 
			
		||||
 | 
			
		||||
    s = suite_create("Passthrough");
 | 
			
		||||
    tc = tcase_create("passthrough");
 | 
			
		||||
    tcase_add_checked_fixture(tc, passthrough_setup, passthrough_teardown);
 | 
			
		||||
    tcase_add_test(tc, passthrough_playback_test);
 | 
			
		||||
    tcase_set_timeout(tc, 4);
 | 
			
		||||
    suite_add_tcase(s, tc);
 | 
			
		||||
 | 
			
		||||
    sr = srunner_create(s);
 | 
			
		||||
    srunner_run_all(sr, CK_NORMAL);
 | 
			
		||||
    failed = srunner_ntests_failed(sr);
 | 
			
		||||
    srunner_free(sr);
 | 
			
		||||
 | 
			
		||||
    return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue