--- a/dom/media/AudioConverter.cpp
+++ b/dom/media/AudioConverter.cpp
@@ -16,21 +16,23 @@
*/
namespace mozilla {
AudioConverter::AudioConverter(const AudioConfig& aIn, const AudioConfig& aOut)
: mIn(aIn)
, mOut(aOut)
{
- MOZ_DIAGNOSTIC_ASSERT(aIn.Channels() == aOut.Channels() &&
- aIn.Rate() == aOut.Rate() &&
+ MOZ_DIAGNOSTIC_ASSERT(aIn.Rate() == aOut.Rate() &&
aIn.Format() == aOut.Format() &&
aIn.Interleaved() == aOut.Interleaved(),
- "Only channel reordering is supported at this stage");
+ "No format or rate conversion is supported at this stage");
+ MOZ_DIAGNOSTIC_ASSERT((aIn.Channels() > aOut.Channels() && aOut.Channels() <= 2) ||
+ aIn.Channels() == aOut.Channels(),
+ "Only downmixing to mono or stereo is supported at this stage");
MOZ_DIAGNOSTIC_ASSERT(aOut.Interleaved(), "planar audio format not supported");
mIn.Layout().MappingTable(mOut.Layout(), mChannelOrderMap);
}
bool
AudioConverter::CanWorkInPlace() const
{
return mIn.Channels() * mIn.Rate() * AudioConfig::SampleSize(mIn.Format()) >=
@@ -38,17 +40,19 @@ AudioConverter::CanWorkInPlace() const
}
size_t
AudioConverter::Process(void* aOut, const void* aIn, size_t aBytes)
{
if (!CanWorkInPlace()) {
return 0;
}
- if (mIn.Layout() != mOut.Layout() &&
+ if (mIn.Channels() > mOut.Channels()) {
+ return DownmixAudio(aOut, aIn, aBytes);
+ } else if (mIn.Layout() != mOut.Layout() &&
CanReorderAudio()) {
ReOrderInterleavedChannels(aOut, aIn, aBytes);
}
return aBytes;
}
// Reorder interleaved channels.
// Can work in place (e.g aOut == aIn).
@@ -106,9 +110,120 @@ AudioConverter::ReOrderInterleavedChanne
MOZ_DIAGNOSTIC_ASSERT(AudioConfig::SampleSize(mOut.Format()) == 4);
_ReOrderInterleavedChannels((int32_t*)aOut,(const int32_t*)aIn,
aDataSize/sizeof(int32_t)/mIn.Channels(),
mIn.Channels(), mChannelOrderMap);
break;
}
}
+static inline int16_t clipTo15(int32_t aX)
+{
+ return aX < -32768 ? -32768 : aX <= 32767 ? aX : 32767;
+}
+
+size_t
+AudioConverter::DownmixAudio(void* aOut, const void* aIn, size_t aDataSize) const
+{
+ MOZ_ASSERT(mIn.Format() == AudioConfig::FORMAT_S16 ||
+ mIn.Format() == AudioConfig::FORMAT_FLT);
+ MOZ_ASSERT(mIn.Channels() >= mOut.Channels());
+ MOZ_ASSERT(mIn.Layout() == AudioConfig::ChannelLayout(mIn.Channels()),
+ "Can only downmix input data in SMPTE layout");
+ MOZ_ASSERT(mOut.Layout() == AudioConfig::ChannelLayout(2) ||
+ mOut.Layout() == AudioConfig::ChannelLayout(1));
+
+ uint32_t channels = mIn.Channels();
+ uint32_t frames =
+ aDataSize / AudioConfig::SampleSize(mOut.Format()) / channels;
+
+ if (channels == 1 && mOut.Channels() == 1) {
+ if (aOut != aIn) {
+ memmove(aOut, aIn, aDataSize);
+ }
+ return aDataSize;
+ }
+
+ if (channels > 2) {
+ if (mIn.Format() == AudioConfig::FORMAT_FLT) {
+ // Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows 5-8.
+ static const float dmatrix[6][8][2]= {
+ /*3*/{{0.5858f,0},{0,0.5858f},{0.4142f,0.4142f}},
+ /*4*/{{0.4226f,0},{0,0.4226f},{0.366f, 0.2114f},{0.2114f,0.366f}},
+ /*5*/{{0.6510f,0},{0,0.6510f},{0.4600f,0.4600f},{0.5636f,0.3254f},{0.3254f,0.5636f}},
+ /*6*/{{0.5290f,0},{0,0.5290f},{0.3741f,0.3741f},{0.3741f,0.3741f},{0.4582f,0.2645f},{0.2645f,0.4582f}},
+ /*7*/{{0.4553f,0},{0,0.4553f},{0.3220f,0.3220f},{0.3220f,0.3220f},{0.2788f,0.2788f},{0.3943f,0.2277f},{0.2277f,0.3943f}},
+ /*8*/{{0.3886f,0},{0,0.3886f},{0.2748f,0.2748f},{0.2748f,0.2748f},{0.3366f,0.1943f},{0.1943f,0.3366f},{0.3366f,0.1943f},{0.1943f,0.3366f}},
+ };
+ // Re-write the buffer with downmixed data
+ const float* in = static_cast<const float*>(aIn);
+ float* out = static_cast<float*>(aOut);
+ for (uint32_t i = 0; i < frames; i++) {
+ float sampL = 0.0;
+ float sampR = 0.0;
+ for (uint32_t j = 0; j < channels; j++) {
+ sampL += in[i*mIn.Channels()+j]*dmatrix[mIn.Channels()-3][j][0];
+ sampR += in[i*mIn.Channels()+j]*dmatrix[mIn.Channels()-3][j][1];
+ }
+ *out++ = sampL;
+ *out++ = sampR;
+ }
+ } else if (mIn.Format() == AudioConfig::FORMAT_S16) {
+ // Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows 5-8.
+ // Coefficients in Q14.
+ static const int16_t dmatrix[6][8][2]= {
+ /*3*/{{9598, 0},{0, 9598},{6786,6786}},
+ /*4*/{{6925, 0},{0, 6925},{5997,3462},{3462,5997}},
+ /*5*/{{10663,0},{0, 10663},{7540,7540},{9234,5331},{5331,9234}},
+ /*6*/{{8668, 0},{0, 8668},{6129,6129},{6129,6129},{7507,4335},{4335,7507}},
+ /*7*/{{7459, 0},{0, 7459},{5275,5275},{5275,5275},{4568,4568},{6460,3731},{3731,6460}},
+ /*8*/{{6368, 0},{0, 6368},{4502,4502},{4502,4502},{5514,3184},{3184,5514},{5514,3184},{3184,5514}}
+ };
+ // Re-write the buffer with downmixed data
+ const int16_t* in = static_cast<const int16_t*>(aIn);
+ int16_t* out = static_cast<int16_t*>(aOut);
+ for (uint32_t i = 0; i < frames; i++) {
+ int32_t sampL = 0;
+ int32_t sampR = 0;
+ for (uint32_t j = 0; j < channels; j++) {
+ sampL+=in[i*channels+j]*dmatrix[channels-3][j][0];
+ sampR+=in[i*channels+j]*dmatrix[channels-3][j][1];
+ }
+ *out++ = clipTo15((sampL + 8192)>>14);
+ *out++ = clipTo15((sampR + 8192)>>14);
+ }
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
+ }
+
+ // If we are to continue downmixing to mono, start working on the output
+ // buffer.
+ aIn = aOut;
+ channels = 2;
+ }
+
+ if (mOut.Channels() == 1) {
+ if (mIn.Format() == AudioConfig::FORMAT_FLT) {
+ const float* in = static_cast<const float*>(aIn);
+ float* out = static_cast<float*>(aOut);
+ for (uint32_t fIdx = 0; fIdx < frames; ++fIdx) {
+ float sample = 0.0;
+ // The sample of the buffer would be interleaved.
+ sample = (in[fIdx*channels] + in[fIdx*channels + 1]) * 0.5;
+ *out++ = sample;
+ }
+ } else if (mIn.Format() == AudioConfig::FORMAT_S16) {
+ const int16_t* in = static_cast<const int16_t*>(aIn);
+ int16_t* out = static_cast<int16_t*>(aOut);
+ for (uint32_t fIdx = 0; fIdx < frames; ++fIdx) {
+ int32_t sample = 0.0;
+ // The sample of the buffer would be interleaved.
+ sample = (in[fIdx*channels] + in[fIdx*channels + 1]) * 0.5;
+ *out++ = sample;
+ }
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
+ }
+ }
+ return frames * AudioConfig::SampleSize(mOut.Format()) * mOut.Channels();
+}
+
} // namespace mozilla
\ No newline at end of file