Add sample endianness info to all audio providers (except one, intentionally; see the stream provider changeset), and make the converting audio provider convert to native endian when required. Updates #725 but needs some testing. Might break compilation in some places, but shouldn't. ("Works for me.")

Originally committed to SVN as r3016.
This commit is contained in:
Niels Martin Hansen 2009-06-04 23:02:29 +00:00
parent 88a5335004
commit ce015fc820
15 changed files with 104 additions and 11 deletions

View file

@ -61,6 +61,9 @@ public:
wxString GetFilename();
// Only exists for x86 Windows, always delivers machine (little) endian
bool AreSamplesNativeEndian() { return true; }
void GetAudio(void *buf, int64_t start, int64_t count);
void GetWaveForm(int *min,int *peak,int64_t start,int w,int h,int samples,float scale);
};

View file

@ -40,6 +40,7 @@
#include "audio_provider_convert.h"
#include "audio_provider_downmix.h"
#include "aegisub_endian.h"
///////////////
@ -70,7 +71,7 @@ ConvertAudioProvider::~ConvertAudioProvider() {
// Convert to 16-bit
void ConvertAudioProvider::Make16Bit(const char *src, short *dst, int64_t count) {
for (int64_t i=0;i<count;i++) {
dst[i] = (src[i]-128)*255;
dst[i] = (short(src[i])-128)*255;
}
}
@ -78,7 +79,9 @@ void ConvertAudioProvider::Make16Bit(const char *src, short *dst, int64_t count)
//////////////////////
// Change sample rate
// This requres 16-bit input
void ConvertAudioProvider::ChangeSampleRate(const short *src, short *dst, int64_t count) {
// The SampleConverter is a class overloading operator() with a function from short to short
template<class SampleConverter>
void ConvertAudioProvider::ChangeSampleRate(const short *src, short *dst, int64_t count, const SampleConverter &converter) {
// Upsample by 2
if (sampleMult == 2) {
int64_t size = count/2;
@ -86,7 +89,7 @@ void ConvertAudioProvider::ChangeSampleRate(const short *src, short *dst, int64_
short next = 0;
for (int64_t i=0;i<size;i++) {
cur = next;
next = *src++;
next = converter(*src++);
*(dst++) = cur;
*(dst++) = (cur+next)/2;
}
@ -100,7 +103,7 @@ void ConvertAudioProvider::ChangeSampleRate(const short *src, short *dst, int64_
short next = 0;
for (int64_t i=0;i<size;i++) {
cur = next;
next = *(src++);
next = converter(*src++);
*(dst++) = cur;
*(dst++) = (cur*3+next)/4;
*(dst++) = (cur+next)/2;
@ -109,11 +112,30 @@ void ConvertAudioProvider::ChangeSampleRate(const short *src, short *dst, int64_
for (int i=0;i<count%4;i++) *(dst++) = next;
}
// Nothing to do (shouldn't really get here, but...)
else if (sampleMult == 1) memcpy((void*)src,dst,count);
// Nothing much to do, just ensure correct endedness
else if (sampleMult == 1) {
while (count-- > 0) {
*dst++ = converter(*src++);
}
}
}
// Do-nothing sample converter for ChangeSampleRate
struct NullSampleConverter {
inline short operator()(const short val) const {
return val;
}
};
// Endian-swapping sample converter for ChangeSampleRate
struct EndianSwapSampleConverter {
inline short operator()(const short val) const {
return (short)Endian::Reverse((uint16_t)val);
};
};
/////////////
// Get audio
void ConvertAudioProvider::GetAudio(void *destination, int64_t start, int64_t count) {
@ -154,8 +176,11 @@ void ConvertAudioProvider::GetAudio(void *destination, int64_t start, int64_t co
else if (srcBps == 2) last = buffer1;
// Convert sample rate
if (sampleMult != 1) {
ChangeSampleRate(last,(short*)destination,count * channels);
if (sampleMult != 1 && source->AreSamplesNativeEndian()) {
ChangeSampleRate(last,(short*)destination,count * channels, NullSampleConverter());
}
else if (!source->AreSamplesNativeEndian()) {
ChangeSampleRate(last,(short*)destination,count * channels, EndianSwapSampleConverter());
}
delete [] buffer1;
@ -166,11 +191,22 @@ void ConvertAudioProvider::GetAudio(void *destination, int64_t start, int64_t co
// See if we need to downmix the number of channels
AudioProvider *CreateConvertAudioProvider(AudioProvider *source_provider) {
AudioProvider *provider = source_provider;
if (provider->GetBytesPerSample() != 2 || provider->GetSampleRate() < 32000)
provider = new ConvertAudioProvider(provider);
// Aegisub requires 16 bit samples,
// some audio players break with low samplerates,
// everything breaks with wrong-ended samples.
if (provider->GetBytesPerSample() != 2 ||
provider->GetSampleRate() < 32000 ||
!provider->AreSamplesNativeEndian())
{
provider = new ConvertAudioProvider(provider);
}
// We also require mono audio for historical reasons
if (provider->GetChannels() != 1)
{
provider = new DownmixingAudioProvider(provider);
}
return provider;
}

View file

@ -50,12 +50,17 @@ private:
AudioProvider *source;
void Make16Bit(const char *src, short *dst, int64_t count);
void ChangeSampleRate(const short *src, short *dst, int64_t count);
template<class SampleConverter>
void ChangeSampleRate(const short *src, short *dst, int64_t count, const SampleConverter &converter);
public:
ConvertAudioProvider(AudioProvider *source);
~ConvertAudioProvider();
// By its nature, the ConvertAudioProvider always delivers machine endian:
// That's one of the points of it!
bool AreSamplesNativeEndian() { return true; }
void GetAudio(void *buf, int64_t start, int64_t count);
wxString GetFilename() { return source->GetFilename(); }
};

View file

@ -55,6 +55,8 @@ DownmixingAudioProvider::DownmixingAudioProvider(AudioProvider *source) {
if (!(bytes_per_sample == 1 || bytes_per_sample == 2))
throw _T("Downmixing Audio Provider: Can only downmix 8 and 16 bit audio");
if (!source->AreSamplesNativeEndian())
throw _T("Downmixing Audio Provider: Source must have machine endian samples");
}
/////////////////

View file

@ -44,6 +44,9 @@ public:
DownmixingAudioProvider(AudioProvider *source);
~DownmixingAudioProvider();
// Downmixing requires samples to be native endian beforehand
bool AreSamplesNativeEndian() { return true; }
void GetAudio(void *buf, int64_t start, int64_t count);
};

View file

@ -52,5 +52,7 @@ public:
DummyAudioProvider(unsigned long dur_ms, bool _noise);
~DummyAudioProvider();
bool AreSamplesNativeEndian() { return true; }
void GetAudio(void *buf, int64_t start, int64_t count);
};

View file

@ -60,6 +60,9 @@ public:
FFmpegSourceAudioProvider(Aegisub::String filename);
virtual ~FFmpegSourceAudioProvider();
// FFMS always delivers samples in machine endian
bool AreSamplesNativeEndian() { return true; }
virtual void GetAudio(void *buf, int64_t start, int64_t count);
};

View file

@ -59,6 +59,7 @@ HDAudioProvider::HDAudioProvider(AudioProvider *source) {
channels = source->GetChannels();
sample_rate = source->GetSampleRate();
filename = source->GetFilename();
samples_native_endian = source->AreSamplesNativeEndian();
// Check free space
wxLongLong freespace;

View file

@ -50,6 +50,7 @@ private:
wxMutex diskmutex;
wxFile file_cache;
wxString diskCacheFilename;
bool samples_native_endian;
static wxString DiskCachePath();
static wxString DiskCacheName();
@ -58,5 +59,7 @@ public:
HDAudioProvider(AudioProvider *source);
~HDAudioProvider();
bool AreSamplesNativeEndian() { return samples_native_endian; }
void GetAudio(void *buf, int64_t start, int64_t count);
};

View file

@ -89,6 +89,10 @@ private:
public:
LAVCAudioProvider(Aegisub::String _filename);
virtual ~LAVCAudioProvider();
// Supposedly lavc always returns machine endian samples
bool AreSamplesNativeEndian() { return true; }
virtual void GetAudio(void *buf, int64_t start, int64_t count);
};

View file

@ -365,6 +365,16 @@ public:
filepos += (Endian::LittleToMachine(ch.size) + 1) & ~1;
}
}
bool AreSamplesNativeEndian()
{
// 8 bit samples don't consider endianness
if (bytes_per_sample < 2) return true;
// Otherwise test whether we're little endian
uint32_t testvalue = 0x008800ff;
return testvalue == Endian::LittleToMachine(testvalue);
}
};
@ -508,6 +518,16 @@ public:
filepos += (chunk_size + 7) & ~7;
}
}
bool AreSamplesNativeEndian()
{
// 8 bit samples don't consider endianness
if (bytes_per_sample < 2) return true;
// Otherwise test whether we're little endian
uint32_t testvalue = 0x008800ff;
return testvalue == Endian::LittleToMachine(testvalue);
}
};

