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