From b8c6a41ac49deb904e8960cbd060ccfe9616c9ee Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 13 Jun 2012 15:58:28 +0000 Subject: [PATCH] Add support for floating-point audio. Closes #1490. Originally committed to SVN as r6904. --- aegisub/src/audio_provider_avs.cpp | 1 + aegisub/src/audio_provider_convert.cpp | 47 +++++++++++++++++++- aegisub/src/audio_provider_dummy.cpp | 1 + aegisub/src/audio_provider_ffmpegsource.cpp | 13 +++--- aegisub/src/audio_provider_hd.cpp | 2 + aegisub/src/audio_provider_pcm.cpp | 1 + aegisub/src/audio_provider_ram.cpp | 1 + aegisub/src/include/aegisub/audio_provider.h | 7 ++- 8 files changed, 63 insertions(+), 10 deletions(-) diff --git a/aegisub/src/audio_provider_avs.cpp b/aegisub/src/audio_provider_avs.cpp index e3af83003..190504662 100644 --- a/aegisub/src/audio_provider_avs.cpp +++ b/aegisub/src/audio_provider_avs.cpp @@ -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; } diff --git a/aegisub/src/audio_provider_convert.cpp b/aegisub/src/audio_provider_convert.cpp index 9b6a5fd58..9b10c1cec 100644 --- a/aegisub/src/audio_provider_convert.cpp +++ b/aegisub/src/audio_provider_convert.cpp @@ -31,6 +31,10 @@ #include +#ifndef AGI_PRE +#include +#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 BitdepthConvertAudioProvider : public AudioProviderConverter { int src_bytes_per_sample; @@ -104,6 +109,40 @@ public: } }; +/// Floating point -> 16 bit signed machine-endian audio converter +template +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 src_buf(count * channels); + source->GetAudio(&src_buf[0], start, count); + + Target *dest = reinterpret_cast(buf); + + for (size_t i = 0; i < static_cast(count * channels); ++i) { + Source expanded; + if (src_buf[i] < 0) + expanded = static_cast(-src_buf[i] * std::numeric_limits::min()); + else + expanded = static_cast(src_buf[i] * std::numeric_limits::max()); + + if (expanded < std::numeric_limits::min()) + dest[i] = std::numeric_limits::min(); + else if (expanded > std::numeric_limits::max()) + dest[i] = std::numeric_limits::max(); + else + dest[i] = static_cast(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(provider); + else + provider = new FloatConvertAudioProvider(provider); + } if (provider->GetBytesPerSample() != 2 || !provider->AreSamplesNativeEndian()) provider = new BitdepthConvertAudioProvider(provider); diff --git a/aegisub/src/audio_provider_dummy.cpp b/aegisub/src/audio_provider_dummy.cpp index adcf17133..e5d974a6b 100644 --- a/aegisub/src/audio_provider_dummy.cpp +++ b/aegisub/src/audio_provider_dummy.cpp @@ -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; } diff --git a/aegisub/src/audio_provider_ffmpegsource.cpp b/aegisub/src/audio_provider_ffmpegsource.cpp index 63f04be8d..687388c09 100644 --- a/aegisub/src/audio_provider_ffmpegsource.cpp +++ b/aegisub/src/audio_provider_ffmpegsource.cpp @@ -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); } diff --git a/aegisub/src/audio_provider_hd.cpp b/aegisub/src/audio_provider_hd.cpp index cafcd0c73..bd1adafad 100644 --- a/aegisub/src/audio_provider_hd.cpp +++ b/aegisub/src/audio_provider_hd.cpp @@ -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(); diff --git a/aegisub/src/audio_provider_pcm.cpp b/aegisub/src/audio_provider_pcm.cpp index 313d7cfbe..5349a23ee 100644 --- a/aegisub/src/audio_provider_pcm.cpp +++ b/aegisub/src/audio_provider_pcm.cpp @@ -114,6 +114,7 @@ PCMAudioProvider::PCMAudioProvider(const wxString &filename) current_mapping = 0; #endif + float_samples = false; } /// @brief DOCME diff --git a/aegisub/src/audio_provider_ram.cpp b/aegisub/src/audio_provider_ram.cpp index 0abc827cd..ede00458c 100644 --- a/aegisub/src/audio_provider_ram.cpp +++ b/aegisub/src/audio_provider_ram.cpp @@ -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)); } diff --git a/aegisub/src/include/aegisub/audio_provider.h b/aegisub/src/include/aegisub/audio_provider.h index 5baa22f1c..7ab96c3e8 100644 --- a/aegisub/src/include/aegisub/audio_provider.h +++ b/aegisub/src/include/aegisub/audio_provider.h @@ -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?