From 3dd5c5d86249654b4cb981fa5f2e36c7c4551a43 Mon Sep 17 00:00:00 2001 From: Zack Middleton Date: Sun, 17 Feb 2013 18:32:05 -0600 Subject: [PATCH] Add Ogg Opus support --- Makefile | 184 ++++++++++++++ code/client/snd_codec.c | 4 + code/client/snd_codec.h | 9 + code/client/snd_codec_opus.c | 452 +++++++++++++++++++++++++++++++++++ 4 files changed, 649 insertions(+) create mode 100644 code/client/snd_codec_opus.c diff --git a/Makefile b/Makefile index ea2682a2..a0a61293 100644 --- a/Makefile +++ b/Makefile @@ -165,6 +165,10 @@ ifndef USE_CODEC_VORBIS USE_CODEC_VORBIS=0 endif +ifndef USE_CODEC_OPUS +USE_CODEC_OPUS=1 +endif + ifndef USE_MUMBLE USE_MUMBLE=1 endif @@ -181,6 +185,10 @@ ifndef USE_INTERNAL_SPEEX USE_INTERNAL_SPEEX=1 endif +ifndef USE_INTERNAL_OPUS +USE_INTERNAL_OPUS=1 +endif + ifndef USE_INTERNAL_ZLIB USE_INTERNAL_ZLIB=1 endif @@ -226,6 +234,8 @@ UIDIR=$(MOUNT_DIR)/ui Q3UIDIR=$(MOUNT_DIR)/q3_ui JPDIR=$(MOUNT_DIR)/jpeg-8c SPEEXDIR=$(MOUNT_DIR)/libspeex +OPUSDIR=$(MOUNT_DIR)/opus-1.0.2 +OPUSFILEDIR=$(MOUNT_DIR)/opusfile-0.2 ZDIR=$(MOUNT_DIR)/zlib Q3ASMDIR=$(MOUNT_DIR)/tools/asm LBURGDIR=$(MOUNT_DIR)/tools/lcc/lburg @@ -914,6 +924,19 @@ ifeq ($(USE_CODEC_VORBIS),1) CLIENT_CFLAGS += -DUSE_CODEC_VORBIS endif +ifeq ($(USE_CODEC_OPUS),1) + CLIENT_CFLAGS += -DUSE_CODEC_OPUS + ifeq ($(USE_INTERNAL_OPUS),1) + CLIENT_CFLAGS += -DOPUS_BUILD -DFLOATING_POINT -DUSE_ALLOCA \ + -I$(OPUSDIR)/include -I$(OPUSDIR)/celt -I$(OPUSDIR)/silk \ + -I$(OPUSDIR)/silk/float + + CLIENT_CFLAGS += -I$(OPUSFILEDIR)/include + else + CLIENT_LIBS += -lopusfile -lopus -logg + endif +endif + ifeq ($(USE_RENDERER_DLOPEN),1) CLIENT_CFLAGS += -DUSE_RENDERER_DLOPEN endif @@ -1166,6 +1189,7 @@ makedirs: @if [ ! -d $(BUILD_DIR) ];then $(MKDIR) $(BUILD_DIR);fi @if [ ! -d $(B) ];then $(MKDIR) $(B);fi @if [ ! -d $(B)/client ];then $(MKDIR) $(B)/client;fi + @if [ ! -d $(B)/client/opus ];then $(MKDIR) $(B)/client/opus;fi @if [ ! -d $(B)/renderergl1 ];then $(MKDIR) $(B)/renderergl1;fi @if [ ! -d $(B)/renderergl2 ];then $(MKDIR) $(B)/renderergl2;fi @if [ ! -d $(B)/renderergl2/glsl ];then $(MKDIR) $(B)/renderergl2/glsl;fi @@ -1405,6 +1429,7 @@ Q3OBJ = \ $(B)/client/snd_codec.o \ $(B)/client/snd_codec_wav.o \ $(B)/client/snd_codec_ogg.o \ + $(B)/client/snd_codec_opus.o \ \ $(B)/client/qal.o \ $(B)/client/snd_openal.o \ @@ -1717,6 +1742,149 @@ Q3OBJ += \ endif endif +ifeq ($(USE_CODEC_OPUS),1) +ifeq ($(USE_INTERNAL_OPUS),1) +Q3OBJ += \ + $(B)/client/opus/opus.o \ + $(B)/client/opus/opus_decoder.o \ + $(B)/client/opus/opus_encoder.o \ + $(B)/client/opus/opus_multistream.o \ + $(B)/client/opus/repacketizer.o \ + \ + $(B)/client/opus/bands.o \ + $(B)/client/opus/celt.o \ + $(B)/client/opus/cwrs.o \ + $(B)/client/opus/entcode.o \ + $(B)/client/opus/entdec.o \ + $(B)/client/opus/entenc.o \ + $(B)/client/opus/kiss_fft.o \ + $(B)/client/opus/laplace.o \ + $(B)/client/opus/mathops.o \ + $(B)/client/opus/mdct.o \ + $(B)/client/opus/modes.o \ + $(B)/client/opus/pitch.o \ + $(B)/client/opus/celt_lpc.o \ + $(B)/client/opus/quant_bands.o \ + $(B)/client/opus/rate.o \ + $(B)/client/opus/vq.o \ + \ + $(B)/client/opus/CNG.o \ + $(B)/client/opus/code_signs.o \ + $(B)/client/opus/init_decoder.o \ + $(B)/client/opus/decode_core.o \ + $(B)/client/opus/decode_frame.o \ + $(B)/client/opus/decode_parameters.o \ + $(B)/client/opus/decode_indices.o \ + $(B)/client/opus/decode_pulses.o \ + $(B)/client/opus/decoder_set_fs.o \ + $(B)/client/opus/dec_API.o \ + $(B)/client/opus/enc_API.o \ + $(B)/client/opus/encode_indices.o \ + $(B)/client/opus/encode_pulses.o \ + $(B)/client/opus/gain_quant.o \ + $(B)/client/opus/interpolate.o \ + $(B)/client/opus/LP_variable_cutoff.o \ + $(B)/client/opus/NLSF_decode.o \ + $(B)/client/opus/NSQ.o \ + $(B)/client/opus/NSQ_del_dec.o \ + $(B)/client/opus/PLC.o \ + $(B)/client/opus/shell_coder.o \ + $(B)/client/opus/tables_gain.o \ + $(B)/client/opus/tables_LTP.o \ + $(B)/client/opus/tables_NLSF_CB_NB_MB.o \ + $(B)/client/opus/tables_NLSF_CB_WB.o \ + $(B)/client/opus/tables_other.o \ + $(B)/client/opus/tables_pitch_lag.o \ + $(B)/client/opus/tables_pulses_per_block.o \ + $(B)/client/opus/VAD.o \ + $(B)/client/opus/control_audio_bandwidth.o \ + $(B)/client/opus/quant_LTP_gains.o \ + $(B)/client/opus/VQ_WMat_EC.o \ + $(B)/client/opus/HP_variable_cutoff.o \ + $(B)/client/opus/NLSF_encode.o \ + $(B)/client/opus/NLSF_VQ.o \ + $(B)/client/opus/NLSF_unpack.o \ + $(B)/client/opus/NLSF_del_dec_quant.o \ + $(B)/client/opus/process_NLSFs.o \ + $(B)/client/opus/stereo_LR_to_MS.o \ + $(B)/client/opus/stereo_MS_to_LR.o \ + $(B)/client/opus/check_control_input.o \ + $(B)/client/opus/control_SNR.o \ + $(B)/client/opus/init_encoder.o \ + $(B)/client/opus/control_codec.o \ + $(B)/client/opus/A2NLSF.o \ + $(B)/client/opus/ana_filt_bank_1.o \ + $(B)/client/opus/biquad_alt.o \ + $(B)/client/opus/bwexpander_32.o \ + $(B)/client/opus/bwexpander.o \ + $(B)/client/opus/debug.o \ + $(B)/client/opus/decode_pitch.o \ + $(B)/client/opus/inner_prod_aligned.o \ + $(B)/client/opus/lin2log.o \ + $(B)/client/opus/log2lin.o \ + $(B)/client/opus/LPC_analysis_filter.o \ + $(B)/client/opus/LPC_inv_pred_gain.o \ + $(B)/client/opus/table_LSF_cos.o \ + $(B)/client/opus/NLSF2A.o \ + $(B)/client/opus/NLSF_stabilize.o \ + $(B)/client/opus/NLSF_VQ_weights_laroia.o \ + $(B)/client/opus/pitch_est_tables.o \ + $(B)/client/opus/resampler.o \ + $(B)/client/opus/resampler_down2_3.o \ + $(B)/client/opus/resampler_down2.o \ + $(B)/client/opus/resampler_private_AR2.o \ + $(B)/client/opus/resampler_private_down_FIR.o \ + $(B)/client/opus/resampler_private_IIR_FIR.o \ + $(B)/client/opus/resampler_private_up2_HQ.o \ + $(B)/client/opus/resampler_rom.o \ + $(B)/client/opus/sigm_Q15.o \ + $(B)/client/opus/sort.o \ + $(B)/client/opus/sum_sqr_shift.o \ + $(B)/client/opus/stereo_decode_pred.o \ + $(B)/client/opus/stereo_encode_pred.o \ + $(B)/client/opus/stereo_find_predictor.o \ + $(B)/client/opus/stereo_quant_pred.o \ + \ + $(B)/client/opus/apply_sine_window_FLP.o \ + $(B)/client/opus/corrMatrix_FLP.o \ + $(B)/client/opus/encode_frame_FLP.o \ + $(B)/client/opus/find_LPC_FLP.o \ + $(B)/client/opus/find_LTP_FLP.o \ + $(B)/client/opus/find_pitch_lags_FLP.o \ + $(B)/client/opus/find_pred_coefs_FLP.o \ + $(B)/client/opus/LPC_analysis_filter_FLP.o \ + $(B)/client/opus/LTP_analysis_filter_FLP.o \ + $(B)/client/opus/LTP_scale_ctrl_FLP.o \ + $(B)/client/opus/noise_shape_analysis_FLP.o \ + $(B)/client/opus/prefilter_FLP.o \ + $(B)/client/opus/process_gains_FLP.o \ + $(B)/client/opus/regularize_correlations_FLP.o \ + $(B)/client/opus/residual_energy_FLP.o \ + $(B)/client/opus/solve_LS_FLP.o \ + $(B)/client/opus/warped_autocorrelation_FLP.o \ + $(B)/client/opus/wrappers_FLP.o \ + $(B)/client/opus/autocorrelation_FLP.o \ + $(B)/client/opus/burg_modified_FLP.o \ + $(B)/client/opus/bwexpander_FLP.o \ + $(B)/client/opus/energy_FLP.o \ + $(B)/client/opus/inner_product_FLP.o \ + $(B)/client/opus/k2a_FLP.o \ + $(B)/client/opus/levinsondurbin_FLP.o \ + $(B)/client/opus/LPC_inv_pred_gain_FLP.o \ + $(B)/client/opus/pitch_analysis_core_FLP.o \ + $(B)/client/opus/scale_copy_vector_FLP.o \ + $(B)/client/opus/scale_vector_FLP.o \ + $(B)/client/opus/schur_FLP.o \ + $(B)/client/opus/sort_FLP.o \ + \ + $(B)/client/http.o \ + $(B)/client/info.o \ + $(B)/client/internal.o \ + $(B)/client/opusfile.o \ + $(B)/client/stream.o +endif +endif + ifeq ($(USE_INTERNAL_ZLIB),1) Q3OBJ += \ $(B)/client/adler32.o \ @@ -2331,6 +2499,21 @@ $(B)/client/%.o: $(BLIBDIR)/%.c $(B)/client/%.o: $(SPEEXDIR)/%.c $(DO_CC) +$(B)/client/opus/%.o: $(OPUSDIR)/src/%.c + $(DO_CC) + +$(B)/client/opus/%.o: $(OPUSDIR)/celt/%.c + $(DO_CC) + +$(B)/client/opus/%.o: $(OPUSDIR)/silk/%.c + $(DO_CC) + +$(B)/client/opus/%.o: $(OPUSDIR)/silk/float/%.c + $(DO_CC) + +$(B)/client/%.o: $(OPUSFILEDIR)/src/%.c + $(DO_CC) + $(B)/client/%.o: $(ZDIR)/%.c $(DO_CC) @@ -2595,6 +2778,7 @@ ifeq ($(PLATFORM),mingw32) USE_RENDERER_DLOPEN=$(USE_RENDERER_DLOPEN) \ USE_OPENAL_DLOPEN=$(USE_OPENAL_DLOPEN) \ USE_CURL_DLOPEN=$(USE_CURL_DLOPEN) \ + USE_INTERNAL_OPUS=$(USE_INTERNAL_OPUS) \ USE_INTERNAL_SPEEX=$(USE_INTERNAL_SPEEX) \ USE_INTERNAL_ZLIB=$(USE_INTERNAL_ZLIB) \ USE_INTERNAL_JPEG=$(USE_INTERNAL_JPEG) diff --git a/code/client/snd_codec.c b/code/client/snd_codec.c index ddddb71a..b1923277 100644 --- a/code/client/snd_codec.c +++ b/code/client/snd_codec.c @@ -124,6 +124,10 @@ void S_CodecInit() { codecs = NULL; +#ifdef USE_CODEC_OPUS + S_CodecRegister(&opus_codec); +#endif + #ifdef USE_CODEC_VORBIS S_CodecRegister(&ogg_codec); #endif diff --git a/code/client/snd_codec.h b/code/client/snd_codec.h index 03fcaa25..5ab1202f 100644 --- a/code/client/snd_codec.h +++ b/code/client/snd_codec.h @@ -95,4 +95,13 @@ void S_OGG_CodecCloseStream(snd_stream_t *stream); int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); #endif // USE_CODEC_VORBIS +// Ogg Opus codec +#ifdef USE_CODEC_OPUS +extern snd_codec_t opus_codec; +void *S_OggOpus_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_OggOpus_CodecOpenStream(const char *filename); +void S_OggOpus_CodecCloseStream(snd_stream_t *stream); +int S_OggOpus_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); +#endif // USE_CODEC_OPUS + #endif // !_SND_CODEC_H_ diff --git a/code/client/snd_codec_opus.c b/code/client/snd_codec_opus.c new file mode 100644 index 00000000..5ac9d4a9 --- /dev/null +++ b/code/client/snd_codec_opus.c @@ -0,0 +1,452 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2005-2006 Joerg Dietrich + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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 2 of the License, +or (at your option) any later version. + +Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// Ogg Opus support is enabled by this define +#ifdef USE_CODEC_OPUS + +// includes for the Q3 sound system +#include "client.h" +#include "snd_codec.h" + +// includes for the Ogg Opus codec +#include +#include + +// samples are 16 bit +#define OPUS_SAMPLEWIDTH 2 + +// Q3 Ogg Opus codec +snd_codec_t opus_codec = +{ + "opus", + S_OggOpus_CodecLoad, + S_OggOpus_CodecOpenStream, + S_OggOpus_CodecReadStream, + S_OggOpus_CodecCloseStream, + NULL +}; + +// callbacks for opusfile + +// fread() replacement +int S_OggOpus_Callback_read(void *datasource, unsigned char *ptr, int size ) +{ + snd_stream_t *stream; + int bytesRead = 0; + + // check if input is valid + if(!ptr) + { + errno = EFAULT; + return -1; + } + + if(!size) + { + // It's not an error, caller just wants zero bytes! + errno = 0; + return 0; + } + + if (size < 0) + { + errno = EINVAL; + return -1; + } + + if(!datasource) + { + errno = EBADF; + return -1; + } + + // we use a snd_stream_t in the generic pointer to pass around + stream = (snd_stream_t *) datasource; + + // read it with the Q3 function FS_Read() + bytesRead = FS_Read(ptr, size, stream->file); + + // update the file position + stream->pos += bytesRead; + + return bytesRead; +} + +// fseek() replacement +int S_OggOpus_Callback_seek(void *datasource, opus_int64 offset, int whence) +{ + snd_stream_t *stream; + int retVal = 0; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + // we must map the whence to its Q3 counterpart + switch(whence) + { + case SEEK_SET : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_SET); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = (int) offset; + break; + } + + case SEEK_CUR : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_CUR); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos += (int) offset; + break; + } + + case SEEK_END : + { + // Quake 3 seems to have trouble with FS_SEEK_END + // so we use the file length and FS_SEEK_SET + + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) stream->length + (long) offset, FS_SEEK_SET); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = stream->length + (int) offset; + break; + } + + default : + { + // unknown whence, so we return an error + errno = EINVAL; + return -1; + } + } + + // stream->pos shouldn't be smaller than zero or bigger than the filesize + stream->pos = (stream->pos < 0) ? 0 : stream->pos; + stream->pos = (stream->pos > stream->length) ? stream->length : stream->pos; + + return 0; +} + +// fclose() replacement +int S_OggOpus_Callback_close(void *datasource) +{ + // we do nothing here and close all things manually in S_OggOpus_CodecCloseStream() + return 0; +} + +// ftell() replacement +opus_int64 S_OggOpus_Callback_tell(void *datasource) +{ + snd_stream_t *stream; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + return (opus_int64) FS_FTell(stream->file); +} + +// the callback structure +const OpusFileCallbacks S_OggOpus_Callbacks = +{ + &S_OggOpus_Callback_read, + &S_OggOpus_Callback_seek, + &S_OggOpus_Callback_tell, + &S_OggOpus_Callback_close +}; + +/* +================= +S_OggOpus_CodecOpenStream +================= +*/ +snd_stream_t *S_OggOpus_CodecOpenStream(const char *filename) +{ + snd_stream_t *stream; + + // Opus codec control structure + OggOpusFile *of; + + // some variables used to get informations about the file + const OpusHead *opusInfo; + ogg_int64_t numSamples; + + // check if input is valid + if(!filename) + { + return NULL; + } + + // Open the stream + stream = S_CodecUtilOpen(filename, &opus_codec); + if(!stream) + { + return NULL; + } + + // open the codec with our callbacks and stream as the generic pointer + of = op_open_callbacks(stream, &S_OggOpus_Callbacks, NULL, 0, NULL ); + if (!of) + { + S_CodecUtilClose(&stream); + + return NULL; + } + + // the stream must be seekable + if(!op_seekable(of)) + { + op_free(of); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // get the info about channels and rate + opusInfo = op_head(of, -1); + if(!opusInfo) + { + op_free(of); + + S_CodecUtilClose(&stream); + + return NULL; + } + + if(opusInfo->stream_count != 1) + { + op_free(of); + + S_CodecUtilClose(&stream); + + Com_Printf("Only Ogg Opus files with one stream are support\n"); + return NULL; + } + + if(opusInfo->channel_count != 1 && opusInfo->channel_count != 2) + { + op_free(of); + + S_CodecUtilClose(&stream); + + Com_Printf("Only mono and stereo Ogg Opus files are supported\n"); + return NULL; + } + + // get the number of sample-frames in the file + numSamples = op_pcm_total(of, -1); + + // fill in the info-structure in the stream + stream->info.rate = 48000; + stream->info.width = OPUS_SAMPLEWIDTH; + stream->info.channels = opusInfo->channel_count; + stream->info.samples = numSamples; + stream->info.size = stream->info.samples * stream->info.channels * stream->info.width; + stream->info.dataofs = 0; + + // We use stream->pos for the file pointer in the compressed ogg file + stream->pos = 0; + + // We use the generic pointer in stream for the opus codec control structure + stream->ptr = of; + + return stream; +} + +/* +================= +S_OggOpus_CodecCloseStream +================= +*/ +void S_OggOpus_CodecCloseStream(snd_stream_t *stream) +{ + // check if input is valid + if(!stream) + { + return; + } + + // let the opus codec cleanup its stuff + op_free((OggOpusFile *) stream->ptr); + + // close the stream + S_CodecUtilClose(&stream); +} + +/* +================= +S_OggOpus_CodecReadStream +================= +*/ +int S_OggOpus_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + // buffer handling + int samplesRead, samplesLeft, c; + opus_int16 *bufPtr; + + // check if input is valid + if(!(stream && buffer)) + { + return 0; + } + + if(bytes <= 0) + { + return 0; + } + + samplesRead = 0; + samplesLeft = bytes / stream->info.channels / stream->info.width; + bufPtr = buffer; + + if(samplesLeft <= 0) + { + return 0; + } + + // cycle until we have the requested or all available bytes read + while(-1) + { + // read some samples from the opus codec + c = op_read((OggOpusFile *) stream->ptr, bufPtr + samplesRead * stream->info.channels, samplesLeft * stream->info.channels, NULL); + + // no more samples are left + if(c <= 0) + { + break; + } + + samplesRead += c; + samplesLeft -= c; + + // we have enough samples + if(samplesLeft <= 0) + { + break; + } + } + + return samplesRead * stream->info.channels * stream->info.width; +} + +/* +===================================================================== +S_OggOpus_CodecLoad + +We handle S_OggOpus_CodecLoad as a special case of the streaming functions +where we read the whole stream at once. +====================================================================== +*/ +void *S_OggOpus_CodecLoad(const char *filename, snd_info_t *info) +{ + snd_stream_t *stream; + byte *buffer; + int bytesRead; + + // check if input is valid + if(!(filename && info)) + { + return NULL; + } + + // open the file as a stream + stream = S_OggOpus_CodecOpenStream(filename); + if(!stream) + { + return NULL; + } + + // copy over the info + info->rate = stream->info.rate; + info->width = stream->info.width; + info->channels = stream->info.channels; + info->samples = stream->info.samples; + info->size = stream->info.size; + info->dataofs = stream->info.dataofs; + + // allocate a buffer + // this buffer must be free-ed by the caller of this function + buffer = Hunk_AllocateTempMemory(info->size); + if(!buffer) + { + S_OggOpus_CodecCloseStream(stream); + + return NULL; + } + + // fill the buffer + bytesRead = S_OggOpus_CodecReadStream(stream, info->size, buffer); + + // we don't even have read a single byte + if(bytesRead <= 0) + { + Hunk_FreeTempMemory(buffer); + S_OggOpus_CodecCloseStream(stream); + + return NULL; + } + + S_OggOpus_CodecCloseStream(stream); + + return buffer; +} + +#endif // USE_CODEC_VORBIS