Add support for floating-point audio. Closes #1490.

Originally committed to SVN as r6904.
This commit is contained in:
Thomas Goyne 2012-06-13 15:58:28 +00:00
parent 5eb14a1f7a
commit b8c6a41ac4
8 changed files with 63 additions and 10 deletions

View File

@ -146,6 +146,7 @@ void AvisynthAudioProvider::LoadFromClip(AVSValue _clip) {
num_samples = vi.num_audio_samples;
sample_rate = vi.SamplesPerSecond();
bytes_per_sample = vi.BytesPerAudioSample();
float_samples = false;
clip = tempclip;
}

View File

@ -31,6 +31,10 @@
#include <libaegisub/scoped_ptr.h>
#ifndef AGI_PRE
#include <limits>
#endif
/// Base class for all wrapping converters
class AudioProviderConverter : public AudioProvider {
protected:
@ -41,13 +45,14 @@ public:
num_samples = source->GetNumSamples();
sample_rate = source->GetSampleRate();
bytes_per_sample = source->GetBytesPerSample();
float_samples = source->AreSamplesFloat();
}
bool AreSamplesNativeEndian() const { return true; }
wxString GetFilename() const { return source->GetFilename(); }
};
/// Anything -> 16 bit signed machine-endian audio converter
/// Anything integral -> 16 bit signed machine-endian audio converter
template<class Target>
class BitdepthConvertAudioProvider : public AudioProviderConverter {
int src_bytes_per_sample;
@ -104,6 +109,40 @@ public:
}
};
/// Floating point -> 16 bit signed machine-endian audio converter
template<class Source, class Target>
class FloatConvertAudioProvider : public AudioProviderConverter {
public:
FloatConvertAudioProvider(AudioProvider *src) : AudioProviderConverter(src) {
if (!src->AreSamplesNativeEndian())
throw agi::AudioProviderOpenError("Audio format converter: Float audio with non-native endianness is currently unsupported.", 0);
bytes_per_sample = sizeof(Target);
float_samples = false;
}
void GetAudio(void *buf, int64_t start, int64_t count) const {
std::vector<Source> src_buf(count * channels);
source->GetAudio(&src_buf[0], start, count);
Target *dest = reinterpret_cast<Target*>(buf);
for (size_t i = 0; i < static_cast<size_t>(count * channels); ++i) {
Source expanded;
if (src_buf[i] < 0)
expanded = static_cast<Target>(-src_buf[i] * std::numeric_limits<Target>::min());
else
expanded = static_cast<Target>(src_buf[i] * std::numeric_limits<Target>::max());
if (expanded < std::numeric_limits<Target>::min())
dest[i] = std::numeric_limits<Target>::min();
else if (expanded > std::numeric_limits<Target>::max())
dest[i] = std::numeric_limits<Target>::max();
else
dest[i] = static_cast<Target>(expanded);
}
}
};
/// Non-mono 16-bit signed machine-endian -> mono 16-bit signed machine endian converter
class DownmixAudioProvider : public AudioProviderConverter {
int src_channels;
@ -178,6 +217,12 @@ AudioProvider *CreateConvertAudioProvider(AudioProvider *source_provider) {
AudioProvider *provider = source_provider;
// Ensure 16-bit audio with proper endianness
if (provider->AreSamplesFloat()) {
if (provider->GetBytesPerSample() == sizeof(float))
provider = new FloatConvertAudioProvider<float, int16_t>(provider);
else
provider = new FloatConvertAudioProvider<double, int16_t>(provider);
}
if (provider->GetBytesPerSample() != 2 || !provider->AreSamplesNativeEndian())
provider = new BitdepthConvertAudioProvider<int16_t>(provider);

View File

@ -44,6 +44,7 @@ DummyAudioProvider::DummyAudioProvider(unsigned long dur_ms, bool _noise) {
channels = 1;
sample_rate = 44100;
bytes_per_sample = 2;
float_samples = false;
num_samples = (int64_t)dur_ms * sample_rate / 1000;
}

View File

@ -161,13 +161,12 @@ void FFmpegSourceAudioProvider::LoadAudio(wxString filename) {
if (channels <= 0 || sample_rate <= 0 || num_samples <= 0)
throw agi::AudioProviderOpenError("sanity check failed, consult your local psychiatrist", 0);
// FIXME: use the actual sample format too?
// why not just bits_per_sample/8? maybe there's some oddball format with half bytes out there somewhere...
switch (AudioInfo.BitsPerSample) {
case 8: bytes_per_sample = 1; break;
case 16: bytes_per_sample = 2; break;
case 24: bytes_per_sample = 3; break;
case 32: bytes_per_sample = 4; break;
switch (AudioInfo.SampleFormat) {
case FFMS_FMT_U8: bytes_per_sample = 1; float_samples = false; break;
case FFMS_FMT_S16: bytes_per_sample = 2; float_samples = false; break;
case FFMS_FMT_S32: bytes_per_sample = 4; float_samples = false; break;
case FFMS_FMT_FLT: bytes_per_sample = 4; float_samples = true; break;
case FFMS_FMT_DBL: bytes_per_sample = 8; float_samples = true; break;
default:
throw agi::AudioProviderOpenError("unknown or unsupported sample format", 0);
}

View File

@ -86,6 +86,7 @@ public:
channels = src->GetChannels();
sample_rate = src->GetSampleRate();
filename = src->GetFilename();
float_samples = src->AreSamplesFloat();
IndexPoint p = { 0, 0, num_samples };
index_points.push_back(p);
@ -112,6 +113,7 @@ HDAudioProvider::HDAudioProvider(AudioProvider *src, agi::BackgroundRunner *br)
channels = source->GetChannels();
sample_rate = source->GetSampleRate();
filename = source->GetFilename();
float_samples = source->AreSamplesFloat();
diskCacheFilename = cache_path();

View File

@ -114,6 +114,7 @@ PCMAudioProvider::PCMAudioProvider(const wxString &filename)
current_mapping = 0;
#endif
float_samples = false;
}
/// @brief DOCME

View File

@ -78,6 +78,7 @@ RAMAudioProvider::RAMAudioProvider(AudioProvider *src, agi::BackgroundRunner *br
channels = source->GetChannels();
sample_rate = source->GetSampleRate();
filename = source->GetFilename();
float_samples = source->AreSamplesFloat();
br->Run(std::tr1::bind(&RAMAudioProvider::FillCache, this, src, std::tr1::placeholders::_1));
}

View File

@ -52,8 +52,8 @@ protected:
/// DOCME
int channels;
/// DOCME
int64_t num_samples; // for one channel, ie. number of PCM frames
/// for one channel, ie. number of PCM frames
int64_t num_samples;
/// DOCME
int sample_rate;
@ -61,6 +61,8 @@ protected:
/// DOCME
int bytes_per_sample;
bool float_samples;
/// DOCME
wxString filename;
@ -76,6 +78,7 @@ public:
int GetSampleRate() const { return sample_rate; }
int GetBytesPerSample() const { return bytes_per_sample; }
int GetChannels() const { return channels; }
bool AreSamplesFloat() const { return float_samples; }
virtual bool AreSamplesNativeEndian() const = 0;
/// @brief Does this provider benefit from external caching?