From 8bd70053beddd912dd84d034be959387d8c480f6 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 8 Aug 2013 17:52:09 -0700 Subject: [PATCH] Initial support for audio in libguac. --- src/libguac/Makefile.am | 15 ++- src/libguac/audio.c | 174 ++++++++++++++++++++++++++++ src/libguac/guacamole/audio.h | 212 ++++++++++++++++++++++++++++++++++ src/libguac/ogg_encoder.c | 211 +++++++++++++++++++++++++++++++++ src/libguac/ogg_encoder.h | 67 +++++++++++ src/libguac/wav_encoder.c | 201 ++++++++++++++++++++++++++++++++ src/libguac/wav_encoder.h | 144 +++++++++++++++++++++++ 7 files changed, 1021 insertions(+), 3 deletions(-) create mode 100644 src/libguac/audio.c create mode 100644 src/libguac/guacamole/audio.h create mode 100644 src/libguac/ogg_encoder.c create mode 100644 src/libguac/ogg_encoder.h create mode 100644 src/libguac/wav_encoder.c create mode 100644 src/libguac/wav_encoder.h diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index c50c6643..100ac210 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -40,6 +40,7 @@ AM_CFLAGS = -Werror -Wall -pedantic -Iguacamole libguacincdir = $(includedir)/guacamole libguacinc_HEADERS = \ + guacamole/audio.h \ guacamole/client.h \ guacamole/error.h \ guacamole/hash.h \ @@ -55,9 +56,11 @@ libguacinc_HEADERS = \ noinst_HEADERS = \ client-handlers.h \ - palette.h + palette.h \ + wav_encoder.h libguac_la_SOURCES = \ + audio.c \ client.c \ client-handlers.c \ error.c \ @@ -73,6 +76,12 @@ libguac_la_SOURCES = \ timestamp.c \ unicode.c -lib_LTLIBRARIES = libguac.la -libguac_la_LDFLAGS = -version-info 5:0:0 @PTHREAD_LIBS@ @CAIRO_LIBS@ @PNG_LIBS@ @DL_LIBS@ +# Compile OGG support if available +if ENABLE_OGG +libguac_la_SOURCES += ogg_encoder.c +noinst_HEADERS += ogg_encoder.h +endif + +lib_LTLIBRARIES = libguac.la +libguac_la_LDFLAGS = -version-info 5:0:0 @PTHREAD_LIBS@ @CAIRO_LIBS@ @PNG_LIBS@ @DL_LIBS@ @VORBIS_LIBS@ diff --git a/src/libguac/audio.c b/src/libguac/audio.c new file mode 100644 index 00000000..2f6fe528 --- /dev/null +++ b/src/libguac/audio.c @@ -0,0 +1,174 @@ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is libguac. + * + * The Initial Developer of the Original Code is + * Michael Jumper. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include +#include +#include +#include +#include +#include +#include + +audio_stream* audio_stream_alloc(guac_client* client, audio_encoder* encoder) { + + /* Allocate stream */ + audio_stream* audio = (audio_stream*) malloc(sizeof(audio_stream)); + audio->client = client; + + /* Reset buffer stats */ + audio->used = 0; + audio->length = 0x40000; + + audio->encoded_data_used = 0; + audio->encoded_data_length = 0x40000; + + /* Allocate buffers */ + audio->pcm_data = malloc(audio->length); + audio->encoded_data = malloc(audio->encoded_data_length); + + /* Assign encoder */ + audio->encoder = encoder; + audio->stream = guac_client_alloc_stream(client); + + /* Ensure socket within new stream is threadsafe */ + guac_socket_require_threadsafe(audio->stream->socket); + + return audio; +} + +void audio_stream_begin(audio_stream* audio, int rate, int channels, int bps) { + + /* Load PCM properties */ + audio->rate = rate; + audio->channels = channels; + audio->bps = bps; + + /* Reset write counter */ + audio->pcm_bytes_written = 0; + + /* Call handler */ + audio->encoder->begin_handler(audio); + +} + +void audio_stream_end(audio_stream* audio) { + + double duration; + + /* Flush stream and finish encoding */ + audio_stream_flush(audio); + audio->encoder->end_handler(audio); + + /* Calculate duration of PCM data */ + duration = ((double) (audio->pcm_bytes_written * 1000 * 8)) + / audio->rate / audio->channels / audio->bps; + + /* Send audio */ + guac_protocol_send_audio(audio->stream->socket, + 0, audio->encoder->mimetype, + duration, audio->encoded_data, audio->encoded_data_used); + + /* Clear data */ + audio->encoded_data_used = 0; + +} + +void audio_stream_free(audio_stream* audio) { + free(audio->pcm_data); + free(audio); +} + +void audio_stream_write_pcm(audio_stream* audio, + unsigned char* data, int length) { + + /* Update counter */ + audio->pcm_bytes_written += length; + + /* Resize audio buffer if necessary */ + if (length > audio->length) { + + /* Resize to double provided length */ + audio->length = length*2; + audio->pcm_data = realloc(audio->pcm_data, audio->length); + + } + + /* Flush if necessary */ + if (audio->used + length > audio->length) + audio_stream_flush(audio); + + /* Append to buffer */ + memcpy(&(audio->pcm_data[audio->used]), data, length); + audio->used += length; + +} + +void audio_stream_flush(audio_stream* audio) { + + /* If data in buffer */ + if (audio->used != 0) { + + /* Write data */ + audio->encoder->write_handler(audio, + audio->pcm_data, audio->used); + + /* Reset buffer */ + audio->used = 0; + + } + +} + +void audio_stream_write_encoded(audio_stream* audio, + unsigned char* data, int length) { + + /* Resize audio buffer if necessary */ + if (audio->encoded_data_used + length > audio->encoded_data_length) { + + /* Increase to double concatenated size to accomodate */ + audio->encoded_data_length = (audio->encoded_data_length + length)*2; + audio->encoded_data = realloc(audio->encoded_data, + audio->encoded_data_length); + + } + + /* Append to buffer */ + memcpy(&(audio->encoded_data[audio->encoded_data_used]), data, length); + audio->encoded_data_used += length; + +} + diff --git a/src/libguac/guacamole/audio.h b/src/libguac/guacamole/audio.h new file mode 100644 index 00000000..d71e1c57 --- /dev/null +++ b/src/libguac/guacamole/audio.h @@ -0,0 +1,212 @@ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is libguac. + * + * The Initial Developer of the Original Code is + * Michael Jumper. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef __GUAC_AUDIO_H +#define __GUAC_AUDIO_H + +#include +#include + +typedef struct audio_stream audio_stream; + +/** + * Handler which is called when the audio stream is opened. + */ +typedef void audio_encoder_begin_handler(audio_stream* audio); + +/** + * Handler which is called when the audio stream is closed. + */ +typedef void audio_encoder_end_handler(audio_stream* audio); + +/** + * Handler which is called when the audio stream is flushed. + */ +typedef void audio_encoder_write_handler(audio_stream* audio, + unsigned char* pcm_data, int length); + +/** + * Arbitrary audio codec encoder. + */ +typedef struct audio_encoder { + + /** + * The mimetype of the audio data encoded by this audio + * encoder. + */ + const char* mimetype; + + /** + * Handler which will be called when the audio stream is opened. + */ + audio_encoder_begin_handler* begin_handler; + + /** + * Handler which will be called when the audio stream is flushed. + */ + audio_encoder_write_handler* write_handler; + + /** + * Handler which will be called when the audio stream is closed. + */ + audio_encoder_end_handler* end_handler; + +} audio_encoder; + +/** + * Basic audio stream. PCM data is added to the stream. When the stream is + * flushed, a write handler receives PCM data packets and, presumably, streams + * them to the guac_stream provided. + */ +struct audio_stream { + + /** + * PCM data buffer, 16-bit samples, 2-channel, 44100 Hz. + */ + unsigned char* pcm_data; + + /** + * Number of bytes in buffer. + */ + int used; + + /** + * Maximum number of bytes in buffer. + */ + int length; + + /** + * Encoded audio data buffer, as written by the encoder. + */ + unsigned char* encoded_data; + + /** + * Number of bytes in the encoded data buffer. + */ + int encoded_data_used; + + /** + * Maximum number of bytes in the encoded data buffer. + */ + int encoded_data_length; + + /** + * Arbitrary codec encoder. When the PCM buffer is flushed, PCM data will + * be sent to this encoder. + */ + audio_encoder* encoder; + + /** + * The client associated with this audio stream. + */ + guac_client* client; + + /** + * The actual stream associated with this audio stream. + */ + guac_stream* stream; + + /** + * The number of samples per second of PCM data sent to this stream. + */ + int rate; + + /** + * The number of audio channels per sample of PCM data. Legal values are + * 1 or 2. + */ + int channels; + + /** + * The number of bits per sample per channel for PCM data. Legal values are + * 8 or 16. + */ + int bps; + + /** + * The number of PCM bytes written since the audio chunk began. + */ + int pcm_bytes_written; + + /** + * Encoder-specific state data. + */ + void* data; + +}; + +/** + * Allocates a new audio stream. + */ +audio_stream* audio_stream_alloc(guac_client* client, + audio_encoder* encoder); + +/** + * Frees the given audio stream. + */ +void audio_stream_free(audio_stream* stream); + +/** + * Begins a new audio stream. + */ +void audio_stream_begin(audio_stream* stream, int rate, int channels, int bps); + +/** + * Ends the current audio stream. + */ +void audio_stream_end(audio_stream* stream); + +/** + * Writes PCM data to the given audio stream. + */ +void audio_stream_write_pcm(audio_stream* stream, + unsigned char* data, int length); + +/** + * Flushes the given audio stream. + */ +void audio_stream_flush(audio_stream* stream); + +/** + * Appends arbitrarily-encoded data to the encoded_data buffer + * within the given audio stream. + */ +void audio_stream_write_encoded(audio_stream* audio, + unsigned char* data, int length); + +#endif + diff --git a/src/libguac/ogg_encoder.c b/src/libguac/ogg_encoder.c new file mode 100644 index 00000000..d1ec6721 --- /dev/null +++ b/src/libguac/ogg_encoder.c @@ -0,0 +1,211 @@ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is libguac. + * + * The Initial Developer of the Original Code is + * Michael Jumper. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include + +#include +#include +#include + +#include + +#include "ogg_encoder.h" + +void ogg_encoder_begin_handler(audio_stream* audio) { + + /* Allocate stream state */ + ogg_encoder_state* state = (ogg_encoder_state*) + malloc(sizeof(ogg_encoder_state)); + + /* Init state */ + vorbis_info_init(&(state->info)); + vorbis_encode_init_vbr(&(state->info), audio->channels, audio->rate, 0.4); + + vorbis_analysis_init(&(state->vorbis_state), &(state->info)); + vorbis_block_init(&(state->vorbis_state), &(state->vorbis_block)); + + vorbis_comment_init(&(state->comment)); + vorbis_comment_add_tag(&(state->comment), "ENCODER", "libguac-client-rdp"); + + ogg_stream_init(&(state->ogg_state), rand()); + + /* Write headers */ + { + ogg_packet header; + ogg_packet header_comm; + ogg_packet header_code; + + vorbis_analysis_headerout( + &(state->vorbis_state), + &(state->comment), + &header, &header_comm, &header_code); + + ogg_stream_packetin(&(state->ogg_state), &header); + ogg_stream_packetin(&(state->ogg_state), &header_comm); + ogg_stream_packetin(&(state->ogg_state), &header_code); + + /* For each packet */ + while (ogg_stream_flush(&(state->ogg_state), &(state->ogg_page)) != 0) { + + /* Write packet header */ + audio_stream_write_encoded(audio, + state->ogg_page.header, + state->ogg_page.header_len); + + /* Write packet body */ + audio_stream_write_encoded(audio, + state->ogg_page.body, + state->ogg_page.body_len); + } + + } + + audio->data = state; + +} + +void ogg_encoder_write_blocks(audio_stream* audio) { + + /* Get state */ + ogg_encoder_state* state = (ogg_encoder_state*) audio->data; + + while (vorbis_analysis_blockout(&(state->vorbis_state), + &(state->vorbis_block)) == 1) { + + /* Analyze */ + vorbis_analysis(&(state->vorbis_block), NULL); + vorbis_bitrate_addblock(&(state->vorbis_block)); + + /* Flush Ogg pages */ + while (vorbis_bitrate_flushpacket(&(state->vorbis_state), + &(state->ogg_packet))) { + + /* Weld packet into bitstream */ + ogg_stream_packetin(&(state->ogg_state), &(state->ogg_packet)); + + /* Write out pages */ + while (ogg_stream_pageout(&(state->ogg_state), + &(state->ogg_page)) != 0) { + + /* Write packet header */ + audio_stream_write_encoded(audio, + state->ogg_page.header, + state->ogg_page.header_len); + + /* Write packet body */ + audio_stream_write_encoded(audio, + state->ogg_page.body, + state->ogg_page.body_len); + + if (ogg_page_eos(&(state->ogg_page))) + break; + + } + + } + + } + +} + +void ogg_encoder_end_handler(audio_stream* audio) { + + /* Get state */ + ogg_encoder_state* state = (ogg_encoder_state*) audio->data; + + /* Write end-of-stream */ + vorbis_analysis_wrote(&(state->vorbis_state), 0); + ogg_encoder_write_blocks(audio); + + /* Clean up encoder */ + ogg_stream_clear(&(state->ogg_state)); + vorbis_block_clear(&(state->vorbis_block)); + vorbis_dsp_clear(&(state->vorbis_state)); + vorbis_comment_clear(&(state->comment)); + vorbis_info_clear(&(state->info)); + + /* Free stream state */ + free(audio->data); + +} + +void ogg_encoder_write_handler(audio_stream* audio, + unsigned char* pcm_data, int length) { + + /* Get state */ + ogg_encoder_state* state = (ogg_encoder_state*) audio->data; + + /* Calculate samples */ + int samples = length / audio->channels * 8 / audio->bps; + int i; + + /* Get buffer */ + float** buffer = vorbis_analysis_buffer(&(state->vorbis_state), samples); + + signed char* readbuffer = (signed char*) pcm_data; + + for (i=0; ivorbis_state), samples); + + /* Write data */ + ogg_encoder_write_blocks(audio); + +} + +/* Encoder handlers */ +audio_encoder _ogg_encoder = { + .mimetype = "audio/ogg", + .begin_handler = ogg_encoder_begin_handler, + .write_handler = ogg_encoder_write_handler, + .end_handler = ogg_encoder_end_handler +}; + +/* Actual encoder */ +audio_encoder* ogg_encoder = &_ogg_encoder; + diff --git a/src/libguac/ogg_encoder.h b/src/libguac/ogg_encoder.h new file mode 100644 index 00000000..3b2b5fbb --- /dev/null +++ b/src/libguac/ogg_encoder.h @@ -0,0 +1,67 @@ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is libguac. + * + * The Initial Developer of the Original Code is + * Michael Jumper. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef __GUAC_OGG_ENCODER_H +#define __GUAC_OGG_ENCODER_H + +#include + +#include + +typedef struct ogg_encoder_state { + + /** + * Ogg state + */ + ogg_stream_state ogg_state; + ogg_page ogg_page; + ogg_packet ogg_packet; + + /** + * Vorbis state + */ + vorbis_info info; + vorbis_comment comment; + vorbis_dsp_state vorbis_state; + vorbis_block vorbis_block; + +} ogg_encoder_state; + +extern audio_encoder* ogg_encoder; + +#endif + diff --git a/src/libguac/wav_encoder.c b/src/libguac/wav_encoder.c new file mode 100644 index 00000000..55d64580 --- /dev/null +++ b/src/libguac/wav_encoder.c @@ -0,0 +1,201 @@ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is libguac. + * + * The Initial Developer of the Original Code is + * Michael Jumper. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#define WAV_BUFFER_SIZE 0x4000 + +#include +#include + +#include +#include +#include + +#include "wav_encoder.h" + +void wav_encoder_begin_handler(audio_stream* audio) { + + /* 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>= 8; + buffer++; + + } + +} + +void wav_encoder_end_handler(audio_stream* audio) { + + /* + * 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)); + + audio_stream_write_encoded(audio, + (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)); + + audio_stream_write_encoded(audio, + (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)); + + audio_stream_write_encoded(audio, + (unsigned char*) &data_header, + sizeof(data_header)); + + /* Write .wav data */ + audio_stream_write_encoded(audio, state->data_buffer, state->used); + + /* Free stream state */ + free(state); + +} + +void wav_encoder_write_handler(audio_stream* audio, + unsigned char* pcm_data, int length) { + + /* 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 */ +audio_encoder _wav_encoder = { + .mimetype = "audio/wav", + .begin_handler = wav_encoder_begin_handler, + .write_handler = wav_encoder_write_handler, + .end_handler = wav_encoder_end_handler +}; + +/* Actual encoder */ +audio_encoder* wav_encoder = &_wav_encoder; + diff --git a/src/libguac/wav_encoder.h b/src/libguac/wav_encoder.h new file mode 100644 index 00000000..3083456e --- /dev/null +++ b/src/libguac/wav_encoder.h @@ -0,0 +1,144 @@ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is libguac. + * + * The Initial Developer of the Original Code is + * Michael Jumper. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef __GUAC_WAV_ENCODER_H +#define __GUAC_WAV_ENCODER_H + +#include + +typedef struct wav_encoder_riff_header { + + /** + * The RIFF chunk header, normally the string "RIFF". + */ + unsigned char chunk_id[4]; + + /** + * Size of the entire file, not including chunk_id or chunk_size. + */ + unsigned char chunk_size[4]; + + /** + * The format of this file, normally the string "WAVE". + */ + unsigned char chunk_format[4]; + +} wav_encoder_riff_header; + +typedef struct wav_encoder_fmt_header { + + /** + * ID of this subchunk. For the fmt subchunk, this should be "fmt ". + */ + unsigned char subchunk_id[4]; + + /** + * The size of the rest of this subchunk. For PCM, this will be 16. + */ + unsigned char subchunk_size[4]; + + /** + * Format of this subchunk. For PCM, this will be 1. + */ + unsigned char subchunk_format[2]; + + /** + * The number of channels in the PCM data. + */ + unsigned char subchunk_channels[2]; + + /** + * The sample rate of the PCM data. + */ + unsigned char subchunk_sample_rate[4]; + + /** + * The sample rate of the PCM data in bytes per second. + */ + unsigned char subchunk_byte_rate[4]; + + /** + * The number of bytes per sample. + */ + unsigned char subchunk_block_align[2]; + + /** + * The number of bits per sample. + */ + unsigned char subchunk_bps[2]; + +} wav_encoder_fmt_header; + +typedef struct wav_encoder_state { + + /** + * Arbitrary PCM data available for writing when the overall WAV is + * flushed. + */ + unsigned char* data_buffer; + + /** + * The number of bytes currently present in the data buffer. + */ + int used; + + /** + * The total number of bytes that can be written into the data buffer + * without requiring resizing. + */ + int length; + +} wav_encoder_state; + +typedef struct wav_encoder_data_header { + + /** + * ID of this subchunk. For the data subchunk, this should be "data". + */ + unsigned char subchunk_id[4]; + + /** + * The number of bytes in the PCM data. + */ + unsigned char subchunk_size[4]; + +} wav_encoder_data_header; + +extern audio_encoder* wav_encoder; + +#endif +