/* Copyright (c) 2013 yvt This file is part of OpenSpades. OpenSpades 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. OpenSpades 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 OpenSpades. If not, see . */ #include #include #include #include "OpusAudioStream.h" #include "Debug.h" #include "Exception.h" #include namespace spades { namespace { std::string stringifyOpusErrorCode(int error) { const char *errorMessage = nullptr; switch (error) { case OP_EREAD: errorMessage = "OP_EREAD"; break; case OP_EFAULT: errorMessage = "OP_EFAULT"; break; case OP_EIMPL: errorMessage = "OP_EIMPL"; break; case OP_EINVAL: errorMessage = "OP_EINVAL"; break; case OP_ENOTFORMAT: errorMessage = "OP_ENOTFORMAT"; break; case OP_EBADHEADER: errorMessage = "OP_EBADHEADER"; break; case OP_EVERSION: errorMessage = "OP_EVERSION"; break; case OP_EBADLINK: errorMessage = "OP_EBADLINK"; break; case OP_EBADTIMESTAMP: errorMessage = "OP_EBADTIMESTAMP"; break; } if (errorMessage) { return errorMessage; } else { return Format("{0}", error); } } } OpusAudioStream::OpusAudioStream(IStream *s, bool ac) : subsamplePosition{0} { SPADES_MARK_FUNCTION(); stream = s; autoClose = ac; opusCallback.reset(new OpusFileCallbacks()); opusCallback->read = [](void *stream, unsigned char *ptr, int nbytes) -> int { auto &self = *reinterpret_cast(stream); return static_cast(self.stream->Read(ptr, static_cast(nbytes))); }; opusCallback->seek = [](void *stream, opus_int64 offset, int whence) -> int { auto &self = *reinterpret_cast(stream); switch (whence) { case SEEK_CUR: self.stream->SetPosition( static_cast(offset + self.stream->GetPosition())); break; case SEEK_SET: self.stream->SetPosition(static_cast(offset)); break; case SEEK_END: self.stream->SetPosition( static_cast(offset + self.stream->GetLength())); break; } return 0; }; opusCallback->tell = [](void *stream) -> opus_int64 { auto &self = *reinterpret_cast(stream); return static_cast(self.stream->GetPosition()); }; opusCallback->close = nullptr; int result = 0; opusFile = op_open_callbacks(this, opusCallback.get(), nullptr, 0, &result); if (result) { SPRaise("op_open_callbacks failed with error code %s", stringifyOpusErrorCode(result).c_str()); } channels = op_channel_count(opusFile, 0); currentSample.resize(channels); SetPosition(0); } OpusAudioStream::~OpusAudioStream() { SPADES_MARK_FUNCTION(); op_free(opusFile); if (autoClose) delete stream; } uint64_t OpusAudioStream::GetLength() { return op_pcm_total(opusFile, 0) * channels * 4; } int OpusAudioStream::GetSamplingFrequency() { return 48000; } int OpusAudioStream::GetNumChannels() { return channels; } OpusAudioStream::SampleFormat OpusAudioStream::GetSampleFormat() { return SampleFormat::SingleFloat; } int OpusAudioStream::ReadByte() { SPADES_MARK_FUNCTION(); if (subsamplePosition == 0) { if (op_pcm_tell(opusFile) >= op_pcm_total(opusFile, 0)) { return -1; } int result = op_read_float(opusFile, currentSample.data(), channels, nullptr); if (result < 0) { SPRaise("op_read_float failed with error code %s", stringifyOpusErrorCode(result).c_str()); } else if (result != 1) { SPRaise("op_read_float returned %d (expected: 1)", result); } } int ret = reinterpret_cast(currentSample.data())[subsamplePosition]; if ((++subsamplePosition) == channels * 4) { subsamplePosition = 0; } return ret; } size_t OpusAudioStream::Read(void *data, size_t bytes) { SPADES_MARK_FUNCTION(); uint64_t maxLen = GetLength() - GetPosition(); if ((uint64_t)bytes > maxLen) bytes = (size_t)maxLen; uint8_t *retBytes = reinterpret_cast(data); uint64_t remainingBytes = bytes; if (subsamplePosition) { uint64_t copied = std::min(remainingBytes, static_cast(channels * 4 - subsamplePosition)); std::memcpy(retBytes, reinterpret_cast(currentSample.data()) + subsamplePosition, static_cast(copied)); subsamplePosition += static_cast(copied); remainingBytes -= copied; retBytes += copied; if (subsamplePosition == channels * 4) { subsamplePosition = 0; } } while (remainingBytes >= channels * 4) { uint64_t numSamples = remainingBytes / (channels * 4); // 64-bit sample count might doesn't fit in int which op_read_float's third parameter // accepts numSamples = std::min(numSamples, 0x1000000); int result = op_read_float(opusFile, reinterpret_cast(retBytes), static_cast(numSamples * channels), nullptr); if (result < 0) { SPRaise("op_read_float failed with error code %s", stringifyOpusErrorCode(result).c_str()); } else if (result == 0) { SPRaise("op_read_float returned 0 (expected: > 0)"); } numSamples = static_cast(result); retBytes += numSamples * (channels * 4); remainingBytes -= numSamples * (channels * 4); } if (remainingBytes) { int result = op_read_float(opusFile, currentSample.data(), channels, nullptr); if (result < 0) { SPRaise("op_read_float failed with error code %s", stringifyOpusErrorCode(result).c_str()); } else if (result != 1) { SPRaise("op_read_float returned %d (expected: 1)", result); } std::memcpy(retBytes, reinterpret_cast(currentSample.data()), static_cast(remainingBytes)); subsamplePosition = static_cast(remainingBytes); } return bytes; } uint64_t OpusAudioStream::GetPosition() { if (subsamplePosition) { return (op_pcm_tell(opusFile) - 1) * channels * 4 + subsamplePosition; } else { return op_pcm_tell(opusFile) * channels * 4; } } void OpusAudioStream::SetPosition(uint64_t pos) { pos = std::min(pos, GetLength()); op_pcm_seek(opusFile, static_cast(pos / (channels * 4))); subsamplePosition = static_cast(pos % (channels * 4)); if (subsamplePosition) { int result = op_read_float(opusFile, currentSample.data(), channels, nullptr); if (result < 0) { SPRaise("op_read_float failed with error code %s", stringifyOpusErrorCode(result).c_str()); } else if (result != 1) { SPRaise("op_read_float returned %d (expected: 1)", result); } } } }