From cb638cd44e71d0601734eac935484273f508b1a1 Mon Sep 17 00:00:00 2001 From: Nicolay Korslund Date: Mon, 28 Dec 2009 17:15:52 +0100 Subject: [PATCH] Started reworking the sound system. Added sources/ - WIP --- sound/source.h | 63 ++++++++ sound/sources/audiere_source.cpp | 139 ++++++++++++++++++ sound/sources/audiere_source.h | 51 +++++++ sound/sources/ffmpeg_source.cpp | 238 +++++++++++++++++++++++++++++++ sound/sources/ffmpeg_source.h | 47 ++++++ sound/sources/loadertemplate.h | 26 ++++ 6 files changed, 564 insertions(+) create mode 100644 sound/source.h create mode 100644 sound/sources/audiere_source.cpp create mode 100644 sound/sources/audiere_source.h create mode 100644 sound/sources/ffmpeg_source.cpp create mode 100644 sound/sources/ffmpeg_source.h create mode 100644 sound/sources/loadertemplate.h diff --git a/sound/source.h b/sound/source.h new file mode 100644 index 000000000..7361fb118 --- /dev/null +++ b/sound/source.h @@ -0,0 +1,63 @@ +#ifndef MANGLE_SOUND_SOURCE_H +#define MANGLE_SOUND_SOURCE_H + +#include +#include +#include + +#include "../stream/stream.h" + +namespace Mangle { +namespace Sound { + +/// A stream containing raw sound data and information about the format +class SampleSource : public Stream::Stream +{ + protected: + bool isEof; + + public: + SampleSource() + { + // These are usually not needed for sound data + isSeekable = false; + hasPosition = false; + hasSize = false; + + isEof = false; + } + + /// Get the sample rate, number of channels, and bits per + /// sample. NULL parameters are ignored. + virtual void getInfo(int32_t *rate, int32_t *channels, int32_t *bits) const = 0; + + bool eof() const { return isEof; } + + // Disabled functions + void seek(size_t pos) const { assert(0); } + size_t tell() const { assert(0); } + size_t size() const { assert(0); } +}; + +/// A factory interface for loading SampleSources from file or stream +class SampleSourceLoader +{ + public: + /// If true, the stream version of load() works + bool canLoadStream; + + /// If true, the file version of load() works + bool canLoadFile; + + /// Load a sound input source from file (if canLoadFile is true) + virtual SampleSource *load(const std::string &file) = 0; + + /// Load a sound input source from stream (if canLoadStream is true) + virtual SampleSource *load(Stream::Stream *input) = 0; + + /// Virtual destructor + virtual ~SampleSourceLoader() {} +}; + +}} // namespaces +#endif diff --git a/sound/sources/audiere_source.cpp b/sound/sources/audiere_source.cpp new file mode 100644 index 000000000..6ebc4f881 --- /dev/null +++ b/sound/sources/audiere_source.cpp @@ -0,0 +1,139 @@ +#include "audiere_source.h" + +#include "../../stream/clients/audiere_file.h" + +// Exception handling +class Audiere_Exception : public std::exception +{ + std::string msg; + + public: + + Audiere_Exception(const std::string &m) : msg(m) {} + ~Audiere_Exception() throw() {} + virtual const char* what() const throw() { return msg.c_str(); } +}; + +static void fail(const std::string &msg) +{ + throw Audiere_Exception("Audiere exception: " + msg); +} + +using namespace audiere; +using namespace Mangle::Sound; + +// --- SampleSource --- + +void AudiereSource::getInfo(int32_t *rate, int32_t *channels, int32_t *bits) +{ + SampleFormat fmt; + sample->getFormat(*channels, *rate, fmt); + if(fmt == SF_U8) + *bits = 8; + else if(fmt == SF_S16) + *bits = 16; + else assert(0); +} + +/* + Get data. Since Audiere operates with frames, not bytes, there's a + little conversion magic going on here. We need to make sure we're + reading a whole number of frames - if not, we need to store the + remainding part of the last frame and remember it for the next read + operation. + */ +size_t AudiereSource::read(void *_data, size_t length) +{ + if(isEof) return 0; + + char *data = (char*)_data; + + // Move the remains from the last operation first + if(pullSize) + { + // pullSize is how much was stored the last time, so skip that. + memcpy(data, pullOver+pullSize, PSIZE-pullSize); + length -= pullSize; + data += pullSize; + } + + // Determine the overshoot up front + pullSize = length % frameSize; + + // Number of whole frames + int frames = length / frameSize; + + // Read the data + int res = sample->read(frames, data); + + if(res < frames) + isEof = true; + + // Are we missing data? If we're at the end of the stream, then this + // doesn't apply. + if(!isEof && pullSize) + { + // Read one more sample + if(sample->read(1, pullOver) != 0) + { + // Then, move as much of it as we can fit into the output + // data + memcpy(data+length-pullSize, pullOver, pullSize); + } + else + // Failed reading, we're out of data + isEof = true; + } + + // If we're at the end of the stream, then no data remains to be + // pulled over + if(isEof) + pullSize = 0; + + // Return the total number of bytes stored + return frameSize*res + pullSize; +} + +// --- Constructors --- + +AudiereSource::AudiereSource(const std::string &file) +{ + sample = OpenSampleSource(file.c_str()); + + if(!sample) + fail("Couldn't load file " + file); + + getFormat(); +} + +AudiereSource::AudiereSource(Stream::Stream *input) +{ + // Use our Stream::AudiereFile implementation to convert a Mangle + // 'Stream' to an Audiere 'File' + sample = OpenSampleSource(new Stream::AudiereFile(input)); + if(!sample) + fail("Couldn't load stream"); + + getFormat(); +} + +AudiereSource::AudiereSource(audiere::SampleSourcePtr src) + : sample(src) +{ assert(sample); getFormat(); } + +// Common function called from all constructors +AudiereSource::getFormat() +{ + assert(sample); + + SampleFormat fmt; + int channels, rate; + sample->getFormat(channels, rate, fmt); + + // Calculate the size of one frame + frameSize = GetSampleSize(fmt) * channels; + + // Make sure that our pullover hack will work. Increase this size if + // this doesn't work in all cases. + assert(frameSize <= PSIZE); +} diff --git a/sound/sources/audiere_source.h b/sound/sources/audiere_source.h new file mode 100644 index 000000000..12ae81bce --- /dev/null +++ b/sound/sources/audiere_source.h @@ -0,0 +1,51 @@ +#ifndef MANGLE_SOUND_AUDIERE_SOURCE_H +#define MANGLE_SOUND_AUDIERE_SOURCE_H + +#include "../source.h" + +#include + +namespace Mangle { +namespace Sound { + +/// A sample source that decodes files using Audiere +class AudiereSource : public SampleSource +{ + audiere::SampleSourcePtr sample; + + // Number of bytes we cache between reads. This should correspond to + // the maximum possible value of frameSize. + static const int PSIZE = 10; + + // Size of one frame, in bytes + int frameSize; + + // Temporary storage for unevenly read samples. See the comment for + // read() in the .cpp file. + char pullOver[PSIZE]; + // How much of the above buffer is in use + int pullSize; + + void getFormat(); + + public: + /// Decode the given sound file + AudiereSource(const std::string &file); + + /// Decode the given sound stream + AudiereSource(Stream::Stream *src); + + /// Read directly from an existing audiere::SampleSource + AudiereSource(audiere::SampleSourcePtr src); + + void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); + size_t read(void *data, size_t length); +}; + +#include "loadertemplate.h" + +/// A factory that loads AudiereSources from file and stream +typedef SSL_Template AudiereLoader; + +}} // Namespace +#endif diff --git a/sound/sources/ffmpeg_source.cpp b/sound/sources/ffmpeg_source.cpp new file mode 100644 index 000000000..d53b1ced3 --- /dev/null +++ b/sound/sources/ffmpeg_source.cpp @@ -0,0 +1,238 @@ +// NOT UPDATED - WIP + +#include "input_ffmpeg.h" +#include + +using namespace Mangle::Sound; + +// Static output buffer. Not thread safe, but supports multiple +// streams operated from the same thread. +static uint8_t outBuf[AVCODEC_MAX_AUDIO_FRAME_SIZE]; +bool FFM_InputManager::init = false; + +/// FFmpeg exception. +class FFM_Exception : public std::exception +{ + std::string msg; + + public: + + FFM_Exception(const std::string &m); + ~FFM_Exception() throw(); + virtual const char* what() const throw(); +}; + +FFM_Exception::FFM_Exception(const std::string &m) + : msg(m) {} + +const char* FFM_Exception::what() const throw() +{ return msg.c_str(); } + +FFM_Exception::~FFM_Exception() throw() {} + +static void fail(const std::string &msg) +{ + throw FFM_Exception("FFMpeg exception: " + msg); +} + + +// --- Manager --- + +FFM_InputManager::FFM_InputManager() +{ + if(!init) + { + av_register_all(); + av_log_set_level(AV_LOG_ERROR); + init = true; + } + + canLoadStream = false; +} + +InputSource *FFM_InputManager::load(const std::string &file) +{ return new FFM_InputSource(file); } + + +// --- Source --- + +FFM_InputSource::FFM_InputSource(const std::string &file) +{ + // FFmpeg doesn't handle several instances from one source. So we + // just store the filename. + name = file; +} + +InputStream *FFM_InputSource::getStream() +{ return new FFM_InputStream(name); } + +void FFM_InputSource::drop() +{ delete this; } + + +// --- Stream --- + +FFM_InputStream::FFM_InputStream(const std::string &file) +{ + std::string msg; + AVCodec *codec; + + empty = false; + + if(av_open_input_file(&FmtCtx, file.c_str(), NULL, 0, NULL) != 0) + fail("Error loading audio file " + file); + + if(av_find_stream_info(FmtCtx) < 0) + { + msg = "Error in file stream " + file; + goto err; + } + + // Pick the first audio stream, if any + for(StreamNum = 0; StreamNum < FmtCtx->nb_streams; StreamNum++) + { + // Pick the first audio stream + if(FmtCtx->streams[StreamNum]->codec->codec_type == CODEC_TYPE_AUDIO) + break; + } + + if(StreamNum == FmtCtx->nb_streams) + fail("File " + file + " didn't contain any audio streams"); + + // Open the decoder + CodecCtx = FmtCtx->streams[StreamNum]->codec; + codec = avcodec_find_decoder(CodecCtx->codec_id); + + if(!codec || avcodec_open(CodecCtx, codec) < 0) + { + msg = "Error loading " + file + ": "; + if(codec) + msg += "coded error"; + else + msg += "no codec found"; + goto err; + } + + // No errors, we're done + return; + + // Handle errors + err: + av_close_input_file(FmtCtx); + fail(msg); +} + +FFM_InputStream::~FFM_InputStream() +{ + avcodec_close(CodecCtx); + av_close_input_file(FmtCtx); +} + +void FFM_InputStream::getInfo(int32_t *rate, int32_t *channels, int32_t *bits) +{ + if(rate) *rate = CodecCtx->sample_rate; + if(channels) *channels = CodecCtx->channels; + if(bits) *bits = 16; +} + +uint32_t FFM_InputStream::getData(void *data, uint32_t length) +{ + if(empty) return 0; + + uint32_t left = length; + uint8_t *outPtr = (uint8_t*)data; + + // First, copy over any stored data we might be sitting on + { + int s = storage.size(); + int copy = s; + if(s) + { + // Make sure there's room + if(copy > left) + copy = left; + + // Copy + memcpy(outPtr, &storage[0], copy); + outPtr += copy; + left -= copy; + + // Is there anything left in the storage? + s -= copy; + if(s) + { + assert(left == 0); + + // Move it to the start and resize + memmove(&storage[0], &storage[copy], s); + storage.resize(s); + } + } + } + + // Next, get more input data from stream, and decode it + while(left) + { + AVPacket packet; + + // Get the next packet, if any + if(av_read_frame(FmtCtx, &packet) < 0) + break; + + // We only allow one stream per file at the moment + assert(StreamNum == packet.stream_index); + + // Decode the packet + int len = AVCODEC_MAX_AUDIO_FRAME_SIZE; + int tmp = avcodec_decode_audio2(CodecCtx, (int16_t*)outBuf, + &len, packet.data, packet.size); + assert(tmp < 0 || tmp == packet.size); + + // We don't need the input packet any longer + av_free_packet(&packet); + + if(tmp < 0) + fail("Error decoding audio stream"); + + // Copy whatever data we got, and advance the pointer + if(len > 0) + { + // copy = how many bytes do we copy now + int copy = len; + if(copy > left) + copy = left; + + // len = how many bytes are left uncopied + len -= copy; + + // copy data + memcpy(outPtr, outBuf, copy); + + // left = how much space is left in the caller output + // buffer + left -= copy; + outPtr += copy; + assert(left >= 0); + + if(len > 0) + { + // There were uncopied bytes. Store them for later. + assert(left == 0); + storage.resize(len); + memcpy(&storage[0], outBuf, len); + } + } + } + + // End of loop. Return the number of bytes copied. + assert(left <= length); + + // If we're returning less than asked for, then we're done + if(left > 0) + empty = true; + + return length - left; +} + +void FFM_InputStream::drop() +{ delete this; } diff --git a/sound/sources/ffmpeg_source.h b/sound/sources/ffmpeg_source.h new file mode 100644 index 000000000..bf32084fa --- /dev/null +++ b/sound/sources/ffmpeg_source.h @@ -0,0 +1,47 @@ +#ifndef MANGLE_SOUND_FFMPEG_H +#define MANGLE_SOUND_FFMPEG_H + +#include "../input.h" +#include +#include +#include + +extern "C" +{ +#include +#include +} + +namespace Mangle { +namespace Sound { + +class FFMpegSource : public SampleSource +{ + AVFormatContext *FmtCtx; + AVCodecContext *CodecCtx; + int StreamNum; + bool empty; + + std::vector storage; + + public: + /// Decode the given sound file + FFMpegSource(const std::string &file); + + /// Decode the given sound stream (not supported by FFmpeg) + FFMpegSource(Stream::Stream *src) { assert(0); } + + ~FFMpegSource(); + + // Overrides + void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); + size_t read(void *data, size_t length); +}; + +#include "loadertemplate.h" + +/// A factory that loads FFMpegSources from file +typedef SSL_Template AudiereLoader; + +}} // namespaces +#endif diff --git a/sound/sources/loadertemplate.h b/sound/sources/loadertemplate.h new file mode 100644 index 000000000..95d0e798c --- /dev/null +++ b/sound/sources/loadertemplate.h @@ -0,0 +1,26 @@ +#ifndef SSL_TEMPL_H +#define SSL_TEMPL_H + +template +class SSL_Template : public SampleSourceLoader +{ + SSL_Template() + { + canLoadStream = stream; + canLoadFile = file; + } + + SampleSource *load(const std::string &file) + { + assert(canLoadFile); + return new X(file); + } + + SampleSource *load(Stream::Stream *input) + { + assert(canLoadStream); + return new X(input); + } +}; + +#endif