Add sound manager initial source code
[staging/soundmanager.git] / sample / radio / binding / radio_output.c
1 /*
2  * Copyright (C) 2017 Konsulko Group
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <errno.h>
21 #include <pulse/pulseaudio.h>
22
23 #include "radio_output.h"
24 #include "rtl_fm.h"
25
26 static pa_threaded_mainloop *mainloop;
27 static pa_context *context;
28 static pa_stream *stream;
29
30 static unsigned int extra;
31 static int16_t extra_buf[1];
32 static unsigned char *output_buf;
33
34 static void pa_context_state_cb(pa_context *c, void *data) {
35         pa_operation *o;
36
37         assert(c);
38         switch (pa_context_get_state(c)) {
39                 case PA_CONTEXT_CONNECTING:
40                 case PA_CONTEXT_AUTHORIZING:
41                 case PA_CONTEXT_SETTING_NAME:
42                 case PA_CONTEXT_READY:
43                         break;
44                 case PA_CONTEXT_TERMINATED:
45                         pa_threaded_mainloop_stop(mainloop);
46                         break;
47                 case PA_CONTEXT_FAILED:
48                 default:
49                         fprintf(stderr, "PA connection failed: %s\n",
50                                 pa_strerror(pa_context_errno(c)));
51                         pa_threaded_mainloop_stop(mainloop);
52                         break;
53         }
54         pa_threaded_mainloop_signal(mainloop, 0);
55 }
56
57 int radio_output_open(void)
58 {
59         pa_context *c;
60         pa_mainloop_api *mapi;
61         char *client;
62
63         if(context)
64                 return 0;
65
66         if (!(mainloop = pa_threaded_mainloop_new())) {
67                 fprintf(stderr, "pa_mainloop_new() failed.\n");
68                 return -1;
69         }
70
71         pa_threaded_mainloop_set_name(mainloop, "pa_mainloop");
72         mapi = pa_threaded_mainloop_get_api(mainloop);
73
74         client = pa_xstrdup("radio");
75         if (!(c = pa_context_new(mapi, client))) {
76                 fprintf(stderr, "pa_context_new() failed.\n");
77                 goto exit;
78         }
79
80         pa_context_set_state_callback(c, pa_context_state_cb, NULL);
81         if (pa_context_connect(c, NULL, 0, NULL) < 0) {
82                 fprintf(stderr, "pa_context_connect(): %s", pa_strerror(pa_context_errno(c)));
83                 goto exit;
84         }
85
86         if (pa_threaded_mainloop_start(mainloop) < 0) {
87                 fprintf(stderr, "pa_mainloop_run() failed.\n");
88                 goto exit;
89         }
90
91         context = c;
92
93         extra = 0;
94         output_buf = malloc(sizeof(unsigned char) * RTL_FM_MAXIMUM_BUF_LENGTH);
95
96         return 0;
97
98 exit:
99         if (c)
100                 pa_context_unref(c);
101
102         if (mainloop)
103                 pa_threaded_mainloop_free(mainloop);
104
105         pa_xfree(client);
106         return -1;
107 }
108
109 int radio_output_start(void)
110 {
111         int error = 0;
112         pa_sample_spec *spec;
113
114         if(stream)
115                 return 0;
116
117         if(!context) {
118                 error = radio_output_open();
119                 if(error != 0)
120                         return error;
121         }
122
123         while(pa_context_get_state(context) != PA_CONTEXT_READY)
124                 pa_threaded_mainloop_wait(mainloop);
125
126         spec = (pa_sample_spec*) calloc(1, sizeof(pa_sample_spec));
127         spec->format = PA_SAMPLE_S16LE;
128         spec->rate = 24000;
129         spec->channels = 2;
130         if (!pa_sample_spec_valid(spec)) {
131                 fprintf(stderr, "%s\n",
132                         pa_strerror(pa_context_errno(context)));
133                 return -1;
134         }
135
136         pa_threaded_mainloop_lock(mainloop);
137         pa_proplist *props = pa_proplist_new();
138         pa_proplist_sets(props, PA_PROP_MEDIA_ROLE, "radio");
139         stream = pa_stream_new_with_proplist(context, "radio-output", spec, 0, props);
140         if(!stream) {
141                 fprintf(stderr, "Error creating stream %s\n",
142                         pa_strerror(pa_context_errno(context)));
143                 pa_proplist_free(props);
144                 free(spec);
145                 pa_threaded_mainloop_unlock(mainloop);
146                 return -1;
147         }
148         pa_proplist_free(props);
149         free(spec);
150
151         if(pa_stream_connect_playback(stream,
152                                       NULL,
153                                       NULL,
154                                       (pa_stream_flags_t) 0,
155                                       NULL,
156                                       NULL) < 0) {
157                 fprintf(stderr, "Error connecting to PulseAudio : %s\n",
158                         pa_strerror(pa_context_errno(context)));
159                 pa_stream_unref(stream);
160                 stream = NULL;
161                 pa_threaded_mainloop_unlock(mainloop);
162                 return -1;
163         }
164
165         pa_threaded_mainloop_unlock(mainloop);
166
167         while(pa_stream_get_state(stream) != PA_STREAM_READY)
168                 pa_threaded_mainloop_wait(mainloop);
169
170         return error;
171 }
172
173 void radio_output_stop(void)
174 {
175         if(stream) {
176                 pa_threaded_mainloop_lock(mainloop);
177
178                 pa_stream_set_state_callback(stream, 0, 0);
179                 pa_stream_set_write_callback(stream, 0, 0);
180                 pa_stream_set_underflow_callback(stream, 0, 0);
181                 pa_stream_set_overflow_callback(stream, 0, 0);
182                 pa_stream_set_latency_update_callback(stream, 0, 0);
183
184                 pa_operation *o = pa_stream_flush(stream, NULL, NULL);
185                 if(o)
186                         pa_operation_unref(o);
187
188                 pa_stream_disconnect(stream);
189                 pa_stream_unref(stream);
190                 stream = NULL;
191
192                 pa_threaded_mainloop_unlock(mainloop);
193         }
194 }
195
196 void radio_output_suspend(int state)
197 {
198         if(stream) {
199                 pa_stream_cork(stream, state, NULL, NULL);
200         }
201 }
202
203 void radio_output_close(void)
204 {
205         radio_output_stop();
206
207         if(context) {
208                 pa_context_disconnect(context);
209                 pa_context_unref(context);
210                 context = NULL;
211         }
212
213         if(mainloop) {
214                 pa_threaded_mainloop_stop(mainloop);
215                 pa_threaded_mainloop_free(mainloop);
216                 mainloop = NULL;
217         }
218
219         free(output_buf);
220         output_buf = NULL;
221 }
222
223 int radio_output_write(void *buf, int len)
224 {
225         int rc = -EINVAL;
226         int error;
227         size_t n = len;
228         size_t avail;
229         int samples = len / 2;
230         void *p;
231
232         if(!stream) {
233                 return -1;
234         }
235
236         if(!buf) {
237                 fprintf(stderr, "Error: buf == null!\n");
238                 return rc;
239         }
240
241         pa_threaded_mainloop_lock(mainloop);
242
243         avail = pa_stream_writable_size(stream);
244         if(avail < n) {
245                 /*
246                  * NOTE: Definitely room for improvement here,but for now just
247                  *       check for the no space case that happens when the
248                  *       stream is corked.
249                  */
250                 if(!avail) {
251                         rc = 0;
252                         goto exit;
253                 }
254         }
255
256         /*
257          * Handle the rtl_fm code giving us an odd number of samples, which
258          * PA does not like.  This extra buffer copying approach is not
259          * particularly efficient, but works for now.  It looks feasible to
260          * hack in something in the demod and output thread routines in
261          * rtl_fm.c to handle it there if more performance is required.
262          */
263         p = output_buf;
264         if(extra) {
265                 memcpy(output_buf, extra_buf, sizeof(int16_t));
266                 if((extra + samples) % 2) {
267                         // We still have an extra sample, n remains the same, store the extra
268                         memcpy(output_buf + sizeof(int16_t), buf, n - 2);
269                         memcpy(extra_buf, ((unsigned char*) buf) + n - 2, sizeof(int16_t));
270                 } else {
271                         // We have an even number of samples, no extra
272                         memcpy(output_buf + sizeof(int16_t), buf, n);
273                         n += 2;
274                         extra = 0;
275                 }
276         } else if(samples % 2) {
277                 // We have an extra sample, store it, and decrease n
278                 n -= 2;
279                 memcpy(output_buf + sizeof(int16_t), buf, n);
280                 memcpy(extra_buf, ((unsigned char*) buf) + n, sizeof(int16_t));
281                 extra = 1;
282         } else {
283                 p = buf;
284         }
285
286         if ((rc = pa_stream_write(stream, p, n, NULL, 0, PA_SEEK_RELATIVE)) < 0) {
287                 fprintf(stderr, "Error writing %d bytes to PulseAudio : %s\n",
288                         n, pa_strerror(pa_context_errno(context)));
289         }
290 exit:
291         pa_threaded_mainloop_unlock(mainloop);
292
293         return rc;
294 }