/* ** vorbissource.cpp ** ** This file is part of mkxp. ** ** Copyright (C) 2014 Jonas Kulla ** ** mkxp 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. ** ** mkxp 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 mkxp. If not, see . */ #include "aldatasource.h" #include "exception.h" #define OV_EXCLUDE_STATIC_CALLBACKS #include #include #include static size_t vfRead(void *ptr, size_t size, size_t nmemb, void *ops) { return SDL_RWread(static_cast(ops), ptr, size, nmemb); } static int vfSeek(void *ops, ogg_int64_t offset, int whence) { return SDL_RWseek(static_cast(ops), offset, whence); } static long vfTell(void *ops) { return SDL_RWtell(static_cast(ops)); } static ov_callbacks OvCallbacks = { vfRead, vfSeek, 0, vfTell }; struct VorbisSource : ALDataSource { SDL_RWops &src; OggVorbis_File vf; uint32_t currentFrame; struct { uint32_t start; uint32_t length; uint32_t end; bool valid; bool requested; } loop; struct { int channels; int rate; int frameSize; ALenum alFormat; } info; std::vector sampleBuf; int bufUsed = 0; bool readFull = false; VorbisSource(SDL_RWops &ops, bool looped) : src(ops), currentFrame(0) { int error = ov_open_callbacks(&src, &vf, 0, 0, OvCallbacks); if (error) { SDL_RWclose(&src); throw Exception(Exception::MKXPError, "Vorbisfile: Cannot read ogg file"); } /* Extract bitstream info */ info.channels = vf.vi->channels; info.rate = vf.vi->rate; if (info.channels > 2) { ov_clear(&vf); SDL_RWclose(&src); throw Exception(Exception::MKXPError, "Cannot handle audio with more than 2 channels"); } info.alFormat = chooseALFormat(sizeof(int16_t), info.channels); info.frameSize = sizeof(int16_t) * info.channels; sampleBuf.resize(STREAM_BUF_SIZE); loop.requested = looped; loop.valid = false; loop.start = loop.length = 0; if (!loop.requested) return; /* Try to extract loop info */ for (int i = 0; i < vf.vc->comments; ++i) { char *comment = vf.vc->user_comments[i]; char *sep = strstr(comment, "="); /* No '=' found */ if (!sep) continue; /* Empty value */ if (!*(sep+1)) continue; *sep = '\0'; if (!strcmp(comment, "LOOPSTART")) loop.start = strtol(sep+1, 0, 10); if (!strcmp(comment, "LOOPLENGTH")) loop.length = strtol(sep+1, 0, 10); *sep = '='; } loop.end = loop.start + loop.length; loop.valid = (loop.start && loop.length); } ~VorbisSource() { ov_clear(&vf); SDL_RWclose(&src); } int sampleRate() { return info.rate; } void seekToOffset(float seconds) { if (seconds <= 0) { ov_raw_seek(&vf, 0); currentFrame = 0; } currentFrame = seconds * info.rate; if (loop.valid && currentFrame > loop.end) currentFrame = loop.start; /* If seeking fails, just seek back to start */ if (ov_pcm_seek(&vf, currentFrame) != 0) ov_raw_seek(&vf, 0); } Status fillBuffer(AL::Buffer::ID alBuffer) { void *bufPtr = sampleBuf.data(); int availBuf = sampleBuf.size(); bufUsed = 0; int canRead = availBuf; Status retStatus = ALDataSource::NoError; bool readAgain = false; if (loop.valid) { int tilLoopEnd = loop.end * info.frameSize; canRead = std::min(availBuf, tilLoopEnd); } while (canRead > 16) { long res = ov_read(&vf, static_cast(bufPtr), canRead, 0, sizeof(int16_t), 1, 0); if (res < 0) { /* Read error */ retStatus = ALDataSource::Error; break; } if (res == 0) { /* EOF */ if (loop.requested) { retStatus = ALDataSource::WrapAround; seekToOffset(0); } else { retStatus = ALDataSource::EndOfStream; } /* If we sought right to the end of the file, * we might be EOF without actually having read * any data at all yet (which mustn't happen), * so we try to continue reading some data. */ if (bufUsed > 0) break; if (readAgain) { /* We're still not getting data though. * Just error out to prevent an endless loop */ retStatus = ALDataSource::Error; break; } readAgain = true; } bufUsed += (res / sizeof(int16_t)); bufPtr = &sampleBuf[bufUsed]; currentFrame += (res / info.frameSize); if (loop.valid && currentFrame >= loop.end) { /* Determine how many frames we're * over the loop end */ int discardFrames = currentFrame - loop.end; bufUsed -= discardFrames * info.channels; retStatus = ALDataSource::WrapAround; /* Seek to loop start */ currentFrame = loop.start; if (ov_pcm_seek(&vf, currentFrame) != 0) retStatus = ALDataSource::Error; break; } canRead -= res; /* Double size if we want to read everything */ if (readFull && !(canRead > 16)) { canRead += sampleBuf.size(); sampleBuf.resize(sampleBuf.size() * 2); } } if (retStatus != ALDataSource::Error) AL::Buffer::uploadData(alBuffer, info.alFormat, sampleBuf.data(), bufUsed*sizeof(int16_t), info.rate); return retStatus; } int fillBufferFull(AL::Buffer::ID alBuffer) { readFull = true; fillBuffer(alBuffer); readFull = false; return bufUsed*sizeof(int16_t); } uint32_t loopStartFrames() { if (loop.valid) return loop.start; else return 0; } bool setPitch(float) { return false; } }; ALDataSource *createVorbisSource(SDL_RWops &ops, bool looped) { return new VorbisSource(ops, looped); }