Started reworking the sound system. Added sources/ - WIP
parent
69e8f9c9db
commit
cb638cd44e
@ -0,0 +1,63 @@
|
||||
#ifndef MANGLE_SOUND_SOURCE_H
|
||||
#define MANGLE_SOUND_SOURCE_H
|
||||
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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
|
@ -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);
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
#ifndef MANGLE_SOUND_AUDIERE_SOURCE_H
|
||||
#define MANGLE_SOUND_AUDIERE_SOURCE_H
|
||||
|
||||
#include "../source.h"
|
||||
|
||||
#include <audiere.h>
|
||||
|
||||
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<AudiereSource,true,true> AudiereLoader;
|
||||
|
||||
}} // Namespace
|
||||
#endif
|
@ -0,0 +1,238 @@
|
||||
// NOT UPDATED - WIP
|
||||
|
||||
#include "input_ffmpeg.h"
|
||||
#include <assert.h>
|
||||
|
||||
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; }
|
@ -0,0 +1,47 @@
|
||||
#ifndef MANGLE_SOUND_FFMPEG_H
|
||||
#define MANGLE_SOUND_FFMPEG_H
|
||||
|
||||
#include "../input.h"
|
||||
#include <exception>
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
namespace Mangle {
|
||||
namespace Sound {
|
||||
|
||||
class FFMpegSource : public SampleSource
|
||||
{
|
||||
AVFormatContext *FmtCtx;
|
||||
AVCodecContext *CodecCtx;
|
||||
int StreamNum;
|
||||
bool empty;
|
||||
|
||||
std::vector<uint8_t> 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<AudiereSource,false,true> AudiereLoader;
|
||||
|
||||
}} // namespaces
|
||||
#endif
|
@ -0,0 +1,26 @@
|
||||
#ifndef SSL_TEMPL_H
|
||||
#define SSL_TEMPL_H
|
||||
|
||||
template <class X, bool stream, bool file>
|
||||
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
|
Loading…
Reference in New Issue