/* ** 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" #include "meow.h" #define OV_EXCLUDE_STATIC_CALLBACKS #include #include #include static size_t vfRead(void *ptr, size_t size, size_t nmemb, void *datasource) { return SDL_ReadIO(static_cast(datasource), ptr, size); } static int vfSeek(void *datasource, ogg_int64_t offset, int whence) { return SDL_SeekIO(static_cast(datasource), offset, (SDL_IOWhence)whence); } static long vfTell(void *datasource) { return SDL_TellIO(static_cast(datasource)); } static ov_callbacks OvCallbacks ={ vfRead, vfSeek, 0, vfTell }; struct VorbisSource : ALDataSource{ SDL_IOStream &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; VorbisSource(SDL_IOStream &ops, bool looped) : src(ops), currentFrame(0) { int error = ov_open_callbacks(&src, &vf, 0, 0, OvCallbacks); if (error){ SDL_CloseIO(&src); crash(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_CloseIO(&src); crash(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_CloseIO(&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(); int 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; } if (retStatus != ALDataSource::Error) AL::Buffer::uploadData(alBuffer, info.alFormat, sampleBuf.data(), bufUsed*sizeof(int16_t), info.rate); return retStatus; } uint32_t loopStartFrames(){ if (loop.valid) return loop.start; else return 0; } bool setPitch(float){ return false; } }; ALDataSource *createVorbisSource(SDL_IOStream &ops, bool looped){ return new VorbisSource(ops, looped); }