blob: 9e4b414262f20c4b93452a0ce6b538fb31c60b3e [file] [log] [blame]
/*
* 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.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.");
}