2013-12-29 04:53:12 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2013 Glyptodon LLC
|
2013-08-09 00:52:09 +00:00
|
|
|
*
|
2013-12-29 04:53:12 +00:00
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
* furnished to do so, subject to the following conditions:
|
2013-08-09 00:52:09 +00:00
|
|
|
*
|
2013-12-29 04:53:12 +00:00
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
* all copies or substantial portions of the Software.
|
2013-08-09 00:52:09 +00:00
|
|
|
*
|
2013-12-29 04:53:12 +00:00
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
* THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
2013-08-09 00:52:09 +00:00
|
|
|
|
|
|
|
#define WAV_BUFFER_SIZE 0x4000
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include <guacamole/audio.h>
|
|
|
|
#include <guacamole/client.h>
|
|
|
|
#include <guacamole/protocol.h>
|
|
|
|
|
|
|
|
#include "wav_encoder.h"
|
|
|
|
|
2013-08-09 01:32:00 +00:00
|
|
|
void wav_encoder_begin_handler(guac_audio_stream* audio) {
|
2013-08-09 00:52:09 +00:00
|
|
|
|
|
|
|
/* Allocate stream state */
|
|
|
|
wav_encoder_state* state = (wav_encoder_state*)
|
|
|
|
malloc(sizeof(wav_encoder_state));
|
|
|
|
|
|
|
|
/* Initialize buffer */
|
|
|
|
state->length = WAV_BUFFER_SIZE;
|
|
|
|
state->used = 0;
|
|
|
|
state->data_buffer = (unsigned char*) malloc(state->length);
|
|
|
|
|
|
|
|
audio->data = state;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void _wav_encoder_write_le(unsigned char* buffer, int value, int length) {
|
|
|
|
|
|
|
|
int offset;
|
|
|
|
|
|
|
|
/* Write all bytes in the given value in little-endian byte order */
|
|
|
|
for (offset=0; offset<length; offset++) {
|
|
|
|
|
|
|
|
/* Store byte */
|
|
|
|
*buffer = value & 0xFF;
|
|
|
|
|
|
|
|
/* Move to next byte */
|
|
|
|
value >>= 8;
|
|
|
|
buffer++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-08-09 01:32:00 +00:00
|
|
|
void wav_encoder_end_handler(guac_audio_stream* audio) {
|
2013-08-09 00:52:09 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Static header init
|
|
|
|
*/
|
|
|
|
|
|
|
|
wav_encoder_riff_header riff_header = {
|
|
|
|
.chunk_id = "RIFF",
|
|
|
|
.chunk_format = "WAVE"
|
|
|
|
};
|
|
|
|
|
|
|
|
wav_encoder_fmt_header fmt_header = {
|
|
|
|
.subchunk_id = "fmt ",
|
|
|
|
.subchunk_size = {0x10, 0x00, 0x00, 0x00}, /* 16 */
|
|
|
|
.subchunk_format = {0x01, 0x00} /* 1 = PCM */
|
|
|
|
};
|
|
|
|
|
|
|
|
wav_encoder_data_header data_header = {
|
|
|
|
.subchunk_id = "data"
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Get state */
|
|
|
|
wav_encoder_state* state = (wav_encoder_state*) audio->data;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* RIFF HEADER
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Chunk size */
|
|
|
|
_wav_encoder_write_le(riff_header.chunk_size,
|
|
|
|
4 + sizeof(fmt_header) + sizeof(data_header) + state->used,
|
|
|
|
sizeof(riff_header.chunk_size));
|
|
|
|
|
2013-08-09 01:32:00 +00:00
|
|
|
guac_audio_stream_write_encoded(audio,
|
2013-08-09 00:52:09 +00:00
|
|
|
(unsigned char*) &riff_header,
|
|
|
|
sizeof(riff_header));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* FMT HEADER
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Channels */
|
|
|
|
_wav_encoder_write_le(fmt_header.subchunk_channels,
|
|
|
|
audio->channels, sizeof(fmt_header.subchunk_channels));
|
|
|
|
|
|
|
|
/* Sample rate */
|
|
|
|
_wav_encoder_write_le(fmt_header.subchunk_sample_rate,
|
|
|
|
audio->rate, sizeof(fmt_header.subchunk_sample_rate));
|
|
|
|
|
|
|
|
/* Byte rate */
|
|
|
|
_wav_encoder_write_le(fmt_header.subchunk_byte_rate,
|
|
|
|
audio->rate * audio->channels * audio->bps / 8,
|
|
|
|
sizeof(fmt_header.subchunk_byte_rate));
|
|
|
|
|
|
|
|
/* Block align */
|
|
|
|
_wav_encoder_write_le(fmt_header.subchunk_block_align,
|
|
|
|
audio->channels * audio->bps / 8,
|
|
|
|
sizeof(fmt_header.subchunk_block_align));
|
|
|
|
|
|
|
|
/* Bits per second */
|
|
|
|
_wav_encoder_write_le(fmt_header.subchunk_bps,
|
|
|
|
audio->bps, sizeof(fmt_header.subchunk_bps));
|
|
|
|
|
2013-08-09 01:32:00 +00:00
|
|
|
guac_audio_stream_write_encoded(audio,
|
2013-08-09 00:52:09 +00:00
|
|
|
(unsigned char*) &fmt_header,
|
|
|
|
sizeof(fmt_header));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* DATA HEADER
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* PCM data size */
|
|
|
|
_wav_encoder_write_le(data_header.subchunk_size,
|
|
|
|
state->used, sizeof(data_header.subchunk_size));
|
|
|
|
|
2013-08-09 01:32:00 +00:00
|
|
|
guac_audio_stream_write_encoded(audio,
|
2013-08-09 00:52:09 +00:00
|
|
|
(unsigned char*) &data_header,
|
|
|
|
sizeof(data_header));
|
|
|
|
|
|
|
|
/* Write .wav data */
|
2013-08-09 01:32:00 +00:00
|
|
|
guac_audio_stream_write_encoded(audio, state->data_buffer, state->used);
|
2013-08-09 00:52:09 +00:00
|
|
|
|
|
|
|
/* Free stream state */
|
|
|
|
free(state);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-08-09 01:32:00 +00:00
|
|
|
void wav_encoder_write_handler(guac_audio_stream* audio,
|
2013-08-09 23:52:25 +00:00
|
|
|
const unsigned char* pcm_data, int length) {
|
2013-08-09 00:52:09 +00:00
|
|
|
|
|
|
|
/* Get state */
|
|
|
|
wav_encoder_state* state = (wav_encoder_state*) audio->data;
|
|
|
|
|
|
|
|
/* Increase size of buffer if necessary */
|
|
|
|
if (state->used + length > state->length) {
|
|
|
|
|
|
|
|
/* Increase to double concatenated size to accomodate */
|
|
|
|
state->length = (state->length + length)*2;
|
|
|
|
state->data_buffer = realloc(state->data_buffer,
|
|
|
|
state->length);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Append to buffer */
|
|
|
|
memcpy(&(state->data_buffer[state->used]), pcm_data, length);
|
|
|
|
state->used += length;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Encoder handlers */
|
2013-08-09 01:32:00 +00:00
|
|
|
guac_audio_encoder _wav_encoder = {
|
2013-08-09 00:52:09 +00:00
|
|
|
.mimetype = "audio/wav",
|
|
|
|
.begin_handler = wav_encoder_begin_handler,
|
|
|
|
.write_handler = wav_encoder_write_handler,
|
|
|
|
.end_handler = wav_encoder_end_handler
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Actual encoder */
|
2013-08-09 01:32:00 +00:00
|
|
|
guac_audio_encoder* wav_encoder = &_wav_encoder;
|
2013-08-09 00:52:09 +00:00
|
|
|
|