audio-buffer.c (22842B)
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/mem.h> 25#include <guacamole/protocol.h> 26#include <guacamole/socket.h> 27#include <guacamole/stream.h> 28#include <guacamole/timestamp.h> 29#include <guacamole/user.h> 30 31#include <assert.h> 32#include <errno.h> 33#include <pthread.h> 34#include <stdint.h> 35#include <stdlib.h> 36#include <time.h> 37 38/** 39 * The number of nanoseconds in one second. 40 */ 41#define NANOS_PER_SECOND 1000000000L 42 43/** 44 * Returns whether the given timespec represents a point in time in the future 45 * relative to the current system time. 46 * 47 * @param ts 48 * The timespec to test. 49 * 50 * @return 51 * Non-zero if the given timespec is in the future relative to the current 52 * system time, zero otherwise. 53 */ 54static int guac_rdp_audio_buffer_is_future(const struct timespec* ts) { 55 56 struct timespec now; 57 clock_gettime(CLOCK_REALTIME, &now); 58 59 if (now.tv_sec != ts->tv_sec) 60 return now.tv_sec < ts->tv_sec; 61 62 return now.tv_nsec < ts->tv_nsec; 63 64} 65 66/** 67 * Returns whether the given audio buffer may be flushed. An audio buffer may 68 * be flushed if the audio buffer is not currently being freed, at least one 69 * packet of audio data is available within the buffer, and flushing the next 70 * packet of audio data now would not violate scheduling/throttling rules for 71 * outbound audio data. 72 * 73 * IMPORTANT: The guac_rdp_audio_buffer's lock MUST already be held when 74 * invoking this function. 75 * 76 * @param audio_buffer 77 * The guac_rdp_audio_buffer to test. 78 * 79 * @return 80 * Non-zero if the given audio buffer may be flushed, zero if the audio 81 * buffer cannot be flushed for any reason. 82 */ 83static int guac_rdp_audio_buffer_may_flush(guac_rdp_audio_buffer* audio_buffer) { 84 return !audio_buffer->stopping 85 && audio_buffer->packet_size > 0 86 && audio_buffer->bytes_written >= audio_buffer->packet_size 87 && !guac_rdp_audio_buffer_is_future(&audio_buffer->next_flush); 88} 89 90/** 91 * Returns the duration of the given quantity of audio data in milliseconds. 92 * 93 * @param format 94 * The format of the audio data in question. 95 * 96 * @param length 97 * The number of bytes of audio data. 98 * 99 * @return 100 * The duration of the audio data in milliseconds. 101 */ 102static int guac_rdp_audio_buffer_duration(const guac_rdp_audio_format* format, int length) { 103 return length * 1000 / format->rate / format->bps / format->channels; 104} 105 106/** 107 * Returns the number of bytes required to store audio data in the given format 108 * covering the given length of time. 109 * 110 * @param format 111 * The format of the audio data in question. 112 * 113 * @param duration 114 * The duration of the audio data in milliseconds. 115 * 116 * @return 117 * The number of bytes required to store audio data in the given format 118 * covering the given length of time. 119 */ 120static size_t guac_rdp_audio_buffer_length(const guac_rdp_audio_format* format, int duration) { 121 return guac_mem_ckd_mul_or_die(duration, format->rate, format->bps, format->channels) / 1000; 122} 123 124/** 125 * Notifies the given guac_rdp_audio_buffer that a single packet of audio data 126 * has just been flushed, updating the scheduled time of the next flush. The 127 * timing of the next flush will be set such that the overall real time audio 128 * generation rate is not exceeded, but will be adjusted as necessary to 129 * compensate for latency induced by differences in audio packet size/duration. 130 * 131 * IMPORTANT: The guac_rdp_audio_buffer's lock MUST already be held when 132 * invoking this function. 133 * 134 * @param audio_buffer 135 * The guac_rdp_audio_buffer to update. 136 */ 137static void guac_rdp_audio_buffer_schedule_flush(guac_rdp_audio_buffer* audio_buffer) { 138 139 struct timespec next_flush; 140 clock_gettime(CLOCK_REALTIME, &next_flush); 141 142 /* Calculate the point in time that the next flush would be allowed, 143 * assuming that the remote server processes data no faster than 144 * real time */ 145 uint64_t delta_nsecs = audio_buffer->packet_size * NANOS_PER_SECOND 146 / audio_buffer->out_format.rate 147 / audio_buffer->out_format.bps 148 / audio_buffer->out_format.channels; 149 150 /* Amortize the additional latency from packet data buffered beyond the 151 * desired packet size over each remaining packet such that we gradually 152 * approach an effective additional latency of 0 */ 153 int packets_remaining = audio_buffer->bytes_written / audio_buffer->packet_size; 154 if (packets_remaining > 1) 155 delta_nsecs = delta_nsecs * (packets_remaining - 1) / packets_remaining; 156 157 uint64_t nsecs = next_flush.tv_nsec + delta_nsecs; 158 159 next_flush.tv_sec += nsecs / NANOS_PER_SECOND; 160 next_flush.tv_nsec = nsecs % NANOS_PER_SECOND; 161 162 audio_buffer->next_flush = next_flush; 163 164} 165 166/** 167 * Waits for additional data to be available for flush within the given audio 168 * buffer. If data is available but insufficient time has elapsed since the 169 * last flush, this function may block until sufficient time has elapsed. If 170 * the state of the audio buffer changes in any way while waiting for 171 * additional data, or if the audio buffer is being freed, this function will 172 * return immediately. 173 * 174 * It is the responsibility of the caller to check the state of the audio 175 * buffer after this function returns to verify whether the desired state 176 * change has occurred and re-invoke the function if needed. 177 * 178 * @param audio_buffer 179 * The guac_rdp_audio_buffer to wait for. 180 */ 181static void guac_rdp_audio_buffer_wait(guac_rdp_audio_buffer* audio_buffer) { 182 183 pthread_mutex_lock(&(audio_buffer->lock)); 184 185 /* Do not wait if audio_buffer is already closed */ 186 if (!audio_buffer->stopping) { 187 188 /* If sufficient data exists for a flush, wait until next possible 189 * flush OR until some other state change occurs (such as the buffer 190 * being closed) */ 191 if (audio_buffer->bytes_written && audio_buffer->bytes_written >= audio_buffer->packet_size) 192 pthread_cond_timedwait(&audio_buffer->modified, &audio_buffer->lock, 193 &audio_buffer->next_flush); 194 195 /* If sufficient data DOES NOT exist, we should wait indefinitely */ 196 else 197 pthread_cond_wait(&audio_buffer->modified, &audio_buffer->lock); 198 199 } 200 201 pthread_mutex_unlock(&(audio_buffer->lock)); 202 203} 204 205/** 206 * Regularly and automatically flushes audio packets by invoking the flush 207 * handler of the associated audio buffer. Packets are scheduled automatically 208 * to avoid potentially exceeding the processing and buffering capabilities of 209 * the software running within the RDP server. Once started, this thread runs 210 * until the associated audio buffer is freed via guac_rdp_audio_buffer_free(). 211 * 212 * @param data 213 * A pointer to the guac_rdp_audio_buffer that should be flushed. 214 * 215 * @return 216 * Always NULL. 217 */ 218static void* guac_rdp_audio_buffer_flush_thread(void* data) { 219 220 guac_rdp_audio_buffer* audio_buffer = (guac_rdp_audio_buffer*) data; 221 while (!audio_buffer->stopping) { 222 223 pthread_mutex_lock(&(audio_buffer->lock)); 224 225 if (!guac_rdp_audio_buffer_may_flush(audio_buffer)) { 226 227 pthread_mutex_unlock(&(audio_buffer->lock)); 228 229 /* Wait for additional data if we aren't able to flush */ 230 guac_rdp_audio_buffer_wait(audio_buffer); 231 232 /* We might still not be able to flush (buffer might be closed, 233 * some other state change might occur that isn't receipt of data, 234 * data might be received but not enough for a flush, etc.) */ 235 continue; 236 237 } 238 239 guac_client_log(audio_buffer->client, GUAC_LOG_TRACE, "Current audio input latency: %i ms (%i bytes waiting in buffer)", 240 guac_rdp_audio_buffer_duration(&audio_buffer->out_format, audio_buffer->bytes_written), 241 audio_buffer->bytes_written); 242 243 /* Only actually invoke if defined */ 244 if (audio_buffer->flush_handler) { 245 guac_rdp_audio_buffer_schedule_flush(audio_buffer); 246 audio_buffer->flush_handler(audio_buffer, 247 audio_buffer->packet_size); 248 } 249 250 /* Shift buffer back by one packet */ 251 audio_buffer->bytes_written -= audio_buffer->packet_size; 252 memmove(audio_buffer->packet, audio_buffer->packet + audio_buffer->packet_size, audio_buffer->bytes_written); 253 254 pthread_cond_broadcast(&(audio_buffer->modified)); 255 pthread_mutex_unlock(&(audio_buffer->lock)); 256 257 } 258 259 return NULL; 260 261} 262 263guac_rdp_audio_buffer* guac_rdp_audio_buffer_alloc(guac_client* client) { 264 265 guac_rdp_audio_buffer* buffer = guac_mem_zalloc(sizeof(guac_rdp_audio_buffer)); 266 267 pthread_mutex_init(&(buffer->lock), NULL); 268 pthread_cond_init(&(buffer->modified), NULL); 269 buffer->client = client; 270 271 /* Begin automated, throttled flush of future data */ 272 pthread_create(&(buffer->flush_thread), NULL, 273 guac_rdp_audio_buffer_flush_thread, (void*) buffer); 274 275 return buffer; 276} 277 278/** 279 * Parameters describing an "ack" instruction to be sent to the current user of 280 * an inbound audio stream (guac_rdp_audio_buffer). 281 */ 282typedef struct guac_rdp_audio_buffer_ack_params { 283 284 /** 285 * The audio buffer associated with the guac_stream for which the "ack" 286 * instruction should be sent, if any. If there is no associated 287 * guac_stream, no "ack" will be sent. 288 */ 289 guac_rdp_audio_buffer* audio_buffer; 290 291 /** 292 * An arbitrary human-readable message to send along with the "ack". 293 */ 294 const char* message; 295 296 /** 297 * The Guacamole protocol status code to send with the "ack". This should 298 * be GUAC_PROTOCOL_STATUS_SUCCESS if the audio stream has been set up 299 * successfully or GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED if the audio stream 300 * has been closed (but may usable again if reopened). 301 */ 302 guac_protocol_status status; 303 304} guac_rdp_audio_buffer_ack_params; 305 306/** 307 * Sends an "ack" instruction over the socket associated with the Guacamole 308 * stream over which audio data is being received. The "ack" instruction will 309 * only be sent if the Guacamole audio stream has been established (through 310 * receipt of an "audio" instruction), is still open (has not received an "end" 311 * instruction nor been associated with an "ack" having an error code), and is 312 * associated with an active RDP AUDIO_INPUT channel. 313 * 314 * IMPORTANT: The guac_rdp_audio_buffer's lock MUST already be held when 315 * invoking this function. 316 * 317 * @param user 318 * The user that should receive the "ack". 319 * 320 * @param data 321 * An instance of guac_rdp_audio_buffer_ack_params describing the "ack" 322 * instruction to be sent. 323 * 324 * @returns 325 * Always NULL. 326 */ 327static void* guac_rdp_audio_buffer_ack(guac_user* user, void* data) { 328 329 guac_rdp_audio_buffer_ack_params* params = (guac_rdp_audio_buffer_ack_params*) data; 330 guac_rdp_audio_buffer* audio_buffer = params->audio_buffer; 331 guac_stream* stream = audio_buffer->stream; 332 333 /* Do not send ack unless both sides of the audio stream are ready */ 334 if (user == NULL || stream == NULL || audio_buffer->packet == NULL) 335 return NULL; 336 337 /* Send ack instruction */ 338 guac_protocol_send_ack(user->socket, stream, params->message, params->status); 339 guac_socket_flush(user->socket); 340 341 return NULL; 342 343} 344 345void guac_rdp_audio_buffer_set_stream(guac_rdp_audio_buffer* audio_buffer, 346 guac_user* user, guac_stream* stream, int rate, int channels, int bps) { 347 348 pthread_mutex_lock(&(audio_buffer->lock)); 349 350 /* Associate received stream */ 351 audio_buffer->user = user; 352 audio_buffer->stream = stream; 353 audio_buffer->in_format.rate = rate; 354 audio_buffer->in_format.channels = channels; 355 audio_buffer->in_format.bps = bps; 356 357 /* Acknowledge stream creation (if buffer is ready to receive) */ 358 guac_rdp_audio_buffer_ack_params ack_params = { audio_buffer, "OK", GUAC_PROTOCOL_STATUS_SUCCESS }; 359 guac_client_for_user(audio_buffer->client, user, guac_rdp_audio_buffer_ack, &ack_params); 360 361 guac_user_log(user, GUAC_LOG_DEBUG, "User is requesting to provide audio " 362 "input as %i-channel, %i Hz PCM audio at %i bytes/sample.", 363 audio_buffer->in_format.channels, 364 audio_buffer->in_format.rate, 365 audio_buffer->in_format.bps); 366 367 pthread_cond_broadcast(&(audio_buffer->modified)); 368 pthread_mutex_unlock(&(audio_buffer->lock)); 369 370} 371 372void guac_rdp_audio_buffer_set_output(guac_rdp_audio_buffer* audio_buffer, 373 int rate, int channels, int bps) { 374 375 pthread_mutex_lock(&(audio_buffer->lock)); 376 377 /* Set output format */ 378 audio_buffer->out_format.rate = rate; 379 audio_buffer->out_format.channels = channels; 380 audio_buffer->out_format.bps = bps; 381 382 pthread_cond_broadcast(&(audio_buffer->modified)); 383 pthread_mutex_unlock(&(audio_buffer->lock)); 384 385} 386 387void guac_rdp_audio_buffer_begin(guac_rdp_audio_buffer* audio_buffer, 388 int packet_frames, guac_rdp_audio_buffer_flush_handler* flush_handler, 389 void* data) { 390 391 pthread_mutex_lock(&(audio_buffer->lock)); 392 393 /* Reset buffer state to provided values */ 394 audio_buffer->bytes_written = 0; 395 audio_buffer->flush_handler = flush_handler; 396 audio_buffer->data = data; 397 398 /* Calculate size of each packet in bytes */ 399 audio_buffer->packet_size = guac_mem_ckd_mul_or_die(packet_frames, 400 audio_buffer->out_format.channels, 401 audio_buffer->out_format.bps); 402 403 /* Ensure outbound buffer includes enough space for at least 250ms of 404 * audio */ 405 size_t ideal_size = guac_rdp_audio_buffer_length(&audio_buffer->out_format, 406 GUAC_RDP_AUDIO_BUFFER_MIN_DURATION); 407 408 /* Round up to nearest whole packet */ 409 size_t ideal_packets = guac_mem_ckd_sub_or_die( 410 guac_mem_ckd_add_or_die(ideal_size, audio_buffer->packet_size), 1 411 ) / audio_buffer->packet_size; 412 413 /* Allocate new buffer */ 414 audio_buffer->packet_buffer_size = guac_mem_ckd_mul_or_die(ideal_packets, audio_buffer->packet_size); 415 audio_buffer->packet = guac_mem_alloc(audio_buffer->packet_buffer_size); 416 417 guac_client_log(audio_buffer->client, GUAC_LOG_DEBUG, "Output buffer for " 418 "audio input is %i bytes (up to %i ms).", audio_buffer->packet_buffer_size, 419 guac_rdp_audio_buffer_duration(&audio_buffer->out_format, audio_buffer->packet_buffer_size)); 420 421 /* Next flush can occur as soon as data is received */ 422 clock_gettime(CLOCK_REALTIME, &audio_buffer->next_flush); 423 424 /* Acknowledge stream creation (if stream is ready to receive) */ 425 if (audio_buffer->user != NULL) { 426 guac_rdp_audio_buffer_ack_params ack_params = { audio_buffer, "OK", GUAC_PROTOCOL_STATUS_SUCCESS }; 427 guac_client_for_user(audio_buffer->client, audio_buffer->user, guac_rdp_audio_buffer_ack, &ack_params); 428 } 429 430 pthread_cond_broadcast(&(audio_buffer->modified)); 431 pthread_mutex_unlock(&(audio_buffer->lock)); 432 433} 434 435/** 436 * Reads a single sample from the given buffer of data, using the input 437 * format defined within the given audio buffer. Each read sample is 438 * translated to a signed 16-bit value, even if the input format is 8-bit. 439 * The offset into the given buffer will be determined according to the 440 * input and output formats, the number of bytes sent thus far, and the 441 * number of bytes received (excluding the contents of the buffer). 442 * 443 * IMPORTANT: The guac_rdp_audio_buffer's lock MUST already be held when 444 * invoking this function. 445 * 446 * @param audio_buffer 447 * The audio buffer dictating the format of the given data buffer, as 448 * well as the offset from which the sample should be read. 449 * 450 * @param buffer 451 * The buffer of raw PCM audio data from which the sample should be read. 452 * This buffer MUST NOT contain data already taken into account by the 453 * audio buffer's total_bytes_received counter. 454 * 455 * @param length 456 * The number of bytes within the given buffer of PCM data. 457 * 458 * @param sample 459 * A pointer to the int16_t in which the read sample should be stored. If 460 * the input format is 8-bit, the sample will be shifted left by 8 bits 461 * to produce a 16-bit sample. 462 * 463 * @return 464 * Non-zero if a sample was successfully read, zero if no data remains 465 * within the given buffer that has not already been mapped to an 466 * output sample. 467 */ 468static int guac_rdp_audio_buffer_read_sample( 469 guac_rdp_audio_buffer* audio_buffer, const char* buffer, int length, 470 int16_t* sample) { 471 472 int in_bps = audio_buffer->in_format.bps; 473 int in_rate = audio_buffer->in_format.rate; 474 int in_channels = audio_buffer->in_format.channels; 475 476 int out_bps = audio_buffer->out_format.bps; 477 int out_rate = audio_buffer->out_format.rate; 478 int out_channels = audio_buffer->out_format.channels; 479 480 /* Calculate position within audio output */ 481 int current_sample = audio_buffer->total_bytes_sent / out_bps; 482 int current_frame = current_sample / out_channels; 483 int current_channel = current_sample % out_channels; 484 485 /* Map output channel to input channel */ 486 if (current_channel >= in_channels) 487 current_channel = in_channels - 1; 488 489 /* Transform output position to input position */ 490 current_frame = (int) current_frame * ((double) in_rate / out_rate); 491 current_sample = current_frame * in_channels + current_channel; 492 493 /* Calculate offset within given buffer from absolute input position */ 494 int offset = current_sample * in_bps 495 - audio_buffer->total_bytes_received; 496 497 /* It should be impossible for the offset to ever go negative */ 498 assert(offset >= 0); 499 500 /* Apply offset to buffer */ 501 buffer += offset; 502 length -= offset; 503 504 /* Read only if sufficient data is present in the given buffer */ 505 if (length < in_bps) 506 return 0; 507 508 /* Simply read sample directly if input is 16-bit */ 509 if (in_bps == 2) { 510 *sample = *((int16_t*) buffer); 511 return 1; 512 } 513 514 /* Translate to 16-bit if input is 8-bit */ 515 if (in_bps == 1) { 516 *sample = *buffer << 8; 517 return 1; 518 } 519 520 /* Accepted audio formats are required to be 8- or 16-bit */ 521 return 0; 522 523} 524 525void guac_rdp_audio_buffer_write(guac_rdp_audio_buffer* audio_buffer, 526 char* buffer, int length) { 527 528 int16_t sample; 529 530 pthread_mutex_lock(&(audio_buffer->lock)); 531 532 guac_client_log(audio_buffer->client, GUAC_LOG_TRACE, "Received %i bytes (%i ms) of audio data", 533 length, guac_rdp_audio_buffer_duration(&audio_buffer->in_format, length)); 534 535 /* Ignore packet if there is no buffer */ 536 if (audio_buffer->packet_buffer_size == 0 || audio_buffer->packet == NULL) { 537 guac_client_log(audio_buffer->client, GUAC_LOG_DEBUG, "Dropped %i " 538 "bytes of received audio data (buffer full or closed).", length); 539 pthread_mutex_unlock(&(audio_buffer->lock)); 540 return; 541 } 542 543 /* Truncate received samples if exceeding size of buffer */ 544 int available = audio_buffer->packet_buffer_size - audio_buffer->bytes_written; 545 if (length > available) { 546 guac_client_log(audio_buffer->client, GUAC_LOG_DEBUG, "Truncating %i " 547 "bytes of received audio data to %i bytes (insufficient space " 548 "in buffer).", length, available); 549 length = available; 550 } 551 552 int out_bps = audio_buffer->out_format.bps; 553 554 /* Continuously write packets until no data remains */ 555 while (guac_rdp_audio_buffer_read_sample(audio_buffer, 556 buffer, length, &sample) > 0) { 557 558 char* current = audio_buffer->packet + audio_buffer->bytes_written; 559 560 /* Store as 16-bit or 8-bit, depending on output format */ 561 if (out_bps == 2) 562 *((int16_t*) current) = sample; 563 else if (out_bps == 1) 564 *current = sample >> 8; 565 566 /* Accepted audio formats are required to be 8- or 16-bit */ 567 else 568 assert(0); 569 570 /* Update byte counters */ 571 audio_buffer->bytes_written += out_bps; 572 audio_buffer->total_bytes_sent += out_bps; 573 574 } /* end packet write loop */ 575 576 /* Track current position in audio stream */ 577 audio_buffer->total_bytes_received += length; 578 579 pthread_cond_broadcast(&(audio_buffer->modified)); 580 pthread_mutex_unlock(&(audio_buffer->lock)); 581 582} 583 584void guac_rdp_audio_buffer_end(guac_rdp_audio_buffer* audio_buffer) { 585 586 pthread_mutex_lock(&(audio_buffer->lock)); 587 588 /* Ignore if stream is already closed */ 589 if (audio_buffer->stream == NULL) { 590 pthread_mutex_unlock(&(audio_buffer->lock)); 591 return; 592 } 593 594 /* The stream is now closed */ 595 if (audio_buffer->user != NULL) { 596 guac_rdp_audio_buffer_ack_params ack_params = { audio_buffer, "CLOSED", GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED }; 597 guac_client_for_user(audio_buffer->client, audio_buffer->user, guac_rdp_audio_buffer_ack, &ack_params); 598 } 599 600 /* Unset user and stream */ 601 audio_buffer->user = NULL; 602 audio_buffer->stream = NULL; 603 604 /* Reset buffer state */ 605 audio_buffer->bytes_written = 0; 606 audio_buffer->packet_size = 0; 607 audio_buffer->packet_buffer_size = 0; 608 audio_buffer->flush_handler = NULL; 609 610 /* Reset I/O counters */ 611 audio_buffer->total_bytes_sent = 0; 612 audio_buffer->total_bytes_received = 0; 613 614 /* Free packet (if any) */ 615 guac_mem_free(audio_buffer->packet); 616 617 pthread_cond_broadcast(&(audio_buffer->modified)); 618 pthread_mutex_unlock(&(audio_buffer->lock)); 619 620} 621 622void guac_rdp_audio_buffer_free(guac_rdp_audio_buffer* audio_buffer) { 623 624 guac_rdp_audio_buffer_end(audio_buffer); 625 626 /* Signal termination of flush thread */ 627 pthread_mutex_lock(&(audio_buffer->lock)); 628 audio_buffer->stopping = 1; 629 pthread_cond_broadcast(&(audio_buffer->modified)); 630 pthread_mutex_unlock(&(audio_buffer->lock)); 631 632 /* Clean up flush thread */ 633 pthread_join(audio_buffer->flush_thread, NULL); 634 635 pthread_mutex_destroy(&(audio_buffer->lock)); 636 pthread_cond_destroy(&(audio_buffer->modified)); 637 guac_mem_free(audio_buffer); 638 639} 640