1 /* -*- c-basic-offset: 8 -*-
2    rdesktop: A Remote Desktop Protocol client.
3    Sound Channel Process Functions - Open Sound System
4    Copyright (C) Matthew Chapman 2003
5    Copyright (C) GuoJunBo guojunbo@ict.ac.cn 2003
6 
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License along
18    with this program; if not, write to the Free Software Foundation, Inc.,
19    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21 
22 /*
23    This is a workaround for Esound bug 312665.
24    FIXME: Remove this when Esound is fixed.
25 */
26 #ifdef _FILE_OFFSET_BITS
27 #undef _FILE_OFFSET_BITS
28 #endif
29 
30 #include "rdesktop.h"
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include <errno.h>
34 #include <sys/time.h>
35 #include <sys/ioctl.h>
36 #include <sys/soundcard.h>
37 
38 #define MAX_QUEUE	10
39 
40 int This->dsp_;
41 BOOL This->dsp_bu = False;
42 static int g_snd_rate;
43 static short g_samplewidth;
44 static BOOL g_driver_broken = False;
45 
46 static struct audio_packet
47 {
48 	struct stream s;
49 	uint16 tick;
50 	uint8 index;
51 } packet_queue[MAX_QUEUE];
52 static unsigned int queue_hi, queue_lo;
53 
54 BOOL
55 wave_out_open(void)
56 {
57 	char *dsp_dev = getenv("AUDIODEV");
58 
59 	if (dsp_dev == NULL)
60 	{
61 		dsp_dev = xstrdup("/dev/dsp");
62 	}
63 
64 	if ((This->dsp_ = open(dsp_dev, O_WRONLY | O_NONBLOCK)) == -1)
65 	{
66 		perror(dsp_dev);
67 		return False;
68 	}
69 
70 	/* Non-blocking so that user interface is responsive */
71 	fcntl(This->dsp_, F_SETFL, fcntl(This->dsp_, F_GETFL) | O_NONBLOCK);
72 	return True;
73 }
74 
75 void
76 wave_out_close(void)
77 {
78 	close(This->dsp_);
79 }
80 
81 BOOL
82 wave_out_format_supported(WAVEFORMATEX * pwfx)
83 {
84 	if (pwfx->wFormatTag != WAVE_FORMAT_PCM)
85 		return False;
86 	if ((pwfx->nChannels != 1) && (pwfx->nChannels != 2))
87 		return False;
88 	if ((pwfx->wBitsPerSample != 8) && (pwfx->wBitsPerSample != 16))
89 		return False;
90 
91 	return True;
92 }
93 
94 BOOL
95 wave_out_set_format(WAVEFORMATEX * pwfx)
96 {
97 	int stereo, format, fragments;
98 
99 	ioctl(This->dsp_, SNDCTL_DSP_RESET, NULL);
100 	ioctl(This->dsp_, SNDCTL_DSP_SYNC, NULL);
101 
102 	if (pwfx->wBitsPerSample == 8)
103 		format = AFMT_U8;
104 	else if (pwfx->wBitsPerSample == 16)
105 		format = AFMT_S16_LE;
106 
107 	g_samplewidth = pwfx->wBitsPerSample / 8;
108 
109 	if (ioctl(This->dsp_, SNDCTL_DSP_SETFMT, &format) == -1)
110 	{
111 		perror("SNDCTL_DSP_SETFMT");
112 		close(This->dsp_);
113 		return False;
114 	}
115 
116 	if (pwfx->nChannels == 2)
117 	{
118 		stereo = 1;
119 		g_samplewidth *= 2;
120 	}
121 	else
122 	{
123 		stereo = 0;
124 	}
125 
126 	if (ioctl(This->dsp_, SNDCTL_DSP_STEREO, &stereo) == -1)
127 	{
128 		perror("SNDCTL_DSP_CHANNELS");
129 		close(This->dsp_);
130 		return False;
131 	}
132 
133 	g_snd_rate = pwfx->nSamplesPerSec;
134 	if (ioctl(This->dsp_, SNDCTL_DSP_SPEED, &g_snd_rate) == -1)
135 	{
136 		perror("SNDCTL_DSP_SPEED");
137 		close(This->dsp_);
138 		return False;
139 	}
140 
141 	/* try to get 7 fragments of 2^12 bytes size */
142 	fragments = (7 << 16) + 12;
143 	ioctl(This->dsp_, SNDCTL_DSP_SETFRAGMENT, &fragments);
144 
145 	if (!g_driver_broken)
146 	{
147 		audio_buf_info info;
148 
149 		memset(&info, 0, sizeof(info));
150 		if (ioctl(This->dsp_, SNDCTL_DSP_GETOSPACE, &info) == -1)
151 		{
152 			perror("SNDCTL_DSP_GETOSPACE");
153 			close(This->dsp_);
154 			return False;
155 		}
156 
157 		if (info.fragments == 0 || info.fragstotal == 0 || info.fragsize == 0)
158 		{
159 			fprintf(stderr,
160 				"Broken OSS-driver detected: fragments: %d, fragstotal: %d, fragsize: %d\n",
161 				info.fragments, info.fragstotal, info.fragsize);
162 			g_driver_broken = True;
163 		}
164 	}
165 
166 	return True;
167 }
168 
169 void
170 wave_out_volume(uint16 left, uint16 right)
171 {
172 	static BOOL use_dev_mixer = False;
173 	uint32 volume;
174 	int fd_mix = -1;
175 
176 	volume = left / (65536 / 100);
177 	volume |= right / (65536 / 100) << 8;
178 
179 	if (use_dev_mixer)
180 	{
181 		if ((fd_mix = open("/dev/mixer", O_RDWR | O_NONBLOCK)) == -1)
182 		{
183 			perror("open /dev/mixer");
184 			return;
185 		}
186 
187 		if (ioctl(fd_mix, MIXER_WRITE(SOUND_MIXER_PCM), &volume) == -1)
188 		{
189 			perror("MIXER_WRITE(SOUND_MIXER_PCM)");
190 			return;
191 		}
192 
193 		close(fd_mix);
194 	}
195 
196 	if (ioctl(This->dsp_, MIXER_WRITE(SOUND_MIXER_PCM), &volume) == -1)
197 	{
198 		perror("MIXER_WRITE(SOUND_MIXER_PCM)");
199 		use_dev_mixer = True;
200 		return;
201 	}
202 }
203 
204 void
205 wave_out_write(STREAM s, uint16 tick, uint8 index)
206 {
207 	struct audio_packet *packet = &packet_queue[queue_hi];
208 	unsigned int next_hi = (queue_hi + 1) % MAX_QUEUE;
209 
210 	if (next_hi == queue_lo)
211 	{
212 		error("No space to queue audio packet\n");
213 		return;
214 	}
215 
216 	queue_hi = next_hi;
217 
218 	packet->s = *s;
219 	packet->tick = tick;
220 	packet->index = index;
221 	packet->s.p += 4;
222 
223 	/* we steal the data buffer from s, give it a new one */
224 	s->data = (uint8 *) malloc(s->size);
225 
226 	if (!This->dsp_bu)
227 		wave_out_play();
228 }
229 
230 void
231 wave_out_play(void)
232 {
233 	struct audio_packet *packet;
234 	ssize_t len;
235 	STREAM out;
236 	static long startedat_us;
237 	static long startedat_s;
238 	static BOOL started = False;
239 	struct timeval tv;
240 	audio_buf_info info;
241 
242 	while (1)
243 	{
244 		if (queue_lo == queue_hi)
245 		{
246 			This->dsp_bu = 0;
247 			return;
248 		}
249 
250 		packet = &packet_queue[queue_lo];
251 		out = &packet->s;
252 
253 		if (!started)
254 		{
255 			gettimeofday(&tv, NULL);
256 			startedat_us = tv.tv_usec;
257 			startedat_s = tv.tv_sec;
258 			started = True;
259 		}
260 
261 		len = out->end - out->p;
262 
263 		if (!g_driver_broken)
264 		{
265 			memset(&info, 0, sizeof(info));
266 			if (ioctl(This->dsp_, SNDCTL_DSP_GETOSPACE, &info) == -1)
267 			{
268 				perror("SNDCTL_DSP_GETOSPACE");
269 				return;
270 			}
271 
272 			if (info.fragments == 0)
273 			{
274 				This->dsp_bu = 1;
275 				return;
276 			}
277 
278 			if (info.fragments * info.fragsize < len
279 			    && info.fragments * info.fragsize > 0)
280 			{
281 				len = info.fragments * info.fragsize;
282 			}
283 		}
284 
285 
286 		len = write(This->dsp_, out->p, len);
287 		if (len == -1)
288 		{
289 			if (errno != EWOULDBLOCK)
290 				perror("write audio");
291 			This->dsp_bu = 1;
292 			return;
293 		}
294 
295 		out->p += len;
296 		if (out->p == out->end)
297 		{
298 			long long duration;
299 			long elapsed;
300 
301 			gettimeofday(&tv, NULL);
302 			duration = (out->size * (1000000 / (g_samplewidth * g_snd_rate)));
303 			elapsed = (tv.tv_sec - startedat_s) * 1000000 + (tv.tv_usec - startedat_us);
304 
305 			if (elapsed >= (duration * 85) / 100)
306 			{
307 				rdpsnd_send_completion(packet->tick, packet->index);
308 				free(out->data);
309 				queue_lo = (queue_lo + 1) % MAX_QUEUE;
310 				started = False;
311 			}
312 			else
313 			{
314 				This->dsp_bu = 1;
315 				return;
316 			}
317 		}
318 	}
319 }
320