Initial support for audio in libguac.
This commit is contained in:
parent
8e732ed2ab
commit
8bd70053be
@ -40,6 +40,7 @@ AM_CFLAGS = -Werror -Wall -pedantic -Iguacamole
|
|||||||
|
|
||||||
libguacincdir = $(includedir)/guacamole
|
libguacincdir = $(includedir)/guacamole
|
||||||
libguacinc_HEADERS = \
|
libguacinc_HEADERS = \
|
||||||
|
guacamole/audio.h \
|
||||||
guacamole/client.h \
|
guacamole/client.h \
|
||||||
guacamole/error.h \
|
guacamole/error.h \
|
||||||
guacamole/hash.h \
|
guacamole/hash.h \
|
||||||
@ -55,9 +56,11 @@ libguacinc_HEADERS = \
|
|||||||
|
|
||||||
noinst_HEADERS = \
|
noinst_HEADERS = \
|
||||||
client-handlers.h \
|
client-handlers.h \
|
||||||
palette.h
|
palette.h \
|
||||||
|
wav_encoder.h
|
||||||
|
|
||||||
libguac_la_SOURCES = \
|
libguac_la_SOURCES = \
|
||||||
|
audio.c \
|
||||||
client.c \
|
client.c \
|
||||||
client-handlers.c \
|
client-handlers.c \
|
||||||
error.c \
|
error.c \
|
||||||
@ -73,6 +76,12 @@ libguac_la_SOURCES = \
|
|||||||
timestamp.c \
|
timestamp.c \
|
||||||
unicode.c
|
unicode.c
|
||||||
|
|
||||||
lib_LTLIBRARIES = libguac.la
|
# Compile OGG support if available
|
||||||
libguac_la_LDFLAGS = -version-info 5:0:0 @PTHREAD_LIBS@ @CAIRO_LIBS@ @PNG_LIBS@ @DL_LIBS@
|
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@
|
||||||
|
|
||||||
|
174
src/libguac/audio.c
Normal file
174
src/libguac/audio.c
Normal file
@ -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 <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <guacamole/protocol.h>
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
#include <guacamole/stream.h>
|
||||||
|
#include <guacamole/audio.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
212
src/libguac/guacamole/audio.h
Normal file
212
src/libguac/guacamole/audio.h
Normal file
@ -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 <guacamole/client.h>
|
||||||
|
#include <guacamole/stream.h>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
211
src/libguac/ogg_encoder.c
Normal file
211
src/libguac/ogg_encoder.c
Normal file
@ -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 <stdlib.h>
|
||||||
|
|
||||||
|
#include <guacamole/audio.h>
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
#include <guacamole/protocol.h>
|
||||||
|
|
||||||
|
#include <vorbis/vorbisenc.h>
|
||||||
|
|
||||||
|
#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; i<samples; i++) {
|
||||||
|
|
||||||
|
/* FIXME: For now, assume 2 channels, 16-bit */
|
||||||
|
int left = ((readbuffer[i*4+1]<<8)|(0x00ff&(int)readbuffer[i*4]));
|
||||||
|
int right = ((readbuffer[i*4+3]<<8)|(0x00ff&(int)readbuffer[i*4+2]));
|
||||||
|
|
||||||
|
/* Store sample in buffer */
|
||||||
|
buffer[0][i] = left / 32768.f;
|
||||||
|
buffer[1][i] = right / 32768.f;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Submit data */
|
||||||
|
vorbis_analysis_wrote(&(state->vorbis_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;
|
||||||
|
|
67
src/libguac/ogg_encoder.h
Normal file
67
src/libguac/ogg_encoder.h
Normal file
@ -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 <guacamole/audio.h>
|
||||||
|
|
||||||
|
#include <vorbis/vorbisenc.h>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
201
src/libguac/wav_encoder.c
Normal file
201
src/libguac/wav_encoder.c
Normal file
@ -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 <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <guacamole/audio.h>
|
||||||
|
#include <guacamole/client.h>
|
||||||
|
#include <guacamole/protocol.h>
|
||||||
|
|
||||||
|
#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<length; offset++) {
|
||||||
|
|
||||||
|
/* Store byte */
|
||||||
|
*buffer = value & 0xFF;
|
||||||
|
|
||||||
|
/* Move to next byte */
|
||||||
|
value >>= 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;
|
||||||
|
|
144
src/libguac/wav_encoder.h
Normal file
144
src/libguac/wav_encoder.h
Normal file
@ -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 <guacamole/audio.h>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user