From 9cecd3f493ac05a472f31990d62ddfc15f12dfda Mon Sep 17 00:00:00 2001 From: "rafael@riseup.net" Date: Tue, 14 May 2013 16:25:16 +0000 Subject: [PATCH] Added Opus support. 2nd part --- darkice/trunk/src/OpusLibEncoder.cpp | 551 ++++++++++++++++++++++++++ darkice/trunk/src/OpusLibEncoder.h | 561 +++++++++++++++++++++++++++ 2 files changed, 1112 insertions(+) create mode 100644 darkice/trunk/src/OpusLibEncoder.cpp create mode 100644 darkice/trunk/src/OpusLibEncoder.h diff --git a/darkice/trunk/src/OpusLibEncoder.cpp b/darkice/trunk/src/OpusLibEncoder.cpp new file mode 100644 index 0000000..9a77481 --- /dev/null +++ b/darkice/trunk/src/OpusLibEncoder.cpp @@ -0,0 +1,551 @@ +/*------------------------------------------------------------------------------ + + Copyright (c) 2000-2007 Tyrell Corporation. All rights reserved. + + Tyrell DarkIce + + File : OpusLibEncoder.cpp + Version : $Revision$ + Author : $Author$ + Location : $HeadURL$ + + Copyright notice: + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +------------------------------------------------------------------------------*/ + +/* ============================================================ include files */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +// compile only if configured for Ogg / Opus +#ifdef HAVE_OPUS_LIB + + +#include "Exception.h" +#include "Util.h" +#include "OpusLibEncoder.h" +#include "CastSink.h" + + +/* =================================================== local data structures */ + + +/* ================================================ local constants & macros */ + +/*------------------------------------------------------------------------------ + * File identity + *----------------------------------------------------------------------------*/ +static const char fileid[] = "$Id$"; + + +/* =============================================== local function prototypes */ + + +/* ============================================================= module code */ + +/*------------------------------------------------------------------------------ + * Initialize the encoder + *----------------------------------------------------------------------------*/ +void +OpusLibEncoder :: init ( unsigned int outMaxBitrate ) + throw ( Exception ) +{ + this->outMaxBitrate = outMaxBitrate; + + if ( getInBitsPerSample() != 16 && getInBitsPerSample() != 8 ) { + throw Exception( __FILE__, __LINE__, + "specified bits per sample not supported", + getInBitsPerSample() ); + } + + if ( getInChannel() != 1 && getInChannel() != 2 ) { + throw Exception( __FILE__, __LINE__, + "unsupported number of channels for the encoder", + getInChannel() ); + } + + if ( getOutSampleRate() != 48000 ) { + throw Exception( __FILE__, __LINE__, + "unsupported sample rate for this encoder", + getOutSampleRate() ); + } + + if ( getOutSampleRate() == getInSampleRate() ) { + resampleRatio = 1; + converter = 0; + } else { + resampleRatio = ( (double) getOutSampleRate() / + (double) getInSampleRate() ); + + // Determine if we can use linear interpolation. + // The inverse of the ratio must be a power of two for linear mode to + // be of sufficient quality. + + bool useLinear = true; + double inverse = 1 / resampleRatio; + int integer = (int) inverse; + + // Check that the inverse of the ratio is an integer + if( integer == inverse ) { + while( useLinear && integer ) { // Loop through the bits + // If the lowest order bit is not the only one set + if( integer & 1 && integer != 1 ) { + // Not a power of two; cannot use linear + useLinear = false; + } else { + // Shift all the bits over and try again + integer >>= 1; + } + } + } else { + useLinear = false; + } + + // If we get here and useLinear is still true, then we have + // a power of two. + + // open the aflibConverter in + // - high quality + // - linear or quadratic (non-linear) based on algorithm + // - not filter interpolation +#ifdef HAVE_SRC_LIB + int srcError = 0; + converter = src_new(useLinear == true ? SRC_LINEAR : SRC_SINC_FASTEST, + getInChannel(), &srcError); + if(srcError) + throw Exception (__FILE__, __LINE__, "libsamplerate error: ", src_strerror (srcError)); +#else + converter = new aflibConverter( true, useLinear, false); +#endif + } + + int bufferSize = (getInBitsPerSample()/8) * getInChannel() * 480; + internalBuffer = new unsigned char[bufferSize]; + internalBufferLength = 0; + memset( internalBuffer, 0, bufferSize); + + encoderOpen = false; +} + + +/*------------------------------------------------------------------------------ + * Open an encoding session + *----------------------------------------------------------------------------*/ +bool +OpusLibEncoder :: open ( void ) + throw ( Exception ) +{ + int ret; + + if ( isOpen() ) { + close(); + } + + // open the underlying sink + if ( !getSink()->open() ) { + throw Exception( __FILE__, __LINE__, + "opus lib opening underlying sink error"); + } + + int err; + opusEncoder = opus_encoder_create( getOutSampleRate(), + getInChannel(), + OPUS_APPLICATION_AUDIO, + &err); + if( err != OPUS_OK ) { + throw Exception( __FILE__, __LINE__, + "opus encoder creation error", + err); + } + + opus_encoder_ctl(opusEncoder, OPUS_SET_COMPLEXITY(10)); + opus_encoder_ctl(opusEncoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)); + + switch ( getOutBitrateMode() ) { + + case cbr: { + int maxBitrate = getOutMaxBitrate() * 1000; + if ( !maxBitrate ) { + maxBitrate = 96000; + } + opus_encoder_ctl(opusEncoder, OPUS_SET_BITRATE(maxBitrate)); + opus_encoder_ctl(opusEncoder, OPUS_SET_VBR(0)); + } break; + + case abr: { + int maxBitrate = getOutMaxBitrate() * 1000; + if ( !maxBitrate ) { + maxBitrate = 96000; + } + /* set non-managed VBR around the average bitrate */ + opus_encoder_ctl(opusEncoder, OPUS_SET_BITRATE(maxBitrate)); + opus_encoder_ctl(opusEncoder, OPUS_SET_VBR(1)); + opus_encoder_ctl(opusEncoder, OPUS_SET_VBR_CONSTRAINT(0)); + } break; + case vbr: + throw Exception( __FILE__, __LINE__, "vbr is not supported; use cbr/abr"); + } + + if ( (ret = ogg_stream_init( &oggStreamState, 0)) ) { + throw Exception( __FILE__, __LINE__, "ogg stream init error", ret); + } + + // First, we need to assemble and send a OggOpus header. + OpusIdHeader header; + strncpy(header.magic, "OpusHead", 8); + header.version = 1; + header.channels = getOutChannel(); + header.preskip = 0; + header.samplerate = getInSampleRate(); + header.gain = 0; // technically a fixed-point decimal. + header.chanmap = 0; + + // And, now we need to send a Opus comment header. + // Anything after this can be audio. + + char vendor[8] = "darkice"; + char titlestr[7] = "TITLE="; + OpusCommentHeader::Tags tags[1]; + char name[40]; + CastSink* sink = dynamic_cast(getSink().get()); + if( sink && sink->getName() ) { + strncpy(name, (char*)sink->getName(), 39); + name[39] = 0; + } + else { + strncpy(name, "Darkice Stream", 39); + } + tags[0].tag_len = strlen(titlestr) + strlen(name); + tags[0].tag_str = (char*) malloc( tags[0].tag_len ); + if( tags[0].tag_str == NULL ) { + throw Exception( __FILE__, __LINE__, "malloc failed"); + } + strncpy( tags[0].tag_str, titlestr, tags[0].tag_len); + strncat( tags[0].tag_str, name, tags[0].tag_len); + + OpusCommentHeader commentHeader; + strncpy(commentHeader.magic, "OpusTags", 8); + commentHeader.vendor_length = strlen(vendor); + commentHeader.vendor_string = vendor; + commentHeader.num_tags = 1; + commentHeader.tags = tags; + + ogg_packet oggHeader; + ogg_packet oggCommentHeader; + memset(&oggHeader, 0, sizeof(oggHeader)); + memset(&oggCommentHeader, 0, sizeof(oggCommentHeader)); + unsigned char* headerData = NULL; + unsigned char* commentData = NULL; + int headerLen = 0; + int commentLen = 0; + + headerLen = header.buildPacket( &headerData); + commentLen = commentHeader.buildPacket( &commentData); + + oggHeader.packet = headerData; + oggHeader.bytes = headerLen; + oggHeader.b_o_s = 1; + oggHeader.e_o_s = 0; + oggHeader.granulepos = 0; + oggHeader.packetno = 0; + + oggCommentHeader.packet = commentData; + oggCommentHeader.bytes = commentLen; + oggCommentHeader.b_o_s = 0; + oggCommentHeader.e_o_s = 0; + oggCommentHeader.granulepos = 0; + oggCommentHeader.packetno = 1; + + oggPacketNumber = 2; + oggGranulePosition = 0; + + ogg_stream_packetin( &oggStreamState, &oggHeader); + ogg_stream_packetin( &oggStreamState, &oggCommentHeader); + + ogg_page oggPage; + while ( ogg_stream_flush( &oggStreamState, &oggPage) ) { + getSink()->write( oggPage.header, oggPage.header_len); + getSink()->write( oggPage.body, oggPage.body_len); + } + + free(tags[0].tag_str); + free(headerData); + free(commentData); + + // initialize the resampling coverter if needed + if ( converter ) { +#ifdef HAVE_SRC_LIB + converterData.input_frames = 4096/((getInBitsPerSample() / 8) * getInChannel()); + converterData.data_in = new float[converterData.input_frames*getInChannel()]; + converterData.output_frames = (int) (converterData.input_frames * resampleRatio + 1); + converterData.data_out = new float[getInChannel() * converterData.output_frames]; + converterData.src_ratio = resampleRatio; + converterData.end_of_input = 0; +#else + converter->initialize( resampleRatio, getInChannel()); +#endif + } + + encoderOpen = true; + + return true; +} + + +/*------------------------------------------------------------------------------ + * Write data to the encoder + *----------------------------------------------------------------------------*/ +unsigned int +OpusLibEncoder :: write ( const void * buf, + unsigned int len ) throw ( Exception ) +{ + if ( !isOpen() || len == 0 ) { + return 0; + } + + unsigned int channels = getInChannel(); + unsigned int bitsPerSample = getInBitsPerSample(); + unsigned int sampleSize = (bitsPerSample / 8) * channels; + + unsigned int i; + + if ( getInChannel() == 2 && getOutChannel() == 1 ) { + for ( i = 0; i < len/sampleSize; i++) { + if ( bitsPerSample == 8 ) { + char * buf8 = (char *) buf; + unsigned int ix = sampleSize * i; + unsigned int iix = ix; + buf8[i] = (buf8[ix] + buf8[++iix]) / 2; + } + if ( bitsPerSample == 16 ) { + short * buf16 = (short *) buf; + unsigned int ix = (bitsPerSample >> 3) * i; + unsigned int iix = ix; + buf16[i] = (buf16[ix] + buf16[++iix]) / 2; + } + } + len >>= 1; + channels = 1; + } + + sampleSize = (bitsPerSample / 8) * channels; + unsigned int processed = 0; + unsigned int bytesToProcess = len - (len % sampleSize); + unsigned int totalProcessed = 0; + unsigned char * b = (unsigned char*) buf; + unsigned char * tempBuffer = NULL; + + if( internalBufferLength > 0 ) { + tempBuffer = new unsigned char[len + internalBufferLength]; + memset( tempBuffer, 0, len + internalBufferLength); + if( !tempBuffer ) { + throw Exception( __FILE__, __LINE__, "could not allocate temp buffer"); + } + memcpy( tempBuffer, internalBuffer, internalBufferLength); + memcpy( tempBuffer+internalBufferLength, buf, len); + b = tempBuffer; + bytesToProcess += internalBufferLength; + } + + + while ( bytesToProcess / resampleRatio >= 480 * sampleSize ) { + unsigned int toProcess = bytesToProcess / (resampleRatio * sampleSize); + if( toProcess >= 480 / resampleRatio) { + processed = 480 / resampleRatio; + } + + int opusBufferSize = (1275*3+7)*channels; + unsigned char* opusBuffer = new unsigned char[opusBufferSize]; + + // convert the byte-based raw input into a short buffer + // with channels still interleaved + unsigned int totalSamples = processed * channels; + short int * shortBuffer = new short int[totalSamples]; + + Util::conv( bitsPerSample, b, processed*sampleSize, shortBuffer, isInBigEndian()); + + if ( converter && processed > 0 ) { + // resample if needed + int inCount = processed; + int outCount = 480; //(int) (inCount * resampleRatio); + short int * resampledBuffer = new short int[(outCount+1)* channels]; + int converted; +#ifdef HAVE_SRC_LIB + (void)inCount; + converterData.input_frames = processed; + src_short_to_float_array (shortBuffer, converterData.data_in, totalSamples); + int srcError = src_process (converter, &converterData); + if (srcError) + throw Exception (__FILE__, __LINE__, "libsamplerate error: ", src_strerror (srcError)); + converted = converterData.output_frames_gen; + + src_float_to_short_array(converterData.data_out, resampledBuffer, converted*channels); + +#else + converted = converter->resample( inCount, + outCount, + shortBuffer, + resampledBuffer ); +#endif + if( converted != 480) { + throw Exception( __FILE__, __LINE__, "resampler error: expected 480 samples", converted); + } + int encBytes = opus_encode( opusEncoder, resampledBuffer, 480, opusBuffer, opusBufferSize); + if( encBytes == -1 ) { + throw Exception( __FILE__, __LINE__, "opus encoder error"); + } + oggGranulePosition += converted; + opusBlocksOut( encBytes, opusBuffer); + + delete[] resampledBuffer; + + } else if( processed > 0) { + memset( opusBuffer, 0, opusBufferSize); + int encBytes = opus_encode( opusEncoder, shortBuffer, processed, opusBuffer, opusBufferSize); + if( encBytes == -1 ) { + throw Exception( __FILE__, __LINE__, "opus encoder error"); + } + oggGranulePosition += processed; + opusBlocksOut( encBytes, opusBuffer); + + } + delete[] shortBuffer; + delete[] opusBuffer; + bytesToProcess -= processed * sampleSize; + totalProcessed += processed * sampleSize; + b = ((unsigned char*)b) + (processed * sampleSize); + } + + int newLen = len - (len % sampleSize) + internalBufferLength - totalProcessed; + newLen -= newLen % sampleSize; + + if( newLen > 0 ) { + memcpy( internalBuffer, b, newLen); + totalProcessed += newLen; + internalBufferLength = newLen; + } else { + internalBufferLength = 0; + } + + if( tempBuffer ) { + delete[] tempBuffer; + tempBuffer = NULL; + } + + return totalProcessed; +} + + +/*------------------------------------------------------------------------------ + * Flush the data from the encoder + *----------------------------------------------------------------------------*/ +void +OpusLibEncoder :: flush ( void ) + throw ( Exception ) +{ + if ( !isOpen() ) { + return; + } + + int opusBufferSize = (1275*3+7)*getOutChannel(); + unsigned char * opusBuffer = new unsigned char[opusBufferSize]; + short int * shortBuffer = new short int[480]; + + // Send an empty audio packet along to flush out the stream. + memset( shortBuffer, 0, 480); + memset( opusBuffer, 0, opusBufferSize); + int encBytes = opus_encode( opusEncoder, shortBuffer, 480, opusBuffer, opusBufferSize); + if( encBytes == -1 ) { + throw Exception( __FILE__, __LINE__, "opus encoder error"); + } + oggGranulePosition += 480; + + // Send the empty block to the Ogg layer, and mark the + // EOS flag. This will trigger any remaining packets to be + // sent. + opusBlocksOut( encBytes, opusBuffer, true); + delete[] opusBuffer; + delete[] shortBuffer; + getSink()->flush(); +} + + +/*------------------------------------------------------------------------------ + * Send pending Opus blocks to the underlying stream + *----------------------------------------------------------------------------*/ +void +OpusLibEncoder :: opusBlocksOut ( int bytes, + unsigned char* data, + bool eos ) throw ( Exception ) +{ + ogg_packet oggPacket; + ogg_page oggPage; + + oggPacket.packet = data; + oggPacket.bytes = bytes; + oggPacket.b_o_s = 0; + oggPacket.e_o_s = ( eos ) ? 1 : 0; + oggPacket.granulepos = oggGranulePosition; + oggPacket.packetno = oggPacketNumber; + oggPacketNumber++; + + if( ogg_stream_packetin( &oggStreamState, &oggPacket) == 0) { + while( ogg_stream_pageout( &oggStreamState, &oggPage) || + ( eos && ogg_stream_flush( &oggStreamState, &oggPage) ) ) { + int written; + + written = getSink()->write(oggPage.header, oggPage.header_len); + written += getSink()->write( oggPage.body, oggPage.body_len); + + if ( written < oggPage.header_len + oggPage.body_len ) { + // just let go data that could not be written + reportEvent( 2, + "couldn't write full opus data to underlying sink", + oggPage.header_len + oggPage.body_len - written); + } + } + } else { + throw Exception( __FILE__, __LINE__, "internal ogg error"); + } +} + + +/*------------------------------------------------------------------------------ + * Close the encoding session + *----------------------------------------------------------------------------*/ +void +OpusLibEncoder :: close ( void ) throw ( Exception ) +{ + if ( isOpen() ) { + flush(); + + ogg_stream_clear( &oggStreamState); + opus_encoder_destroy( opusEncoder); + opusEncoder = NULL; + + encoderOpen = false; + delete[] internalBuffer; + + getSink()->close(); + } +} + + +#endif // HAVE_OPUS_LIB + diff --git a/darkice/trunk/src/OpusLibEncoder.h b/darkice/trunk/src/OpusLibEncoder.h new file mode 100644 index 0000000..86d1cd8 --- /dev/null +++ b/darkice/trunk/src/OpusLibEncoder.h @@ -0,0 +1,561 @@ +/*------------------------------------------------------------------------------ + + Copyright (c) 2000-2007 Tyrell Corporation. All rights reserved. + + Tyrell DarkIce + + File : OpusLibEncoder.h + Version : $Revision$ + Author : $Author$ + Location : $HeadURL$ + + Copyright notice: + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 3 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +------------------------------------------------------------------------------*/ +#ifndef OPUS_LIB_ENCODER_H +#define OPUS_LIB_ENCODER_H + +#ifndef __cplusplus +#error This is a C++ include file +#endif + + +/* ============================================================ include files */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_OPUS_LIB +#include +#include +#else +#error configure for Ogg Opus +#endif + + +#include "Ref.h" +#include "Exception.h" +#include "Reporter.h" +#include "AudioEncoder.h" +#include "CastSink.h" +#ifdef HAVE_SRC_LIB +#include +#else +#include "aflibConverter.h" +#endif + + +/* ================================================================ constants */ + + +/* =================================================================== macros */ + + +/* =============================================================== data types */ + +/** + * A struct for containing the ogg opus header format, sent at the stream + * beginning. + * + * @author $Author$ + * @version $Revision$ + */ +struct OpusIdHeader { + char magic[8]; /* "OpusHead" */ + unsigned char version; /* Version (currently 1) */ + unsigned char channels; /* Number of channels */ + unsigned short preskip; /* Pre-skip */ + unsigned int samplerate; /* Original samplerate; informational only */ + unsigned short gain; /* Output gain, stored in Q7.8 in dB */ + unsigned char chanmap; /* 0 = mono or stereo L/R, 1=vorbis spec order, 2..254=reserved, 255=undef */ + + /** + * Build an Ogg packet from the header. + * @param packet output pointer, must be freed by caller + * + * @return number of bytes in packet. + */ + inline int buildPacket( unsigned char** packet) throw ( Exception ) { + int i = 0; + // FIXME - doesn't support multistream + unsigned char* out = (unsigned char*)malloc(15); + if( out == NULL ) { + throw Exception( __FILE__, __LINE__, "cannot alloc buffer"); + } + for( ; i < 8; i++) { + out[i] = magic[i]; + } + out[i++] = version; + out[i++] = channels; + out[i++] = preskip & 0xff; + out[i++] = (preskip >> 8) & 0xff; + out[i++] = samplerate & 0xff; + out[i++] = (samplerate >> 8) & 0xff; + out[i++] = (samplerate >> 16) & 0xff; + out[i++] = (samplerate >> 24) & 0xff; + out[i++] = gain & 0xff; + out[i++] = (gain >> 8) & 0xff; + out[i++] = chanmap; + *packet = out; + return i; + } +}; + +/** + * A struct for containing the ogg opus comment header format + * + * @author $Author$ + * @version $Revision$ + */ +struct OpusCommentHeader { + /** + * Struct for each tag pair, in the form of a UTF-8 + * string, "TAG=value" + * + * @author $Author$ + * @version $Revision$ + */ + struct Tags { + unsigned int tag_len; /* For each tag, how long the following tag is */ + char* tag_str; /* For each tag, UTF-8 encoded string, in tag=value */ + }; + char magic[8]; /* "OpusTags" */ + unsigned int vendor_length; /* Length of the vendor string */ + char* vendor_string; /* Vendor string -- parsed as utf-8 */ + unsigned int num_tags; /* Number of tags following */ + Tags* tags; /* Pointer to allocated tag array */ + + /** + * Build an Ogg packet from the header. + * @param packet output pointer, must be freed by caller + * + * @return number of bytes in packet. + */ + inline int buildPacket( unsigned char** packet) throw ( Exception ) { + int len = 8 + sizeof(unsigned int) * 2 + vendor_length; + int pos = 0; + for( unsigned int i = 0; i < num_tags; i++ ) + { + len += sizeof(unsigned int) + tags[i].tag_len; + } + unsigned char* out = (unsigned char*)malloc(len); + if( out == NULL ) { + throw Exception( __FILE__, __LINE__, "cannot alloc buffer"); + } + for( ; pos < 8; pos++) { + out[pos] = magic[pos]; + } + out[pos++] = vendor_length & 0xff; + out[pos++] = (vendor_length >> 8) & 0xff; + out[pos++] = (vendor_length >> 16) & 0xff; + out[pos++] = (vendor_length >> 24) & 0xff; + for( unsigned int i = 0; i < vendor_length; i++ ) { + out[pos++] = vendor_string[i]; + } + out[pos++] = num_tags & 0xff; + out[pos++] = (num_tags >> 8) & 0xff; + out[pos++] = (num_tags >> 16) & 0xff; + out[pos++] = (num_tags >> 24) & 0xff; + for( unsigned int i = 0; i < num_tags; i++ ) { + out[pos++] = tags[i].tag_len & 0xff; + out[pos++] = (tags[i].tag_len >> 8) & 0xff; + out[pos++] = (tags[i].tag_len >> 16) & 0xff; + out[pos++] = (tags[i].tag_len >> 24) & 0xff; + for( unsigned int j = 0; j < tags[i].tag_len; j++ ) { + out[pos++] = tags[i].tag_str[j]; + } + } + *packet = out; + return len; + } +}; + +/** + * A class representing the ogg opus encoder linked as a shared object or + * as a static library. + * + * @author $Author$ + * @version $Revision$ + */ +class OpusLibEncoder : public AudioEncoder, public virtual Reporter +{ + private: + + /** + * Value indicating if the encoding process is going on + */ + bool encoderOpen; + + /** + * Ogg Opus library global info + */ + OpusEncoder* opusEncoder; + + /** + * Ogg library global stream state + */ + ogg_stream_state oggStreamState; + + ogg_int64_t oggGranulePosition; + ogg_int64_t oggPacketNumber; + + unsigned char* internalBuffer; + int internalBufferLength; + + /** + * Maximum bitrate of the output in kbits/sec. If 0, don't care. + */ + unsigned int outMaxBitrate; + + /** + * Resample ratio + */ + double resampleRatio; + + /** + * sample rate converter object for possible resampling + */ +#ifdef HAVE_SRC_LIB + SRC_STATE * converter; + SRC_DATA converterData; +#else + aflibConverter * converter; +#endif + + /** + * Initialize the object. + * + * @param the maximum bit rate + * @exception Exception + */ + void + init ( unsigned int outMaxBitrate ) throw ( Exception ); + + /** + * De-initialize the object. + * + * @exception Exception + */ + inline void + strip ( void ) throw ( Exception ) + { + if ( converter ) { +#ifdef HAVE_SRC_LIB + delete [] converterData.data_in; + delete [] converterData.data_out; + src_delete (converter); +#else + delete converter; +#endif + } + } + + /** + * Send pending Opus blocks to the underlying stream + */ + void + opusBlocksOut( int bytes, + unsigned char* data, + bool eos = false ) throw ( Exception ); + + + protected: + + /** + * Default constructor. Always throws an Exception. + * + * @exception Exception + */ + inline + OpusLibEncoder ( void ) throw ( Exception ) + { + throw Exception( __FILE__, __LINE__); + } + + + public: + + /** + * Constructor. + * + * @param sink the sink to send encoded output to + * @param inSampleRate sample rate of the input. + * @param inBitsPerSample number of bits per sample of the input. + * @param inChannel number of channels of the input. + * @param inBigEndian shows if the input is big or little endian + * @param outBitrateMode the bit rate mode of the output. + * @param outBitrate bit rate of the output (kbits/sec). + * @param outQuality the quality of the stream. + * @param outSampleRate sample rate of the output. + * If 0, inSampleRate is used. + * @param outMaxBitrate maximum output bitrate. + * 0 if not used. + * @param outChannel number of channels of the output. + * If 0, inChannel is used. + * @exception Exception + */ + inline + OpusLibEncoder ( CastSink * sink, + unsigned int inSampleRate, + unsigned int inBitsPerSample, + unsigned int inChannel, + bool inBigEndian, + BitrateMode outBitrateMode, + unsigned int outBitrate, + double outQuality, + unsigned int outSampleRate = 0, + unsigned int outChannel = 0, + unsigned int outMaxBitrate = 0 ) + throw ( Exception ) + + : AudioEncoder ( sink, + inSampleRate, + inBitsPerSample, + inChannel, + inBigEndian, + outBitrateMode, + outBitrate, + outQuality, + outSampleRate, + outChannel ) + { + init( outMaxBitrate); + } + + /** + * Constructor. + * + * @param sink the sink to send encoded output to + * @param as get input sample rate, bits per sample and channels + * from this AudioSource. + * @param outBitrateMode the bit rate mode of the output. + * @param outBitrate bit rate of the output (kbits/sec). + * @param outQuality the quality of the stream. + * @param outSampleRate sample rate of the output. + * If 0, input sample rate is used. + * @param outMaxBitrate maximum output bitrate. + * 0 if not used. + * @param outChannel number of channels of the output. + * If 0, input channel is used. + * @exception Exception + */ + inline + OpusLibEncoder ( CastSink * sink, + const AudioSource * as, + BitrateMode outBitrateMode, + unsigned int outBitrate, + double outQuality, + unsigned int outSampleRate = 0, + unsigned int outChannel = 0, + unsigned int outMaxBitrate = 0 ) + throw ( Exception ) + + : AudioEncoder ( sink, + as, + outBitrateMode, + outBitrate, + outQuality, + outSampleRate, + outChannel ) + { + init( outMaxBitrate); + } + + /** + * Copy constructor. + * + * @param encoder the OpusLibEncoder to copy. + */ + inline + OpusLibEncoder ( const OpusLibEncoder & encoder ) + throw ( Exception ) + : AudioEncoder( encoder ) + { + if( encoder.isOpen() ) { + throw Exception(__FILE__, __LINE__, "don't copy open encoders"); + } + init( encoder.getOutMaxBitrate() ); + } + + /** + * Destructor. + * + * @exception Exception + */ + inline virtual + ~OpusLibEncoder ( void ) throw ( Exception ) + { + if ( isOpen() ) { + close(); + } + strip(); + } + + /** + * Assignment operator. + * + * @param encoder the OpusLibEncoder to assign this to. + * @return a reference to this OpusLibEncoder. + * @exception Exception + */ + inline virtual OpusLibEncoder & + operator= ( const OpusLibEncoder & encoder ) throw ( Exception ) + { + if( encoder.isOpen() ) { + throw Exception(__FILE__, __LINE__, "don't copy open encoders"); + } + + if ( this != &encoder ) { + strip(); + AudioEncoder::operator=( encoder); + init( encoder.getOutMaxBitrate() ); + } + + return *this; + } + + /** + * Get the maximum bit rate of the output in kbits/sec, + * for fixed / average bitrate encodings. + * + * @return the maximum bit rate of the output, or 0 if not set. + */ + inline unsigned int + getOutMaxBitrate ( void ) const throw () + { + return outMaxBitrate; + } + + /** + * Check wether encoding is in progress. + * + * @return true if encoding is in progress, false otherwise. + */ + inline virtual bool + isRunning ( void ) const throw () + { + return isOpen(); + } + + /** + * Start encoding. This function returns as soon as possible, + * with encoding started in the background. + * + * @return true if encoding has started, false otherwise. + * @exception Exception + */ + inline virtual bool + start ( void ) throw ( Exception ) + { + return open(); + } + + /** + * Stop encoding. Stops the encoding running in the background. + * + * @exception Exception + */ + inline virtual void + stop ( void ) throw ( Exception ) + { + return close(); + } + + /** + * Open an encoding session. + * + * @return true if opening was successfull, false otherwise. + * @exception Exception + */ + virtual bool + open ( void ) throw ( Exception ); + + /** + * Check if the encoding session is open. + * + * @return true if the encoding session is open, false otherwise. + */ + inline virtual bool + isOpen ( void ) const throw () + { + return encoderOpen; + } + + /** + * Check if the encoder is ready to accept data. + * + * @param sec the maximum seconds to block. + * @param usec micro seconds to block after the full seconds. + * @return true if the encoder is ready to accept data, + * false otherwise. + * @exception Exception + */ + inline virtual bool + canWrite ( unsigned int sec, + unsigned int usec ) throw ( Exception ) + { + if ( !isOpen() ) { + return false; + } + + return getSink()->canWrite(sec, usec); + } + + /** + * Write data to the encoder. + * Buf is expected to be a sequence of big-endian 16 bit values, + * with left and right channels interleaved. Len is the number of + * bytes, must be a multiple of 4. + * + * @param buf the data to write. + * @param len number of bytes to write from buf. + * @return the number of bytes written (may be less than len). + * @exception Exception + */ + virtual unsigned int + write ( const void * buf, + unsigned int len ) throw ( Exception ); + + /** + * Flush all data that was written to the encoder to the underlying + * connection. + * + * @exception Exception + */ + virtual void + flush ( void ) throw ( Exception ); + + /** + * Close the encoding session. + * + * @exception Exception + */ + virtual void + close ( void ) throw ( Exception ); +}; + + +/* ================================================= external data structures */ + + +/* ====================================================== function prototypes */ + + + +#endif /* OPUS_LIB_ENCODER_H */ +