Initial commit - from Precise source
[freerdp-ubuntu-pcb-backport.git] / channels / drdynvc / audin / alsa / audin_alsa.c
1 /**
2  * FreeRDP: A Remote Desktop Protocol client.
3  * Audio Input Redirection Virtual Channel - ALSA implementation
4  *
5  * Copyright 2010-2011 Vic Lee
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <alsa/asoundlib.h>
24 #include <freerdp/utils/memory.h>
25 #include <freerdp/utils/thread.h>
26 #include <freerdp/utils/dsp.h>
27
28 #include "audin_main.h"
29
30 typedef struct _AudinALSADevice
31 {
32         IAudinDevice iface;
33
34         char device_name[32];
35         uint32 frames_per_packet;
36         uint32 target_rate;
37         uint32 actual_rate;
38         snd_pcm_format_t format;
39         uint32 target_channels;
40         uint32 actual_channels;
41         int bytes_per_channel;
42         int wformat;
43         int block_size;
44         ADPCM adpcm;
45
46         freerdp_thread* thread;
47
48         uint8* buffer;
49         int buffer_frames;
50
51         AudinReceive receive;
52         void* user_data;
53 } AudinALSADevice;
54
55 static boolean audin_alsa_set_params(AudinALSADevice* alsa, snd_pcm_t* capture_handle)
56 {
57         int error;
58         snd_pcm_hw_params_t* hw_params;
59
60         if ((error = snd_pcm_hw_params_malloc(&hw_params)) < 0)
61         {
62                 DEBUG_WARN("snd_pcm_hw_params_malloc (%s)",
63                          snd_strerror(error));
64                 return false;
65         }
66         snd_pcm_hw_params_any(capture_handle, hw_params);
67         snd_pcm_hw_params_set_access(capture_handle, hw_params,
68                 SND_PCM_ACCESS_RW_INTERLEAVED);
69         snd_pcm_hw_params_set_format(capture_handle, hw_params,
70                 alsa->format);
71         snd_pcm_hw_params_set_rate_near(capture_handle, hw_params,
72                 &alsa->actual_rate, NULL);
73         snd_pcm_hw_params_set_channels_near(capture_handle, hw_params,
74                 &alsa->actual_channels);
75         snd_pcm_hw_params(capture_handle, hw_params);
76         snd_pcm_hw_params_free(hw_params);
77         snd_pcm_prepare(capture_handle);
78
79         if ((alsa->actual_rate != alsa->target_rate) ||
80                 (alsa->actual_channels != alsa->target_channels))
81         {
82                 DEBUG_DVC("actual rate %d / channel %d is "
83                         "different from target rate %d / channel %d, resampling required.",
84                         alsa->actual_rate, alsa->actual_channels,
85                         alsa->target_rate, alsa->target_channels);
86         }
87         return true;
88 }
89
90 static boolean audin_alsa_thread_receive(AudinALSADevice* alsa, uint8* src, int size)
91 {
92         int frames;
93         int cframes;
94         int ret = 0;
95         int encoded_size;
96         uint8* encoded_data;
97         int rbytes_per_frame;
98         int tbytes_per_frame;
99         uint8* resampled_data;
100
101         rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_channel;
102         tbytes_per_frame = alsa->target_channels * alsa->bytes_per_channel;
103
104         if ((alsa->target_rate == alsa->actual_rate) &&
105                 (alsa->target_channels == alsa->actual_channels))
106         {
107                 resampled_data = NULL;
108                 frames = size / rbytes_per_frame;
109         }
110         else
111         {
112                 resampled_data = dsp_resample(src, alsa->bytes_per_channel,
113                         alsa->actual_channels, alsa->actual_rate, size / rbytes_per_frame,
114                         alsa->target_channels, alsa->target_rate, &frames);
115                 DEBUG_DVC("resampled %d frames at %d to %d frames at %d",
116                         size / rbytes_per_frame, alsa->actual_rate, frames, alsa->target_rate);
117                 size = frames * tbytes_per_frame;
118                 src = resampled_data;
119         }
120
121         while (frames > 0)
122         {
123                 if (freerdp_thread_is_stopped(alsa->thread))
124                         break;
125
126                 cframes = alsa->frames_per_packet - alsa->buffer_frames;
127                 if (cframes > frames)
128                         cframes = frames;
129                 memcpy(alsa->buffer + alsa->buffer_frames * tbytes_per_frame,
130                         src, cframes * tbytes_per_frame);
131                 alsa->buffer_frames += cframes;
132                 if (alsa->buffer_frames >= alsa->frames_per_packet)
133                 {
134                         if (alsa->wformat == 0x11)
135                         {
136                                 encoded_data = dsp_encode_ima_adpcm(&alsa->adpcm,
137                                         alsa->buffer, alsa->buffer_frames * tbytes_per_frame,
138                                         alsa->target_channels, alsa->block_size, &encoded_size);
139                                 DEBUG_DVC("encoded %d to %d",
140                                         alsa->buffer_frames * tbytes_per_frame, encoded_size);
141                         }
142                         else
143                         {
144                                 encoded_data = alsa->buffer;
145                                 encoded_size = alsa->buffer_frames * tbytes_per_frame;
146                         }
147
148                         if (freerdp_thread_is_stopped(alsa->thread))
149                         {
150                                 ret = 0;
151                                 frames = 0;
152                         }
153                         else
154                                 ret = alsa->receive(encoded_data, encoded_size, alsa->user_data);
155                         alsa->buffer_frames = 0;
156                         if (encoded_data != alsa->buffer)
157                                 xfree(encoded_data);
158                         if (!ret)
159                                 break;
160                 }
161                 src += cframes * tbytes_per_frame;
162                 frames -= cframes;
163         }
164
165         if (resampled_data)
166                 xfree(resampled_data);
167
168         return ret;
169 }
170
171 static void* audin_alsa_thread_func(void* arg)
172 {
173         int error;
174         uint8* buffer;
175         int rbytes_per_frame;
176         int tbytes_per_frame;
177         snd_pcm_t* capture_handle = NULL;
178         AudinALSADevice* alsa = (AudinALSADevice*) arg;
179
180         DEBUG_DVC("in");
181
182         rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_channel;
183         tbytes_per_frame = alsa->target_channels * alsa->bytes_per_channel;
184         alsa->buffer = (uint8*) xzalloc(tbytes_per_frame * alsa->frames_per_packet);
185         alsa->buffer_frames = 0;
186         buffer = (uint8*) xzalloc(rbytes_per_frame * alsa->frames_per_packet);
187         memset(&alsa->adpcm, 0, sizeof(ADPCM));
188         do
189         {
190                 if ((error = snd_pcm_open(&capture_handle, alsa->device_name, SND_PCM_STREAM_CAPTURE, 0)) < 0)
191                 {
192                         DEBUG_WARN("snd_pcm_open (%s)", snd_strerror(error));
193                         break;
194                 }
195                 if (!audin_alsa_set_params(alsa, capture_handle))
196                 {
197                         break;
198                 }
199
200                 while (!freerdp_thread_is_stopped(alsa->thread))
201                 {
202                         error = snd_pcm_readi(capture_handle, buffer, alsa->frames_per_packet);
203                         if (error == -EPIPE)
204                         {
205                                 snd_pcm_recover(capture_handle, error, 0);
206                                 continue;
207                         }
208                         else if (error < 0)
209                         {
210                                 DEBUG_WARN("snd_pcm_readi (%s)", snd_strerror(error));
211                                 break;
212                         }
213                         if (!audin_alsa_thread_receive(alsa, buffer, error * rbytes_per_frame))
214                                 break;
215                 }
216         } while (0);
217
218         xfree(buffer);
219         xfree(alsa->buffer);
220         alsa->buffer = NULL;
221         if (capture_handle)
222                 snd_pcm_close(capture_handle);
223
224         freerdp_thread_quit(alsa->thread);
225
226         DEBUG_DVC("out");
227
228         return NULL;
229 }
230
231 static void audin_alsa_free(IAudinDevice* device)
232 {
233         AudinALSADevice* alsa = (AudinALSADevice*) device;
234
235         freerdp_thread_free(alsa->thread);
236         xfree(alsa);
237 }
238
239 static boolean audin_alsa_format_supported(IAudinDevice* device, audinFormat* format)
240 {
241         switch (format->wFormatTag)
242         {
243                 case 1: /* PCM */
244                         if (format->cbSize == 0 &&
245                                 (format->nSamplesPerSec <= 48000) &&
246                                 (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
247                                 (format->nChannels == 1 || format->nChannels == 2))
248                         {
249                                 return true;
250                         }
251                         break;
252
253                 case 0x11: /* IMA ADPCM */
254                         if ((format->nSamplesPerSec <= 48000) &&
255                                 (format->wBitsPerSample == 4) &&
256                                 (format->nChannels == 1 || format->nChannels == 2))
257                         {
258                                 return true;
259                         }
260                         break;
261         }
262         return false;
263 }
264
265 static void audin_alsa_set_format(IAudinDevice* device, audinFormat* format, uint32 FramesPerPacket)
266 {
267         int bs;
268         AudinALSADevice* alsa = (AudinALSADevice*) device;
269
270         alsa->target_rate = format->nSamplesPerSec;
271         alsa->actual_rate = format->nSamplesPerSec;
272         alsa->target_channels = format->nChannels;
273         alsa->actual_channels = format->nChannels;
274         switch (format->wFormatTag)
275         {
276                 case 1: /* PCM */
277                         switch (format->wBitsPerSample)
278                         {
279                                 case 8:
280                                         alsa->format = SND_PCM_FORMAT_S8;
281                                         alsa->bytes_per_channel = 1;
282                                         break;
283                                 case 16:
284                                         alsa->format = SND_PCM_FORMAT_S16_LE;
285                                         alsa->bytes_per_channel = 2;
286                                         break;
287                         }
288                         break;
289
290                 case 0x11: /* IMA ADPCM */
291                         alsa->format = SND_PCM_FORMAT_S16_LE;
292                         alsa->bytes_per_channel = 2;
293                         bs = (format->nBlockAlign - 4 * format->nChannels) * 4;
294                         alsa->frames_per_packet = (alsa->frames_per_packet * format->nChannels * 2 /
295                                 bs + 1) * bs / (format->nChannels * 2);
296                         DEBUG_DVC("aligned FramesPerPacket=%d",
297                                 alsa->frames_per_packet);
298                         break;
299         }
300         alsa->wformat = format->wFormatTag;
301         alsa->block_size = format->nBlockAlign;
302 }
303
304 static void audin_alsa_open(IAudinDevice* device, AudinReceive receive, void* user_data)
305 {
306         AudinALSADevice* alsa = (AudinALSADevice*) device;
307
308         DEBUG_DVC("");
309
310         alsa->receive = receive;
311         alsa->user_data = user_data;
312
313         freerdp_thread_start(alsa->thread, audin_alsa_thread_func, alsa);
314 }
315
316 static void audin_alsa_close(IAudinDevice* device)
317 {
318         AudinALSADevice* alsa = (AudinALSADevice*) device;
319
320         DEBUG_DVC("");
321
322         freerdp_thread_stop(alsa->thread);
323
324         alsa->receive = NULL;
325         alsa->user_data = NULL;
326 }
327
328 int FreeRDPAudinDeviceEntry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints)
329 {
330         AudinALSADevice* alsa;
331         RDP_PLUGIN_DATA* data;
332
333         alsa = xnew(AudinALSADevice);
334
335         alsa->iface.Open = audin_alsa_open;
336         alsa->iface.FormatSupported = audin_alsa_format_supported;
337         alsa->iface.SetFormat = audin_alsa_set_format;
338         alsa->iface.Close = audin_alsa_close;
339         alsa->iface.Free = audin_alsa_free;
340
341         data = pEntryPoints->plugin_data;
342         if (data && data->data[0] && strcmp(data->data[0], "audin") == 0 &&
343                 data->data[1] && strcmp(data->data[1], "alsa") == 0)
344         {
345                 if (data[2].size)
346                         strncpy(alsa->device_name, (char*)data->data[2], sizeof(alsa->device_name));
347         }
348         if (alsa->device_name[0] == '\0')
349         {
350                 strcpy(alsa->device_name, "default");
351         }
352
353         alsa->frames_per_packet = 128;
354         alsa->target_rate = 22050;
355         alsa->actual_rate = 22050;
356         alsa->format = SND_PCM_FORMAT_S16_LE;
357         alsa->target_channels = 2;
358         alsa->actual_channels = 2;
359         alsa->bytes_per_channel = 2;
360         alsa->thread = freerdp_thread_new();
361
362         pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*) alsa);
363
364         return 0;
365 }
366