1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 #include "channels/audio-input/audio-buffer.h"
21 #include "rdp.h"
22
23 #include <guacamole/client.h>
24 #include <guacamole/protocol.h>
25 #include <guacamole/socket.h>
26 #include <guacamole/stream.h>
27 #include <guacamole/user.h>
28
29 #include <assert.h>
30 #include <pthread.h>
31 #include <stdint.h>
32 #include <stdlib.h>
33
guac_rdp_audio_buffer_alloc()34 guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc() {
35 guac_rdp_audio_buffer* buffer = calloc(1, sizeof(guac_rdp_audio_buffer));
36 pthread_mutex_init(&(buffer->lock), NULL);
37 return buffer;
38 }
39
40 /**
41 * Sends an "ack" instruction over the socket associated with the Guacamole
42 * stream over which audio data is being received. The "ack" instruction will
43 * only be sent if the Guacamole audio stream has been established (through
44 * receipt of an "audio" instruction), is still open (has not received an "end"
45 * instruction nor been associated with an "ack" having an error code), and is
46 * associated with an active RDP AUDIO_INPUT channel.
47 *
48 * @param audio_buffer
49 * The audio buffer associated with the guac_stream for which the "ack"
50 * instruction should be sent, if any. If there is no associated
51 * guac_stream, this function has no effect.
52 *
53 * @param message
54 * An arbitrary human-readable message to send along with the "ack".
55 *
56 * @param status
57 * The Guacamole protocol status code to send with the "ack". This should
58 * be GUAC_PROTOCOL_STATUS_SUCCESS if the audio stream has been set up
59 * successfully or GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED if the audio stream
60 * has been closed (but may usable again if reopened).
61 */
guac_rdp_audio_buffer_ack(guac_rdp_audio_buffer * audio_buffer,const char * message,guac_protocol_status status)62 static void guac_rdp_audio_buffer_ack(guac_rdp_audio_buffer* audio_buffer,
63 const char* message, guac_protocol_status status) {
64
65 guac_user* user = audio_buffer->user;
66 guac_stream* stream = audio_buffer->stream;
67
68 /* Do not send ack unless both sides of the audio stream are ready */
69 if (user == NULL || stream == NULL || audio_buffer->packet == NULL)
70 return;
71
72 /* Send ack instruction */
73 guac_protocol_send_ack(user->socket, stream, message, status);
74 guac_socket_flush(user->socket);
75
76 }
77
guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer * audio_buffer,guac_user * user,guac_stream * stream,int rate,int channels,int bps)78 void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer,
79 guac_user* user, guac_stream* stream, int rate, int channels, int bps) {
80
81 pthread_mutex_lock(&(audio_buffer->lock));
82
83 /* Associate received stream */
84 audio_buffer->user = user;
85 audio_buffer->stream = stream;
86 audio_buffer->in_format.rate = rate;
87 audio_buffer->in_format.channels = channels;
88 audio_buffer->in_format.bps = bps;
89
90 /* Acknowledge stream creation (if buffer is ready to receive) */
91 guac_rdp_audio_buffer_ack(audio_buffer,
92 "OK", GUAC_PROTOCOL_STATUS_SUCCESS);
93
94 guac_user_log(user, GUAC_LOG_DEBUG, "User is requesting to provide audio "
95 "input as %i-channel, %i Hz PCM audio at %i bytes/sample.",
96 audio_buffer->in_format.channels,
97 audio_buffer->in_format.rate,
98 audio_buffer->in_format.bps);
99
100 pthread_mutex_unlock(&(audio_buffer->lock));
101
102 }
103
guac_rdp_audio_buffer_set_output(guac_rdp_audio_buffer * audio_buffer,int rate,int channels,int bps)104 void guac_rdp_audio_buffer_set_output(guac_rdp_audio_buffer* audio_buffer,
105 int rate, int channels, int bps) {
106
107 pthread_mutex_lock(&(audio_buffer->lock));
108
109 /* Set output format */
110 audio_buffer->out_format.rate = rate;
111 audio_buffer->out_format.channels = channels;
112 audio_buffer->out_format.bps = bps;
113
114 pthread_mutex_unlock(&(audio_buffer->lock));
115
116 }
117
guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer * audio_buffer,int packet_frames,guac_rdp_audio_buffer_flush_handler * flush_handler,void * data)118 void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer,
119 int packet_frames, guac_rdp_audio_buffer_flush_handler* flush_handler,
120 void* data) {
121
122 pthread_mutex_lock(&(audio_buffer->lock));
123
124 /* Reset buffer state to provided values */
125 audio_buffer->bytes_written = 0;
126 audio_buffer->flush_handler = flush_handler;
127 audio_buffer->data = data;
128
129 /* Calculate size of each packet in bytes */
130 audio_buffer->packet_size = packet_frames
131 * audio_buffer->out_format.channels
132 * audio_buffer->out_format.bps;
133
134 /* Allocate new buffer */
135 free(audio_buffer->packet);
136 audio_buffer->packet = malloc(audio_buffer->packet_size);
137
138 /* Acknowledge stream creation (if stream is ready to receive) */
139 guac_rdp_audio_buffer_ack(audio_buffer,
140 "OK", GUAC_PROTOCOL_STATUS_SUCCESS);
141
142 pthread_mutex_unlock(&(audio_buffer->lock));
143
144 }
145
146 /**
147 * Reads a single sample from the given buffer of data, using the input
148 * format defined within the given audio buffer. Each read sample is
149 * translated to a signed 16-bit value, even if the input format is 8-bit.
150 * The offset into the given buffer will be determined according to the
151 * input and output formats, the number of bytes sent thus far, and the
152 * number of bytes received (excluding the contents of the buffer).
153 *
154 * @param audio_buffer
155 * The audio buffer dictating the format of the given data buffer, as
156 * well as the offset from which the sample should be read.
157 *
158 * @param buffer
159 * The buffer of raw PCM audio data from which the sample should be read.
160 * This buffer MUST NOT contain data already taken into account by the
161 * audio buffer's total_bytes_received counter.
162 *
163 * @param length
164 * The number of bytes within the given buffer of PCM data.
165 *
166 * @param sample
167 * A pointer to the int16_t in which the read sample should be stored. If
168 * the input format is 8-bit, the sample will be shifted left by 8 bits
169 * to produce a 16-bit sample.
170 *
171 * @return
172 * Non-zero if a sample was successfully read, zero if no data remains
173 * within the given buffer that has not already been mapped to an
174 * output sample.
175 */
guac_rdp_audio_buffer_read_sample(guac_rdp_audio_buffer * audio_buffer,const char * buffer,int length,int16_t * sample)176 static int guac_rdp_audio_buffer_read_sample(
177 guac_rdp_audio_buffer* audio_buffer, const char* buffer, int length,
178 int16_t* sample) {
179
180 int in_bps = audio_buffer->in_format.bps;
181 int in_rate = audio_buffer->in_format.rate;
182 int in_channels = audio_buffer->in_format.channels;
183
184 int out_bps = audio_buffer->out_format.bps;
185 int out_rate = audio_buffer->out_format.rate;
186 int out_channels = audio_buffer->out_format.channels;
187
188 /* Calculate position within audio output */
189 int current_sample = audio_buffer->total_bytes_sent / out_bps;
190 int current_frame = current_sample / out_channels;
191 int current_channel = current_sample % out_channels;
192
193 /* Map output channel to input channel */
194 if (current_channel >= in_channels)
195 current_channel = in_channels - 1;
196
197 /* Transform output position to input position */
198 current_frame = (int) current_frame * ((double) in_rate / out_rate);
199 current_sample = current_frame * in_channels + current_channel;
200
201 /* Calculate offset within given buffer from absolute input position */
202 int offset = current_sample * in_bps
203 - audio_buffer->total_bytes_received;
204
205 /* It should be impossible for the offset to ever go negative */
206 assert(offset >= 0);
207
208 /* Apply offset to buffer */
209 buffer += offset;
210 length -= offset;
211
212 /* Read only if sufficient data is present in the given buffer */
213 if (length < in_bps)
214 return 0;
215
216 /* Simply read sample directly if input is 16-bit */
217 if (in_bps == 2) {
218 *sample = *((int16_t*) buffer);
219 return 1;
220 }
221
222 /* Translate to 16-bit if input is 8-bit */
223 if (in_bps == 1) {
224 *sample = *buffer << 8;
225 return 1;
226 }
227
228 /* Accepted audio formats are required to be 8- or 16-bit */
229 return 0;
230
231 }
232
guac_rdp_audio_buffer_write(guac_rdp_audio_buffer * audio_buffer,char * buffer,int length)233 void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer,
234 char* buffer, int length) {
235
236 int16_t sample;
237
238 pthread_mutex_lock(&(audio_buffer->lock));
239
240 /* Ignore packet if there is no buffer */
241 if (audio_buffer->packet_size == 0 || audio_buffer->packet == NULL) {
242 pthread_mutex_unlock(&(audio_buffer->lock));
243 return;
244 }
245
246 int out_bps = audio_buffer->out_format.bps;
247
248 /* Continuously write packets until no data remains */
249 while (guac_rdp_audio_buffer_read_sample(audio_buffer,
250 buffer, length, &sample) > 0) {
251
252 char* current = audio_buffer->packet + audio_buffer->bytes_written;
253
254 /* Store as 16-bit or 8-bit, depending on output format */
255 if (out_bps == 2)
256 *((int16_t*) current) = sample;
257 else if (out_bps == 1)
258 *current = sample >> 8;
259
260 /* Accepted audio formats are required to be 8- or 16-bit */
261 else
262 assert(0);
263
264 /* Update byte counters */
265 audio_buffer->bytes_written += out_bps;
266 audio_buffer->total_bytes_sent += out_bps;
267
268 /* Invoke flush handler if full */
269 if (audio_buffer->bytes_written == audio_buffer->packet_size) {
270
271 /* Only actually invoke if defined */
272 if (audio_buffer->flush_handler)
273 audio_buffer->flush_handler(audio_buffer->packet,
274 audio_buffer->bytes_written, audio_buffer->data);
275
276 /* Reset buffer in all cases */
277 audio_buffer->bytes_written = 0;
278
279 }
280
281 } /* end packet write loop */
282
283 /* Track current position in audio stream */
284 audio_buffer->total_bytes_received += length;
285
286 pthread_mutex_unlock(&(audio_buffer->lock));
287
288 }
289
guac_rdp_audio_buffer_end(guac_rdp_audio_buffer * audio_buffer)290 void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) {
291
292 pthread_mutex_lock(&(audio_buffer->lock));
293
294 /* The stream is now closed */
295 guac_rdp_audio_buffer_ack(audio_buffer,
296 "CLOSED", GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED);
297
298 /* Unset user and stream */
299 audio_buffer->user = NULL;
300 audio_buffer->stream = NULL;
301
302 /* Reset buffer state */
303 audio_buffer->bytes_written = 0;
304 audio_buffer->packet_size = 0;
305 audio_buffer->flush_handler = NULL;
306
307 /* Reset I/O counters */
308 audio_buffer->total_bytes_sent = 0;
309 audio_buffer->total_bytes_received = 0;
310
311 /* Free packet (if any) */
312 free(audio_buffer->packet);
313 audio_buffer->packet = NULL;
314
315 pthread_mutex_unlock(&(audio_buffer->lock));
316
317 }
318
guac_rdp_audio_buffer_free(guac_rdp_audio_buffer * audio_buffer)319 void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer) {
320 pthread_mutex_destroy(&(audio_buffer->lock));
321 free(audio_buffer->packet);
322 free(audio_buffer);
323 }
324
325