2 * FreeRDP: A Remote Desktop Protocol client.
3 * Audio Input Redirection Virtual Channel - ALSA implementation
5 * Copyright 2010-2011 Vic Lee
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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.
23 #include <alsa/asoundlib.h>
24 #include <freerdp/utils/memory.h>
25 #include <freerdp/utils/thread.h>
26 #include <freerdp/utils/dsp.h>
28 #include "audin_main.h"
30 typedef struct _AudinALSADevice
35 uint32 frames_per_packet;
38 snd_pcm_format_t format;
39 uint32 target_channels;
40 uint32 actual_channels;
41 int bytes_per_channel;
46 freerdp_thread* thread;
55 static boolean audin_alsa_set_params(AudinALSADevice* alsa, snd_pcm_t* capture_handle)
58 snd_pcm_hw_params_t* hw_params;
60 if ((error = snd_pcm_hw_params_malloc(&hw_params)) < 0)
62 DEBUG_WARN("snd_pcm_hw_params_malloc (%s)",
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,
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);
79 if ((alsa->actual_rate != alsa->target_rate) ||
80 (alsa->actual_channels != alsa->target_channels))
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);
90 static boolean audin_alsa_thread_receive(AudinALSADevice* alsa, uint8* src, int size)
99 uint8* resampled_data;
101 rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_channel;
102 tbytes_per_frame = alsa->target_channels * alsa->bytes_per_channel;
104 if ((alsa->target_rate == alsa->actual_rate) &&
105 (alsa->target_channels == alsa->actual_channels))
107 resampled_data = NULL;
108 frames = size / rbytes_per_frame;
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;
123 if (freerdp_thread_is_stopped(alsa->thread))
126 cframes = alsa->frames_per_packet - alsa->buffer_frames;
127 if (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)
134 if (alsa->wformat == 0x11)
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);
144 encoded_data = alsa->buffer;
145 encoded_size = alsa->buffer_frames * tbytes_per_frame;
148 if (freerdp_thread_is_stopped(alsa->thread))
154 ret = alsa->receive(encoded_data, encoded_size, alsa->user_data);
155 alsa->buffer_frames = 0;
156 if (encoded_data != alsa->buffer)
161 src += cframes * tbytes_per_frame;
166 xfree(resampled_data);
171 static void* audin_alsa_thread_func(void* arg)
175 int rbytes_per_frame;
176 int tbytes_per_frame;
177 snd_pcm_t* capture_handle = NULL;
178 AudinALSADevice* alsa = (AudinALSADevice*) arg;
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));
190 if ((error = snd_pcm_open(&capture_handle, alsa->device_name, SND_PCM_STREAM_CAPTURE, 0)) < 0)
192 DEBUG_WARN("snd_pcm_open (%s)", snd_strerror(error));
195 if (!audin_alsa_set_params(alsa, capture_handle))
200 while (!freerdp_thread_is_stopped(alsa->thread))
202 error = snd_pcm_readi(capture_handle, buffer, alsa->frames_per_packet);
205 snd_pcm_recover(capture_handle, error, 0);
210 DEBUG_WARN("snd_pcm_readi (%s)", snd_strerror(error));
213 if (!audin_alsa_thread_receive(alsa, buffer, error * rbytes_per_frame))
222 snd_pcm_close(capture_handle);
224 freerdp_thread_quit(alsa->thread);
231 static void audin_alsa_free(IAudinDevice* device)
233 AudinALSADevice* alsa = (AudinALSADevice*) device;
235 freerdp_thread_free(alsa->thread);
239 static boolean audin_alsa_format_supported(IAudinDevice* device, audinFormat* format)
241 switch (format->wFormatTag)
244 if (format->cbSize == 0 &&
245 (format->nSamplesPerSec <= 48000) &&
246 (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) &&
247 (format->nChannels == 1 || format->nChannels == 2))
253 case 0x11: /* IMA ADPCM */
254 if ((format->nSamplesPerSec <= 48000) &&
255 (format->wBitsPerSample == 4) &&
256 (format->nChannels == 1 || format->nChannels == 2))
265 static void audin_alsa_set_format(IAudinDevice* device, audinFormat* format, uint32 FramesPerPacket)
268 AudinALSADevice* alsa = (AudinALSADevice*) device;
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)
277 switch (format->wBitsPerSample)
280 alsa->format = SND_PCM_FORMAT_S8;
281 alsa->bytes_per_channel = 1;
284 alsa->format = SND_PCM_FORMAT_S16_LE;
285 alsa->bytes_per_channel = 2;
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);
300 alsa->wformat = format->wFormatTag;
301 alsa->block_size = format->nBlockAlign;
304 static void audin_alsa_open(IAudinDevice* device, AudinReceive receive, void* user_data)
306 AudinALSADevice* alsa = (AudinALSADevice*) device;
310 alsa->receive = receive;
311 alsa->user_data = user_data;
313 freerdp_thread_start(alsa->thread, audin_alsa_thread_func, alsa);
316 static void audin_alsa_close(IAudinDevice* device)
318 AudinALSADevice* alsa = (AudinALSADevice*) device;
322 freerdp_thread_stop(alsa->thread);
324 alsa->receive = NULL;
325 alsa->user_data = NULL;
328 int FreeRDPAudinDeviceEntry(PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints)
330 AudinALSADevice* alsa;
331 RDP_PLUGIN_DATA* data;
333 alsa = xnew(AudinALSADevice);
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;
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)
346 strncpy(alsa->device_name, (char*)data->data[2], sizeof(alsa->device_name));
348 if (alsa->device_name[0] == '\0')
350 strcpy(alsa->device_name, "default");
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();
362 pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*) alsa);