Bug 805254. Part 8: Consolidate audio sample processing code using templates over the format types. r=kinetik

Replace nsAudioStream::Format with an AUDIO_OUTPUT_FORMAT enum value so we
can use it as a template parameter.

Introduce AudioSampleTraits<AudioSampleFormat> to give us access to the C++ type
corresponding to an enum value.

Move SampleToFloat/FloatToSample to AudioSampleFormat.h.

Introduce ConvertAudioSamples and ConvertAudioSamplesWithScale functions
and use them from various places.

Moves AudioDataValue to AudioSampleFormat.h. The name isn't great, but it'll do.
This commit is contained in:
Robert O'Callahan 2012-10-25 23:09:40 +13:00
parent eb84abab22
commit acc22ea0d6
8 changed files with 138 additions and 126 deletions

View File

@ -14,6 +14,7 @@
#include "nsJSUtils.h"
#include "AudioSampleFormat.h"
using namespace mozilla;
using namespace mozilla::dom;
nsGenericHTMLElement*
@ -181,24 +182,12 @@ nsHTMLAudioElement::MozWriteAudio(const JS::Value& aData, JSContext* aCx, uint32
float* frames = JS_GetFloat32ArrayData(tsrc, aCx);
nsresult rv;
if (nsAudioStream::Format() == AUDIO_FORMAT_S16) {
if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) {
// Convert the samples back to integers as we are using fixed point audio in
// the nsAudioStream.
nsAutoArrayPtr<short> shortsArray(new short[writeLen * mChannels]);
// Hard clip the samples.
for (uint32_t i = 0; i < writeLen * mChannels; ++i) {
float scaled_value = floorf(0.5 + 32768 * frames[i]);
if (frames[i] < 0.0) {
shortsArray[i] = (scaled_value < -32768.0) ?
-32768 :
short(scaled_value);
} else {
shortsArray[i] = (scaled_value > 32767.0) ?
32767 :
short(scaled_value);
}
}
rv = mAudioStream->Write(shortsArray, writeLen);
nsAutoArrayPtr<AudioDataValue> shortsArray(new AudioDataValue[writeLen * mChannels]);
ConvertAudioSamples(frames, shortsArray.get(), writeLen * mChannels);
rv = mAudioStream->Write(shortsArray.get(), writeLen);
} else {
rv = mAudioStream->Write(frames, writeLen);
}

View File

@ -6,23 +6,130 @@
#ifndef MOZILLA_AUDIOSAMPLEFORMAT_H_
#define MOZILLA_AUDIOSAMPLEFORMAT_H_
#include "nsAlgorithm.h"
namespace mozilla {
/**
* Audio formats supported in MediaStreams and media elements.
*
* Only one of these is supported by nsAudioStream, and that is determined
* at compile time (roughly, FLOAT32 on desktops, S16 on mobile). That format
* is returned by nsAudioStream::Format().
* at compile time (roughly, FLOAT32 on desktops, S16 on mobile). Media decoders
* produce that format only; queued AudioData always uses that format.
*/
enum AudioSampleFormat
{
// Native-endian signed 16-bit audio samples
AUDIO_FORMAT_S16,
// Signed 32-bit float samples
AUDIO_FORMAT_FLOAT32
AUDIO_FORMAT_FLOAT32,
// The format used for output by nsAudioStream.
#ifdef MOZ_SAMPLE_TYPE_S16
AUDIO_OUTPUT_FORMAT = AUDIO_FORMAT_S16
#else
AUDIO_OUTPUT_FORMAT = AUDIO_FORMAT_FLOAT32
#endif
};
template <AudioSampleFormat Format> class AudioSampleTraits;
template <> class AudioSampleTraits<AUDIO_FORMAT_FLOAT32> {
public:
typedef float Type;
};
template <> class AudioSampleTraits<AUDIO_FORMAT_S16> {
public:
typedef int16_t Type;
};
typedef AudioSampleTraits<AUDIO_OUTPUT_FORMAT>::Type AudioDataValue;
// Single-sample conversion
/*
* Use "2^N" conversion since it's simple, fast, "bit transparent", used by
* many other libraries and apparently behaves reasonably.
* http://blog.bjornroche.com/2009/12/int-float-int-its-jungle-out-there.html
* http://blog.bjornroche.com/2009/12/linearity-and-dynamic-range-in-int.html
*/
inline float
AudioSampleToFloat(float aValue)
{
return aValue;
}
inline float
AudioSampleToFloat(int16_t aValue)
{
return aValue/32768.0f;
}
template <typename T> T FloatToAudioSample(float aValue);
template <> inline float
FloatToAudioSample<float>(float aValue)
{
return aValue;
}
template <> inline int16_t
FloatToAudioSample<int16_t>(float aValue)
{
float v = aValue*32768.0f;
float clamped = NS_MAX(-32768.0f, NS_MIN(32767.0f, v));
return int16_t(clamped);
}
// Sample buffer conversion
template <typename From, typename To> inline void
ConvertAudioSamples(const From* aFrom, To* aTo, int aCount)
{
for (int i = 0; i < aCount; ++i) {
aTo[i] = FloatToAudioSample<To>(AudioSampleToFloat(aFrom[i]));
}
}
inline void
ConvertAudioSamples(const int16_t* aFrom, int16_t* aTo, int aCount)
{
memcpy(aTo, aFrom, sizeof(*aTo)*aCount);
}
inline void
ConvertAudioSamples(const float* aFrom, float* aTo, int aCount)
{
memcpy(aTo, aFrom, sizeof(*aTo)*aCount);
}
// Sample buffer conversion with scale
template <typename From, typename To> inline void
ConvertAudioSamplesWithScale(const From* aFrom, To* aTo, int aCount, float aScale)
{
if (aScale == 1.0f) {
ConvertAudioSamples(aFrom, aTo, aCount);
return;
}
for (int i = 0; i < aCount; ++i) {
aTo[i] = FloatToAudioSample<To>(AudioSampleToFloat(aFrom[i])*aScale);
}
}
inline void
ConvertAudioSamplesWithScale(const int16_t* aFrom, int16_t* aTo, int aCount, float aScale)
{
if (aScale == 1.0f) {
ConvertAudioSamples(aFrom, aTo, aCount);
return;
}
if (0.0f <= aScale && aScale < 1.0f) {
int32_t scale = int32_t((1 << 16) * aScale);
for (int i = 0; i < aCount; ++i) {
aTo[i] = int16_t((int32_t(aFrom[i]) * scale) >> 16);
}
return;
}
for (int i = 0; i < aCount; ++i) {
aTo[i] = FloatToAudioSample<int16_t>(AudioSampleToFloat(aFrom[i])*aScale);
}
}
}
#endif /* MOZILLA_AUDIOSAMPLEFORMAT_H_ */

View File

@ -9,36 +9,6 @@
namespace mozilla {
/*
* Use "2^N" conversion since it's simple, fast, "bit transparent", used by
* many other libraries and apparently behaves reasonably.
* http://blog.bjornroche.com/2009/12/int-float-int-its-jungle-out-there.html
* http://blog.bjornroche.com/2009/12/linearity-and-dynamic-range-in-int.html
*/
static float
SampleToFloat(float aValue)
{
return aValue;
}
static float
SampleToFloat(int16_t aValue)
{
return aValue/32768.0f;
}
static void
FloatToSample(float aValue, float* aOut)
{
*aOut = aValue;
}
static void
FloatToSample(float aValue, int16_t* aOut)
{
float v = aValue*32768.0f;
float clamped = NS_MAX(-32768.0f, NS_MIN(32767.0f, v));
*aOut = int16_t(clamped);
}
template <class SrcT, class DestT>
static void
InterleaveAndConvertBuffer(const SrcT* aSource, int32_t aSourceLength,
@ -50,8 +20,8 @@ InterleaveAndConvertBuffer(const SrcT* aSource, int32_t aSourceLength,
DestT* output = aOutput;
for (int32_t i = 0; i < aLength; ++i) {
for (int32_t channel = 0; channel < aChannels; ++channel) {
float v = SampleToFloat(aSource[channel*aSourceLength + i])*aVolume;
FloatToSample(v, output);
float v = AudioSampleToFloat(aSource[channel*aSourceLength + i])*aVolume;
*output = FloatToAudioSample<DestT>(v);
++output;
}
}
@ -137,7 +107,7 @@ AudioSegment::WriteTo(nsAudioStream* aOutput)
{
NS_ASSERTION(mChannels == aOutput->GetChannels(), "Wrong number of channels");
nsAutoTArray<uint8_t,STATIC_AUDIO_BUFFER_BYTES> buf;
uint32_t frameSize = GetSampleSize(nsAudioStream::Format())*mChannels;
uint32_t frameSize = GetSampleSize(AUDIO_OUTPUT_FORMAT)*mChannels;
for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) {
AudioChunk& c = *ci;
if (frameSize*c.mDuration > UINT32_MAX) {
@ -150,7 +120,7 @@ AudioSegment::WriteTo(nsAudioStream* aOutput)
c.mOffset, int32_t(c.mDuration),
c.mVolume,
aOutput->GetChannels(),
buf.Elements(), nsAudioStream::Format());
buf.Elements(), AUDIO_OUTPUT_FORMAT);
} else {
// Assumes that a bit pattern of zeroes == 0.0f
memset(buf.Elements(), 0, buf.Length());

View File

@ -474,30 +474,9 @@ nsresult nsNativeAudioStream::Write(const void* aBuf, uint32_t aFrames)
uint32_t samples = aFrames * mChannels;
nsAutoArrayPtr<short> s_data(new short[samples]);
if (s_data) {
double scaled_volume = GetVolumeScale() * mVolume;
if (Format() == AUDIO_FORMAT_S16) {
const short* buf = static_cast<const short*>(aBuf);
int32_t volume = int32_t((1 << 16) * scaled_volume);
for (uint32_t i = 0; i < samples; ++i) {
s_data[i] = short((int32_t(buf[i]) * volume) >> 16);
}
} else {
const float* buf = static_cast<const float*>(aBuf);
for (uint32_t i = 0; i < samples; ++i) {
float scaled_value = floorf(0.5 + 32768 * buf[i] * scaled_volume);
if (buf[i] < 0.0) {
s_data[i] = (scaled_value < -32768.0) ?
-32768 :
short(scaled_value);
} else {
s_data[i] = (scaled_value > 32767.0) ?
32767 :
short(scaled_value);
}
}
}
}
float scaled_volume = float(GetVolumeScale() * mVolume);
const AudioDataValue* buf = static_cast<const AudioDataValue*>(aBuf);
ConvertAudioSamplesWithScale(buf, s_data.get(), samples, scaled_volume);
if (sa_stream_write(static_cast<sa_stream_t*>(mAudioHandle),
s_data.get(),
@ -633,7 +612,7 @@ nsRemotedAudioStream::Init(int32_t aNumChannels,
{
mRate = aRate;
mChannels = aNumChannels;
mBytesPerFrame = (Format() == FORMAT_FLOAT32 ? 4 : 2) * mChannels;
mBytesPerFrame = sizeof(AudioDataValue) * mChannels;
nsCOMPtr<nsIRunnable> event = new AudioInitEvent(this);
NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
@ -947,13 +926,12 @@ nsBufferedAudioStream::Init(int32_t aNumChannels, int32_t aRate)
cubeb_stream_params params;
params.rate = aRate;
params.channels = aNumChannels;
if (Format() == AUDIO_FORMAT_S16) {
params.format = CUBEB_SAMPLE_S16NE;
mBytesPerFrame = sizeof(int16_t) * aNumChannels;
if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) {
params.format = CUBEB_SAMPLE_S16NE;
} else {
params.format = CUBEB_SAMPLE_FLOAT32NE;
mBytesPerFrame = sizeof(float) * aNumChannels;
}
mBytesPerFrame = sizeof(AudioDataValue) * aNumChannels;
{
cubeb_stream* stream;
@ -1170,37 +1148,21 @@ nsBufferedAudioStream::DataCallback(void* aBuffer, long aFrames)
if (available > 0) {
// Copy each sample from mBuffer to aBuffer, adjusting the volume during the copy.
double scaled_volume = GetVolumeScale() * mVolume;
float scaled_volume = float(GetVolumeScale() * mVolume);
// Fetch input pointers from the ring buffer.
void* input[2];
uint32_t input_size[2];
mBuffer.PopElements(available, &input[0], &input_size[0], &input[1], &input_size[1]);
uint8_t* output = reinterpret_cast<uint8_t*>(aBuffer);
uint8_t* output = static_cast<uint8_t*>(aBuffer);
for (int i = 0; i < 2; ++i) {
// Fast path for unity volume case.
if (scaled_volume == 1.0) {
memcpy(output, input[i], input_size[i]);
output += input_size[i];
} else if (Format() == AUDIO_FORMAT_S16) {
// Adjust volume as each sample is copied out.
int32_t volume = int32_t(1 << 16) * scaled_volume;
const AudioDataValue* src = static_cast<const AudioDataValue*>(input[i]);
AudioDataValue* dst = reinterpret_cast<AudioDataValue*>(output);
const short* src = static_cast<const short*>(input[i]);
short* dst = reinterpret_cast<short*>(output);
for (uint32_t j = 0; j < input_size[i] / (mBytesPerFrame / mChannels); ++j) {
dst[j] = short((int32_t(src[j]) * volume) >> 16);
}
output += input_size[i];
} else {
const float* src = static_cast<const float*>(input[i]);
float* dst = reinterpret_cast<float*>(output);
for (uint32_t j = 0; j < input_size[i] / (mBytesPerFrame / mChannels); ++j) {
dst[j] = src[j] * scaled_volume;
}
output += input_size[i];
}
ConvertAudioSamplesWithScale(src, dst, input_size[i]/sizeof(AudioDataValue),
scaled_volume);
output += input_size[i];
}
NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Must copy complete frames");

View File

@ -99,14 +99,6 @@ public:
int GetRate() { return mRate; }
int GetChannels() { return mChannels; }
static mozilla::AudioSampleFormat Format() {
#ifdef MOZ_SAMPLE_TYPE_S16
return mozilla::AUDIO_FORMAT_S16;
#else
return mozilla::AUDIO_FORMAT_FLOAT32;
#endif
}
protected:
nsCOMPtr<nsIThread> mAudioPlaybackThread;
int mRate;

View File

@ -12,6 +12,7 @@
#include "MediaStreamGraph.h"
#include "SharedBuffer.h"
#include "ImageLayers.h"
#include "AudioSampleFormat.h"
// Stores info relevant to presenting media frames.
class nsVideoInfo {
@ -56,7 +57,6 @@ public:
#ifdef MOZ_SAMPLE_TYPE_S16
#include <ogg/os_types.h>
typedef ogg_int32_t VorbisPCMValue;
typedef short AudioDataValue;
#define MOZ_CLIP_TO_15(x) ((x)<-32768?-32768:(x)<=32767?(x):32767)
// Convert the output of vorbis_synthesis_pcmout to a AudioDataValue
@ -68,7 +68,6 @@ typedef short AudioDataValue;
#else /* MOZ_SAMPLE_TYPE_FLOAT32 */
typedef float VorbisPCMValue;
typedef float AudioDataValue;
#define MOZ_CONVERT_VORBIS_SAMPLE(x) (x)
#define MOZ_CONVERT_AUDIO_SAMPLE(x) (x)
@ -79,6 +78,7 @@ typedef float AudioDataValue;
class AudioData {
public:
typedef mozilla::SharedBuffer SharedBuffer;
typedef mozilla::AudioDataValue AudioDataValue;
AudioData(int64_t aOffset,
int64_t aTime,
@ -379,6 +379,7 @@ public:
typedef mozilla::ReentrantMonitorAutoEnter ReentrantMonitorAutoEnter;
typedef mozilla::VideoFrameContainer VideoFrameContainer;
typedef mozilla::MediaByteRange MediaByteRange;
typedef mozilla::AudioDataValue AudioDataValue;
nsBuiltinDecoderReader(nsBuiltinDecoder* aDecoder);
virtual ~nsBuiltinDecoderReader();

View File

@ -561,7 +561,7 @@ void nsBuiltinDecoderStateMachine::SendStreamAudio(AudioData* aAudio,
aAudio->EnsureAudioBuffer();
nsRefPtr<SharedBuffer> buffer = aAudio->mAudioBuffer;
aOutput->AppendFrames(buffer.forget(), aAudio->mFrames, int32_t(offset), aAudio->mFrames,
nsAudioStream::Format());
AUDIO_OUTPUT_FORMAT);
LOG(PR_LOG_DEBUG, ("%p Decoder writing %d frames of data to MediaStream for AudioData at %lld",
mDecoder.get(), aAudio->mFrames - int32_t(offset), aAudio->mTime));
aStream->mAudioFramesWritten += aAudio->mFrames - int32_t(offset);

View File

@ -559,17 +559,8 @@ void MediaPipelineTransmit::ProcessAudioChunk(AudioSessionConduit *conduit,
break;
case AUDIO_FORMAT_S16:
{
// Code based on nsAudioStream
const short* buf = static_cast<const short *>(chunk.mBuffer->Data());
int32_t volume = int32_t((1 << 16) * chunk.mVolume);
for (uint32_t i = 0; i < chunk.mDuration; ++i) {
int16_t s = buf[i];
#if defined(IS_BIG_ENDIAN)
s = ((s & 0x00ff) << 8) | ((s & 0xff00) >> 8);
#endif
samples[i] = short((int32_t(s) * volume) >> 16);
}
ConvertAudioSamplesWithScale(buf, samples, chunk.mDuration, chunk.mVolume);
}
break;
default: