Initial commit - from Precise source
[freerdp-ubuntu-pcb-backport.git] / channels / drdynvc / tsmf / alsa / tsmf_alsa.c
1 /**
2  * FreeRDP: A Remote Desktop Protocol client.
3  * Video Redirection Virtual Channel - ALSA Audio Device
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 <pthread.h>
24 #include <unistd.h>
25 #include <alsa/asoundlib.h>
26 #include <freerdp/types.h>
27 #include <freerdp/utils/memory.h>
28 #include <freerdp/utils/dsp.h>
29
30 #include "tsmf_audio.h"
31
32 typedef struct _TSMFALSAAudioDevice
33 {
34         ITSMFAudioDevice iface;
35
36         char device[32];
37         snd_pcm_t* out_handle;
38         uint32 source_rate;
39         uint32 actual_rate;
40         uint32 source_channels;
41         uint32 actual_channels;
42         uint32 bytes_per_sample;
43 } TSMFALSAAudioDevice;
44
45 static boolean tsmf_alsa_open_device(TSMFALSAAudioDevice* alsa)
46 {
47         int error;
48
49         error = snd_pcm_open(&alsa->out_handle, alsa->device, SND_PCM_STREAM_PLAYBACK, 0);
50         if (error < 0)
51         {
52                 DEBUG_WARN("failed to open device %s", alsa->device);
53                 return false;
54         }
55
56         DEBUG_DVC("open device %s", alsa->device);
57         return true;
58 }
59
60 static boolean tsmf_alsa_open(ITSMFAudioDevice* audio, const char* device)
61 {
62         TSMFALSAAudioDevice* alsa = (TSMFALSAAudioDevice*) audio;
63
64         if (!device)
65         {
66                 if (!alsa->device[0])
67                         strcpy(alsa->device, "default");
68         }
69         else
70         {
71                 strcpy(alsa->device, device);
72         }
73
74         return tsmf_alsa_open_device(alsa);
75 }
76
77 static boolean tsmf_alsa_set_format(ITSMFAudioDevice* audio,
78         uint32 sample_rate, uint32 channels, uint32 bits_per_sample)
79 {
80         int error;
81         snd_pcm_uframes_t frames;
82         snd_pcm_hw_params_t* hw_params;
83         snd_pcm_sw_params_t* sw_params;
84         TSMFALSAAudioDevice* alsa = (TSMFALSAAudioDevice*) audio;
85
86         if (!alsa->out_handle)
87                 return false;
88
89         snd_pcm_drop(alsa->out_handle);
90
91         alsa->actual_rate = alsa->source_rate = sample_rate;
92         alsa->actual_channels = alsa->source_channels = channels;
93         alsa->bytes_per_sample = bits_per_sample / 8;
94
95         error = snd_pcm_hw_params_malloc(&hw_params);
96         if (error < 0)
97         {
98                 DEBUG_WARN("snd_pcm_hw_params_malloc failed");
99                 return false;
100         }
101         snd_pcm_hw_params_any(alsa->out_handle, hw_params);
102         snd_pcm_hw_params_set_access(alsa->out_handle, hw_params,
103                 SND_PCM_ACCESS_RW_INTERLEAVED);
104         snd_pcm_hw_params_set_format(alsa->out_handle, hw_params,
105                 SND_PCM_FORMAT_S16_LE);
106         snd_pcm_hw_params_set_rate_near(alsa->out_handle, hw_params,
107                 &alsa->actual_rate, NULL);
108         snd_pcm_hw_params_set_channels_near(alsa->out_handle, hw_params,
109                 &alsa->actual_channels);
110         frames = sample_rate;
111         snd_pcm_hw_params_set_buffer_size_near(alsa->out_handle, hw_params,
112                 &frames);
113         snd_pcm_hw_params(alsa->out_handle, hw_params);
114         snd_pcm_hw_params_free(hw_params);
115
116         error = snd_pcm_sw_params_malloc(&sw_params);
117         if (error < 0)
118         {
119                 DEBUG_WARN("snd_pcm_sw_params_malloc");
120                 return false;
121         }
122         snd_pcm_sw_params_current(alsa->out_handle, sw_params);
123         snd_pcm_sw_params_set_start_threshold(alsa->out_handle, sw_params,
124                 frames / 2);
125         snd_pcm_sw_params(alsa->out_handle, sw_params);
126         snd_pcm_sw_params_free(sw_params);
127
128         snd_pcm_prepare(alsa->out_handle);
129
130         DEBUG_DVC("sample_rate %d channels %d bits_per_sample %d",
131                 sample_rate, channels, bits_per_sample);
132         DEBUG_DVC("hardware buffer %d frames", (int)frames);
133         if ((alsa->actual_rate != alsa->source_rate) ||
134                 (alsa->actual_channels != alsa->source_channels))
135         {
136                 DEBUG_DVC("actual rate %d / channel %d is different "
137                         "from source rate %d / channel %d, resampling required.",
138                         alsa->actual_rate, alsa->actual_channels,
139                         alsa->source_rate, alsa->source_channels);
140         }
141         return true;
142 }
143
144 static boolean tsmf_alsa_play(ITSMFAudioDevice* audio, uint8* data, uint32 data_size)
145 {
146         int len;
147         int error;
148         int frames;
149         uint8* end;
150         uint8* src;
151         uint8* pindex;
152         int rbytes_per_frame;
153         int sbytes_per_frame;
154         uint8* resampled_data;
155         TSMFALSAAudioDevice* alsa = (TSMFALSAAudioDevice*) audio;
156
157         DEBUG_DVC("data_size %d", data_size);
158
159         if (alsa->out_handle)
160         {
161                 sbytes_per_frame = alsa->source_channels * alsa->bytes_per_sample;
162                 rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_sample;
163
164                 if ((alsa->source_rate == alsa->actual_rate) &&
165                         (alsa->source_channels == alsa->actual_channels))
166                 {
167                         resampled_data = NULL;
168                         src = data;
169                 }
170                 else
171                 {
172                         resampled_data = dsp_resample(data, alsa->bytes_per_sample,
173                                 alsa->source_channels, alsa->source_rate, data_size / sbytes_per_frame,
174                                 alsa->actual_channels, alsa->actual_rate, &frames);
175                         DEBUG_DVC("resampled %d frames at %d to %d frames at %d",
176                                 data_size / sbytes_per_frame, alsa->source_rate, frames, alsa->actual_rate);
177                         data_size = frames * rbytes_per_frame;
178                         src = resampled_data;
179                 }
180
181                 pindex = src;
182                 end = pindex + data_size;
183                 while (pindex < end)
184                 {
185                         len = end - pindex;
186                         frames = len / rbytes_per_frame;
187                         error = snd_pcm_writei(alsa->out_handle, pindex, frames);
188                         if (error == -EPIPE)
189                         {
190                                 snd_pcm_recover(alsa->out_handle, error, 0);
191                                 error = 0;
192                         }
193                         else if (error < 0)
194                         {
195                                 DEBUG_DVC("error len %d", error);
196                                 snd_pcm_close(alsa->out_handle);
197                                 alsa->out_handle = 0;
198                                 tsmf_alsa_open_device(alsa);
199                                 break;
200                         }
201                         DEBUG_DVC("%d frames played.", error);
202                         if (error == 0)
203                                 break;
204                         pindex += error * rbytes_per_frame;
205                 }
206
207                 if (resampled_data)
208                         xfree(resampled_data);
209         }
210         xfree(data);
211
212         return true;
213 }
214
215 static uint64 tsmf_alsa_get_latency(ITSMFAudioDevice* audio)
216 {
217         uint64 latency = 0;
218         snd_pcm_sframes_t frames = 0;
219         TSMFALSAAudioDevice* alsa = (TSMFALSAAudioDevice*) audio;
220
221         if (alsa->out_handle && alsa->actual_rate > 0 &&
222                 snd_pcm_delay(alsa->out_handle, &frames) == 0 &&
223                 frames > 0)
224         {
225                 latency = ((uint64)frames) * 10000000LL / (uint64)alsa->actual_rate;
226         }
227         return latency;
228 }
229
230 static void tsmf_alsa_flush(ITSMFAudioDevice* audio)
231 {
232 }
233
234 static void tsmf_alsa_free(ITSMFAudioDevice* audio)
235 {
236         TSMFALSAAudioDevice* alsa = (TSMFALSAAudioDevice*) audio;
237
238         DEBUG_DVC("");
239
240         if (alsa->out_handle)
241         {
242                 snd_pcm_drain(alsa->out_handle);
243                 snd_pcm_close(alsa->out_handle);
244         }
245         xfree(alsa);
246 }
247
248 ITSMFAudioDevice* TSMFAudioDeviceEntry(void)
249 {
250         TSMFALSAAudioDevice* alsa;
251
252         alsa = xnew(TSMFALSAAudioDevice);
253
254         alsa->iface.Open = tsmf_alsa_open;
255         alsa->iface.SetFormat = tsmf_alsa_set_format;
256         alsa->iface.Play = tsmf_alsa_play;
257         alsa->iface.GetLatency = tsmf_alsa_get_latency;
258         alsa->iface.Flush = tsmf_alsa_flush;
259         alsa->iface.Free = tsmf_alsa_free;
260
261         return (ITSMFAudioDevice*) alsa;
262 }
263