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