View file

@ -57,6 +57,7 @@ RAMAudioProvider::RAMAudioProvider(AudioProvider *source) {
// Init
blockcache = NULL;
blockcount = 0;
samples_native_endian = source->AreSamplesNativeEndian();
// Allocate cache
int64_t ssize = source->GetNumSamples() * source->GetBytesPerSample();

View file

@ -48,6 +48,7 @@ class RAMAudioProvider : public AudioProvider {
private:
char** blockcache;
int blockcount;
bool samples_native_endian;
void Clear();
@ -55,5 +56,7 @@ public:
RAMAudioProvider(AudioProvider *source);
~RAMAudioProvider();
bool AreSamplesNativeEndian() { return samples_native_endian; }
void GetAudio(void *buf, int64_t start, int64_t count);
};

View file

@ -68,6 +68,12 @@ public:
StreamAudioProvider();
~StreamAudioProvider();
// The AreSamplesNativeEndian() method is intentionally missing.
// This class was used with the experimental scrubbing code but never
// used outside that.
// The method is left out to make it break compilation in case it does
// get used again, so it can get a review and stuff.
void GetAudio(void *buf, int64_t start, int64_t count);
void Append(void *buf, int64_t count);
void SetParams(int channels,int rate,int bps);

View file

@ -74,6 +74,7 @@ public:
int GetSampleRate();
int GetBytesPerSample();
int GetChannels();
virtual bool AreSamplesNativeEndian() = 0;
void GetWaveForm(int *min,int *peak,int64_t start,int w,int h,int samples,float scale);
};