Michael Jumper 4282da662f GUACAMOLE-249: Restructure audio input such that audio buffer can be separately linked.
On some platforms, the libguacai-client.so plugin for FreeRDP reports an
unlinked symbol:

    undefined symbol: guac_freerdp_dynamic_channel_collection_add	(/usr/local/lib/freerdp2/libguacai-client.so)

This symbol is actually unused within the plugin, but may be referenced
due to being defined within a function in a common piece of source
shared between the plugin and the RDP support.

Separating the actual common components such that they can be included
by both the RDP support and the libguacai-client.so plugin removes the
potential for unused pieces being flagged as missing.
2020-01-12 22:04:01 -08:00

341 lines
11 KiB
C

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "channels/audio-input/audio-buffer.h"
#include "plugins/guacai/guacai-messages.h"
#include "rdp.h"
#include <freerdp/dvc.h>
#include <guacamole/client.h>
#include <winpr/stream.h>
#include <stdlib.h>
/**
* Reads AUDIO_FORMAT data from the given stream into the given struct.
*
* @param stream
* The stream to read AUDIO_FORMAT data from.
*
* @param format
* The structure to populate with data from the stream.
*/
static void guac_rdp_ai_read_format(wStream* stream,
guac_rdp_ai_format* format) {
/* Read audio format into structure */
Stream_Read_UINT16(stream, format->tag); /* wFormatTag */
Stream_Read_UINT16(stream, format->channels); /* nChannels */
Stream_Read_UINT32(stream, format->rate); /* nSamplesPerSec */
Stream_Read_UINT32(stream, format->bytes_per_sec); /* nAvgBytesPerSec */
Stream_Read_UINT16(stream, format->block_align); /* nBlockAlign */
Stream_Read_UINT16(stream, format->bps); /* wBitsPerSample */
Stream_Read_UINT16(stream, format->data_size); /* cbSize */
/* Read arbitrary data block (if applicable) */
if (format->data_size != 0) {
format->data = Stream_Pointer(stream); /* data */
Stream_Seek(stream, format->data_size);
}
}
/**
* Writes AUDIO_FORMAT data to the given stream from the given struct.
*
* @param stream
* The stream to write AUDIO_FORMAT data to.
*
* @param format
* The structure containing the data that should be written to the stream.
*/
static void guac_rdp_ai_write_format(wStream* stream,
guac_rdp_ai_format* format) {
/* Write audio format into structure */
Stream_Write_UINT16(stream, format->tag); /* wFormatTag */
Stream_Write_UINT16(stream, format->channels); /* nChannels */
Stream_Write_UINT32(stream, format->rate); /* nSamplesPerSec */
Stream_Write_UINT32(stream, format->bytes_per_sec); /* nAvgBytesPerSec */
Stream_Write_UINT16(stream, format->block_align); /* nBlockAlign */
Stream_Write_UINT16(stream, format->bps); /* wBitsPerSample */
Stream_Write_UINT16(stream, format->data_size); /* cbSize */
/* Write arbitrary data block (if applicable) */
if (format->data_size != 0)
Stream_Write(stream, format->data, format->data_size);
}
/**
* Sends a Data Incoming PDU along the given channel. A Data Incoming PDU is
* used by the client to indicate to the server that format or audio data is
* about to be sent.
*
* @param channel
* The channel along which the PDU should be sent.
*/
static void guac_rdp_ai_send_incoming_data(IWTSVirtualChannel* channel) {
/* Build data incoming PDU */
wStream* stream = Stream_New(NULL, 1);
Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_DATA_INCOMING); /* MessageId */
/* Send stream */
channel->Write(channel, (UINT32) Stream_GetPosition(stream),
Stream_Buffer(stream), NULL);
Stream_Free(stream, TRUE);
}
/**
* Sends a Data PDU along the given channel. A Data PDU is used by the client
* to send actual audio data following a Data Incoming PDU.
*
* @param channel
* The channel along which the PDU should be sent.
*
* @param buffer
* The audio data to send.
*
* @param length
* The number of bytes of audio data to send.
*/
static void guac_rdp_ai_send_data(IWTSVirtualChannel* channel,
char* buffer, int length) {
/* Build data PDU */
wStream* stream = Stream_New(NULL, length + 1);
Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_DATA); /* MessageId */
Stream_Write(stream, buffer, length); /* Data */
/* Send stream */
channel->Write(channel, (UINT32) Stream_GetPosition(stream),
Stream_Buffer(stream), NULL);
Stream_Free(stream, TRUE);
}
/**
* Sends a Sound Formats PDU along the given channel. A Sound Formats PDU is
* used by the client to indicate to the server which formats of audio it
* supports (in response to the server sending exactly the same type of PDU).
* This PDU MUST be preceded by the Data Incoming PDU.
*
* @param channel
* The channel along which the PDU should be sent.
*
* @param formats
* An array of all supported formats.
*
* @param num_formats
* The number of entries in the formats array.
*/
static void guac_rdp_ai_send_formats(IWTSVirtualChannel* channel,
guac_rdp_ai_format* formats, int num_formats) {
int index;
int packet_size = 9;
/* Calculate packet size */
for (index = 0; index < num_formats; index++)
packet_size += 18 + formats[index].data_size;
wStream* stream = Stream_New(NULL, packet_size);
/* Write header */
Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_FORMATS); /* MessageId */
Stream_Write_UINT32(stream, num_formats); /* NumFormats */
Stream_Write_UINT32(stream, packet_size); /* cbSizeFormatsPacket */
/* Write all formats */
for (index = 0; index < num_formats; index++)
guac_rdp_ai_write_format(stream, &(formats[index]));
/* Send PDU */
channel->Write(channel, (UINT32) Stream_GetPosition(stream),
Stream_Buffer(stream), NULL);
Stream_Free(stream, TRUE);
}
/**
* Sends an Open Reply PDU along the given channel. An Open Reply PDU is
* used by the client to acknowledge the successful opening of the AUDIO_INPUT
* channel.
*
* @param channel
* The channel along which the PDU should be sent.
*
* @param result
* The HRESULT code to send to the server indicating success, failure, etc.
*/
static void guac_rdp_ai_send_open_reply(IWTSVirtualChannel* channel,
UINT32 result) {
/* Build open reply PDU */
wStream* stream = Stream_New(NULL, 5);
Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_OPEN_REPLY); /* MessageId */
Stream_Write_UINT32(stream, result); /* Result */
/* Send stream */
channel->Write(channel, (UINT32) Stream_GetPosition(stream),
Stream_Buffer(stream), NULL);
Stream_Free(stream, TRUE);
}
/**
* Sends a Format Change PDU along the given channel. A Format Change PDU is
* used by the client to acknowledge the format being used for data sent
* along the AUDIO_INPUT channel.
*
* @param channel
* The channel along which the PDU should be sent.
*
* @param format
* The index of the format being acknowledged, which must be the index of
* the format within the original Sound Formats PDU received from the
* server.
*/
static void guac_rdp_ai_send_formatchange(IWTSVirtualChannel* channel,
UINT32 format) {
/* Build format change PDU */
wStream* stream = Stream_New(NULL, 5);
Stream_Write_UINT8(stream, GUAC_RDP_MSG_SNDIN_FORMATCHANGE); /* MessageId */
Stream_Write_UINT32(stream, format); /* NewFormat */
/* Send stream */
channel->Write(channel, (UINT32) Stream_GetPosition(stream),
Stream_Buffer(stream), NULL);
Stream_Free(stream, TRUE);
}
void guac_rdp_ai_process_version(guac_client* client,
IWTSVirtualChannel* channel, wStream* stream) {
UINT32 version;
Stream_Read_UINT32(stream, version);
/* Warn if server's version number is incorrect */
if (version != 1)
guac_client_log(client, GUAC_LOG_WARNING,
"Server reports AUDIO_INPUT version %i, not 1", version);
/* Build response version PDU */
wStream* response = Stream_New(NULL, 5);
Stream_Write_UINT8(response, GUAC_RDP_MSG_SNDIN_VERSION); /* MessageId */
Stream_Write_UINT32(response, 1); /* Version */
/* Send response */
channel->Write(channel, (UINT32) Stream_GetPosition(response),
Stream_Buffer(response), NULL);
Stream_Free(response, TRUE);
}
void guac_rdp_ai_process_formats(guac_client* client,
IWTSVirtualChannel* channel, wStream* stream) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_audio_buffer* audio_buffer = rdp_client->audio_input;
UINT32 num_formats;
Stream_Read_UINT32(stream, num_formats); /* NumFormats */
Stream_Seek_UINT32(stream); /* cbSizeFormatsPacket (MUST BE IGNORED) */
UINT32 index;
for (index = 0; index < num_formats; index++) {
guac_rdp_ai_format format;
guac_rdp_ai_read_format(stream, &format);
/* Ignore anything but WAVE_FORMAT_PCM */
if (format.tag != GUAC_RDP_WAVE_FORMAT_PCM)
continue;
/* Set output format of internal audio buffer to match RDP server */
guac_rdp_audio_buffer_set_output(audio_buffer, format.rate,
format.channels, format.bps / 8);
/* Accept single format */
guac_rdp_ai_send_incoming_data(channel);
guac_rdp_ai_send_formats(channel, &format, 1);
return;
}
/* No formats available */
guac_client_log(client, GUAC_LOG_WARNING, "AUDIO_INPUT: No WAVE format.");
guac_rdp_ai_send_incoming_data(channel);
guac_rdp_ai_send_formats(channel, NULL, 0);
}
void guac_rdp_ai_flush_packet(char* buffer, int length, void* data) {
IWTSVirtualChannel* channel = (IWTSVirtualChannel*) data;
/* Send data over channel */
guac_rdp_ai_send_incoming_data(channel);
guac_rdp_ai_send_data(channel, buffer, length);
}
void guac_rdp_ai_process_open(guac_client* client,
IWTSVirtualChannel* channel, wStream* stream) {
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
guac_rdp_audio_buffer* audio_buffer = rdp_client->audio_input;
UINT32 packet_frames;
UINT32 initial_format;
Stream_Read_UINT32(stream, packet_frames); /* FramesPerPacket */
Stream_Read_UINT32(stream, initial_format); /* InitialFormat */
guac_client_log(client, GUAC_LOG_DEBUG, "RDP server is accepting audio "
"input as %i-channel, %i Hz PCM audio at %i bytes/sample.",
audio_buffer->out_format.channels,
audio_buffer->out_format.rate,
audio_buffer->out_format.bps);
/* Success */
guac_rdp_ai_send_formatchange(channel, initial_format);
guac_rdp_ai_send_open_reply(channel, 0);
/* Begin receiving audio data */
guac_rdp_audio_buffer_begin(audio_buffer, packet_frames,
guac_rdp_ai_flush_packet, channel);
}
void guac_rdp_ai_process_formatchange(guac_client* client,
IWTSVirtualChannel* channel, wStream* stream) {
/* Should not be called as we only accept one format */
guac_client_log(client, GUAC_LOG_DEBUG,
"RDP server requesting AUDIO_INPUT format change despite only one "
"format available.");
}