cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

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