/* * Ogg/Opus streaming music support, loosely based on several open source * Quake engine based projects with many modifications. * * Copyright (C) 2012-2013 O.Sezer * * 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 2 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "q_defs.h" #if USE_CODEC_OPUS #include "snd_codec.h" #include "snd_codeci.h" #include #include /* CALLBACK FUNCTIONS: */ static int opc_fread(void *f, unsigned char *buf, int size) { int32_t ret; if(size < 0) { errno = EINVAL; return -1; } ret = (int32_t) FS_fread(buf, 1, (size_t)size, (fshandle_t *)f); if(ret == 0 && errno != 0) ret = -1; return ret; } static int opc_fseek(void *f, opus_int64 off, int whence) { if(f == NULL) return -1; return FS_fseek((fshandle_t *)f, (long)off, whence); } static opus_int64 opc_ftell(void *f) { return (opus_int64)FS_ftell(f); } static int opc_fclose(void *f) { (void)f; return 0; /* we fclose() elsewhere. */ } static const OpusFileCallbacks opc_qfs = { .read = opc_fread, .seek = opc_fseek, .tell = opc_ftell, .close = opc_fclose, }; static bool S_OPUS_CodecInitialize(void) { return true; } static void S_OPUS_CodecShutdown(void) { } static bool S_OPUS_CodecOpenStream(snd_stream_t *stream) { OggOpusFile *opFile; const OpusHead *op_info; long numstreams; int32_t res; opFile = op_open_callbacks(&stream->fh, &opc_qfs, NULL, 0, &res); if(!opFile) { Con_Printf("%s is not a valid Opus file (error %" PRIi32 ").\n", stream->name, res); goto fail; } stream->priv = opFile; if(!op_seekable(opFile)) { Con_Printf("Opus stream %s not seekable.\n", stream->name); goto fail; } op_info = op_head(opFile, -1); if(!op_info) { Con_Printf("Unable to get stream information for %s.\n", stream->name); goto fail; } /* FIXME: handle section changes */ numstreams = op_info->stream_count; if(numstreams != 1) { Con_Printf("More than one (%li) stream in %s\n", numstreams, stream->name); goto fail; } if(op_info->channel_count != 1 && op_info->channel_count != 2) { Con_Printf("Unsupported number of channels %" PRIi32 " in %s\n", op_info->channel_count, stream->name); goto fail; } /* All Opus audio is coded at 48 kHz, and should also be decoded * at 48 kHz for playback: info->input_sample_rate only tells us * the sampling rate of the original input before opus encoding. * S_RawSamples() shall already downsample this, as necessary. */ stream->info.rate = 48000; stream->info.channels = op_info->channel_count; /* op_read() yields 16-bit output using native endian ordering: */ stream->info.bits = 16; stream->info.width = 2; return true; fail: if(opFile) op_free(opFile); return false; } static int32_t S_OPUS_CodecReadStream(snd_stream_t *stream, int32_t bytes, void *buffer) { int32_t section; /* FIXME: handle section changes */ int32_t cnt, res, rem; opus_int16 * ptr; rem = bytes / stream->info.width; if(rem / stream->info.channels <= 0) return 0; cnt = 0; ptr = (opus_int16 *) buffer; while(1) { /* op_read() yields 16-bit output using native endian ordering. returns * the number of samples read per channel on success, or a negative value * on failure. */ res = op_read((OggOpusFile *)stream->priv, ptr, rem, §ion); if(res <= 0) break; cnt += res; res *= stream->info.channels; rem -= res; if(rem <= 0) break; ptr += res; } if(res < 0) return res; cnt *= (stream->info.channels * stream->info.width); return cnt; } static void S_OPUS_CodecCloseStream(snd_stream_t *stream) { op_free((OggOpusFile *)stream->priv); S_CodecUtilClose(&stream); } static int32_t S_OPUS_CodecRewindStream(snd_stream_t *stream) { return op_pcm_seek((OggOpusFile *)stream->priv, 0); } snd_codec_t opus_codec = { CODECTYPE_OPUS, true, /* always available. */ "opus", S_OPUS_CodecInitialize, S_OPUS_CodecShutdown, S_OPUS_CodecOpenStream, S_OPUS_CodecReadStream, S_OPUS_CodecRewindStream, S_OPUS_CodecCloseStream, }; #endif /* USE_CODEC_OPUS */