1/*** 2 This file is part of PulseAudio. 3 4 PulseAudio is free software; you can redistribute it and/or modify 5 it under the terms of the GNU Lesser General Public License as published 6 by the Free Software Foundation; either version 2.1 of the License, 7 or (at your option) any later version. 8 9 PulseAudio is distributed in the hope that it will be useful, but 10 WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 General Public License for more details. 13 14 You should have received a copy of the GNU Lesser General Public License 15 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 16***/ 17 18#ifdef HAVE_CONFIG_H 19#include <config.h> 20#endif 21 22#include <signal.h> 23#include <errno.h> 24#include <unistd.h> 25#include <assert.h> 26#include <stdio.h> 27#include <stdlib.h> 28#include <math.h> 29 30#include <check.h> 31 32#include <pulse/pulseaudio.h> 33#include <pulse/mainloop.h> 34#include <pulsecore/macro.h> 35 36#define NSTREAMS 4 37#define SINE_HZ 440 38#define SAMPLE_HZ 8000 39 40static pa_context *context = NULL; 41static pa_stream *streams[NSTREAMS]; 42static pa_mainloop_api *mainloop_api = NULL; 43static const char *bname; 44 45static float data[SAMPLE_HZ]; /* one second space */ 46 47static int n_streams_ready = 0; 48 49static const pa_buffer_attr buffer_attr = { 50 .maxlength = SAMPLE_HZ*sizeof(float)*NSTREAMS, /* exactly space for the entire play time */ 51 .tlength = (uint32_t) -1, 52 .prebuf = 0, /* Setting prebuf to 0 guarantees us the streams will run synchronously, no matter what */ 53 .minreq = (uint32_t) -1, 54 .fragsize = 0 55}; 56 57static void nop_free_cb(void *p) {} 58 59static void underflow_cb(struct pa_stream *s, void *userdata) { 60 int i = PA_PTR_TO_INT(userdata); 61 62 fprintf(stderr, "Stream %i finished\n", i); 63 64 if (++n_streams_ready >= 2*NSTREAMS) { 65 fprintf(stderr, "We're done\n"); 66 mainloop_api->quit(mainloop_api, 0); 67 } 68} 69 70/* This routine is called whenever the stream state changes */ 71static void stream_state_callback(pa_stream *s, void *userdata) { 72 fail_unless(s != NULL); 73 74 switch (pa_stream_get_state(s)) { 75 case PA_STREAM_UNCONNECTED: 76 case PA_STREAM_CREATING: 77 case PA_STREAM_TERMINATED: 78 break; 79 80 case PA_STREAM_READY: { 81 82 int r, i = PA_PTR_TO_INT(userdata); 83 84 fprintf(stderr, "Writing data to stream %i.\n", i); 85 86 r = pa_stream_write(s, data, sizeof(data), nop_free_cb, (int64_t) sizeof(data) * (int64_t) i, PA_SEEK_ABSOLUTE); 87 fail_unless(r == 0); 88 89 /* Be notified when this stream is drained */ 90 pa_stream_set_underflow_callback(s, underflow_cb, userdata); 91 92 /* All streams have been set up, let's go! */ 93 if (++n_streams_ready >= NSTREAMS) { 94 fprintf(stderr, "Uncorking\n"); 95 pa_operation_unref(pa_stream_cork(s, 0, NULL, NULL)); 96 } 97 98 break; 99 } 100 101 default: 102 case PA_STREAM_FAILED: 103 fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); 104 ck_abort(); 105 } 106} 107 108/* This is called whenever the context status changes */ 109static void context_state_callback(pa_context *c, void *userdata) { 110 fail_unless(c != NULL); 111 112 switch (pa_context_get_state(c)) { 113 case PA_CONTEXT_CONNECTING: 114 case PA_CONTEXT_AUTHORIZING: 115 case PA_CONTEXT_SETTING_NAME: 116 break; 117 118 case PA_CONTEXT_READY: { 119 120 int i; 121 fprintf(stderr, "Connection established.\n"); 122 123 for (i = 0; i < NSTREAMS; i++) { 124 char name[64]; 125 pa_format_info *formats[1]; 126 127 formats[0] = pa_format_info_new(); 128 formats[0]->encoding = PA_ENCODING_PCM; 129 pa_format_info_set_sample_format(formats[0], PA_SAMPLE_FLOAT32); 130 pa_format_info_set_rate(formats[0], SAMPLE_HZ); 131 pa_format_info_set_channels(formats[0], 1); 132 133 fprintf(stderr, "Creating stream %i\n", i); 134 135 snprintf(name, sizeof(name), "stream #%i", i); 136 137 streams[i] = pa_stream_new_extended(c, name, formats, 1, NULL); 138 fail_unless(streams[i] != NULL); 139 pa_stream_set_state_callback(streams[i], stream_state_callback, PA_INT_TO_PTR(i)); 140 pa_stream_connect_playback(streams[i], NULL, &buffer_attr, PA_STREAM_START_CORKED, NULL, i == 0 ? NULL : streams[0]); 141 142 pa_format_info_free(formats[0]); 143 } 144 145 break; 146 } 147 148 case PA_CONTEXT_TERMINATED: 149 mainloop_api->quit(mainloop_api, 0); 150 break; 151 152 case PA_CONTEXT_FAILED: 153 default: 154 fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c))); 155 ck_abort(); 156 } 157} 158 159START_TEST (extended_test) { 160 pa_mainloop* m = NULL; 161 int i, ret = 1; 162 163 for (i = 0; i < SAMPLE_HZ; i++) 164 data[i] = (float) sin(((double) i/SAMPLE_HZ)*2*M_PI*SINE_HZ)/2; 165 166 for (i = 0; i < NSTREAMS; i++) 167 streams[i] = NULL; 168 169 /* Set up a new main loop */ 170 m = pa_mainloop_new(); 171 fail_unless(m != NULL); 172 173 mainloop_api = pa_mainloop_get_api(m); 174 175 context = pa_context_new(mainloop_api, bname); 176 fail_unless(context != NULL); 177 178 pa_context_set_state_callback(context, context_state_callback, NULL); 179 180 /* Connect the context */ 181 if (pa_context_connect(context, NULL, 0, NULL) < 0) { 182 fprintf(stderr, "pa_context_connect() failed.\n"); 183 goto quit; 184 } 185 186 if (pa_mainloop_run(m, &ret) < 0) 187 fprintf(stderr, "pa_mainloop_run() failed.\n"); 188 189quit: 190 pa_context_unref(context); 191 192 for (i = 0; i < NSTREAMS; i++) 193 if (streams[i]) 194 pa_stream_unref(streams[i]); 195 196 pa_mainloop_free(m); 197 198 fail_unless(ret == 0); 199} 200END_TEST 201 202int main(int argc, char *argv[]) { 203 int failed = 0; 204 Suite *s; 205 TCase *tc; 206 SRunner *sr; 207 208 bname = argv[0]; 209 210 s = suite_create("Extended"); 211 tc = tcase_create("extended"); 212 tcase_add_test(tc, extended_test); 213 /* 4s of audio, 0.5s grace time */ 214 tcase_set_timeout(tc, 4.5); 215 suite_add_tcase(s, tc); 216 217 sr = srunner_create(s); 218 srunner_run_all(sr, CK_NORMAL); 219 failed = srunner_ntests_failed(sr); 220 srunner_free(sr); 221 222 return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 223} 224