1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "AudioConverter.h"
8 #include <speex/speex_resampler.h>
13 * Parts derived from MythTV AudioConvert Class
14 * Created by Jean-Yves Avenard.
16 * Copyright (C) Bubblestuff Pty Ltd 2013
17 * Copyright (C) foobum@gmail.com 2010
22 AudioConverter::AudioConverter(const AudioConfig
& aIn
, const AudioConfig
& aOut
)
23 : mIn(aIn
), mOut(aOut
), mResampler(nullptr) {
24 MOZ_DIAGNOSTIC_ASSERT(CanConvert(aIn
, aOut
),
25 "The conversion is not supported");
26 mIn
.Layout().MappingTable(mOut
.Layout(), &mChannelOrderMap
);
27 if (aIn
.Rate() != aOut
.Rate()) {
32 AudioConverter::~AudioConverter() {
34 speex_resampler_destroy(mResampler
);
39 bool AudioConverter::CanConvert(const AudioConfig
& aIn
,
40 const AudioConfig
& aOut
) {
41 if (aIn
.Format() != aOut
.Format() ||
42 aIn
.Interleaved() != aOut
.Interleaved()) {
43 NS_WARNING("No format conversion is supported at this stage");
46 if (aIn
.Channels() != aOut
.Channels() && aOut
.Channels() > 2) {
48 "Only down/upmixing to mono or stereo is supported at this stage");
51 if (!aOut
.Interleaved()) {
52 NS_WARNING("planar audio format not supported");
58 bool AudioConverter::CanWorkInPlace() const {
59 bool needDownmix
= mIn
.Channels() > mOut
.Channels();
60 bool needUpmix
= mIn
.Channels() < mOut
.Channels();
61 bool canDownmixInPlace
=
62 mIn
.Channels() * AudioConfig::SampleSize(mIn
.Format()) >=
63 mOut
.Channels() * AudioConfig::SampleSize(mOut
.Format());
64 bool needResample
= mIn
.Rate() != mOut
.Rate();
65 bool canResampleInPlace
= mIn
.Rate() >= mOut
.Rate();
66 // We should be able to work in place if 1s of audio input takes less space
67 // than 1s of audio output. However, as we downmix before resampling we can't
68 // perform any upsampling in place (e.g. if incoming rate >= outgoing rate)
69 return !needUpmix
&& (!needDownmix
|| canDownmixInPlace
) &&
70 (!needResample
|| canResampleInPlace
);
73 size_t AudioConverter::ProcessInternal(void* aOut
, const void* aIn
,
78 if (mIn
.Channels() > mOut
.Channels()) {
79 return DownmixAudio(aOut
, aIn
, aFrames
);
80 } else if (mIn
.Channels() < mOut
.Channels()) {
81 return UpmixAudio(aOut
, aIn
, aFrames
);
82 } else if (mIn
.Layout() != mOut
.Layout() && CanReorderAudio()) {
83 ReOrderInterleavedChannels(aOut
, aIn
, aFrames
);
84 } else if (aIn
!= aOut
) {
85 memmove(aOut
, aIn
, FramesOutToBytes(aFrames
));
90 // Reorder interleaved channels.
91 // Can work in place (e.g aOut == aIn).
92 template <class AudioDataType
>
93 void _ReOrderInterleavedChannels(AudioDataType
* aOut
, const AudioDataType
* aIn
,
94 uint32_t aFrames
, uint32_t aChannels
,
95 const uint8_t* aChannelOrderMap
) {
96 MOZ_DIAGNOSTIC_ASSERT(aChannels
<= AudioConfig::ChannelLayout::MAX_CHANNELS
);
97 AudioDataType val
[AudioConfig::ChannelLayout::MAX_CHANNELS
];
98 for (uint32_t i
= 0; i
< aFrames
; i
++) {
99 for (uint32_t j
= 0; j
< aChannels
; j
++) {
100 val
[j
] = aIn
[aChannelOrderMap
[j
]];
102 for (uint32_t j
= 0; j
< aChannels
; j
++) {
110 void AudioConverter::ReOrderInterleavedChannels(void* aOut
, const void* aIn
,
111 size_t aFrames
) const {
112 MOZ_DIAGNOSTIC_ASSERT(mIn
.Channels() == mOut
.Channels());
113 MOZ_DIAGNOSTIC_ASSERT(CanReorderAudio());
115 if (mChannelOrderMap
.IsEmpty() || mOut
.Channels() == 1 ||
116 mOut
.Layout() == mIn
.Layout()) {
117 // If channel count is 1, planar and non-planar formats are the same or
118 // there's nothing to reorder, or if we don't know how to re-order.
120 memmove(aOut
, aIn
, FramesOutToBytes(aFrames
));
125 uint32_t bits
= AudioConfig::FormatToBits(mOut
.Format());
128 _ReOrderInterleavedChannels((uint8_t*)aOut
, (const uint8_t*)aIn
, aFrames
,
129 mIn
.Channels(), mChannelOrderMap
.Elements());
132 _ReOrderInterleavedChannels((int16_t*)aOut
, (const int16_t*)aIn
, aFrames
,
133 mIn
.Channels(), mChannelOrderMap
.Elements());
136 MOZ_DIAGNOSTIC_ASSERT(AudioConfig::SampleSize(mOut
.Format()) == 4);
137 _ReOrderInterleavedChannels((int32_t*)aOut
, (const int32_t*)aIn
, aFrames
,
138 mIn
.Channels(), mChannelOrderMap
.Elements());
143 static inline int16_t clipTo15(int32_t aX
) {
144 return aX
< -32768 ? -32768 : aX
<= 32767 ? aX
: 32767;
147 template <typename TYPE
>
148 static void dumbUpDownMix(TYPE
* aOut
, int32_t aOutChannels
, const TYPE
* aIn
,
149 int32_t aInChannels
, int32_t aFrames
) {
153 int32_t commonChannels
= std::min(aInChannels
, aOutChannels
);
155 for (int32_t i
= 0; i
< aFrames
; i
++) {
156 for (int32_t j
= 0; j
< commonChannels
; j
++) {
157 aOut
[i
* aOutChannels
+ j
] = aIn
[i
* aInChannels
+ j
];
159 for (int32_t j
= 0; j
< aInChannels
- aOutChannels
; j
++) {
160 aOut
[i
* aOutChannels
+ j
] = 0;
165 size_t AudioConverter::DownmixAudio(void* aOut
, const void* aIn
,
166 size_t aFrames
) const {
167 MOZ_DIAGNOSTIC_ASSERT(mIn
.Format() == AudioConfig::FORMAT_S16
||
168 mIn
.Format() == AudioConfig::FORMAT_FLT
);
169 MOZ_DIAGNOSTIC_ASSERT(mIn
.Channels() >= mOut
.Channels());
170 MOZ_DIAGNOSTIC_ASSERT(mOut
.Layout() == AudioConfig::ChannelLayout(2) ||
171 mOut
.Layout() == AudioConfig::ChannelLayout(1));
173 uint32_t inChannels
= mIn
.Channels();
174 uint32_t outChannels
= mOut
.Channels();
176 if (inChannels
== outChannels
) {
178 memmove(aOut
, aIn
, FramesOutToBytes(aFrames
));
183 if (!mIn
.Layout().IsValid() || !mOut
.Layout().IsValid()) {
184 // Dumb copy dropping extra channels.
185 if (mIn
.Format() == AudioConfig::FORMAT_FLT
) {
186 dumbUpDownMix(static_cast<float*>(aOut
), outChannels
,
187 static_cast<const float*>(aIn
), inChannels
, aFrames
);
188 } else if (mIn
.Format() == AudioConfig::FORMAT_S16
) {
189 dumbUpDownMix(static_cast<int16_t*>(aOut
), outChannels
,
190 static_cast<const int16_t*>(aIn
), inChannels
, aFrames
);
192 MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
198 mIn
.Layout() == AudioConfig::ChannelLayout::SMPTEDefault(mIn
.Layout()),
199 "Can only downmix input data in SMPTE layout");
200 if (inChannels
> 2) {
201 if (mIn
.Format() == AudioConfig::FORMAT_FLT
) {
202 // Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows
204 static const float dmatrix
[6][8][2] = {
205 /*3*/ {{0.5858f
, 0}, {0, 0.5858f
}, {0.4142f
, 0.4142f
}},
207 {{0.4226f
, 0}, {0, 0.4226f
}, {0.366f
, 0.2114f
}, {0.2114f
, 0.366f
}},
239 // Re-write the buffer with downmixed data
240 const float* in
= static_cast<const float*>(aIn
);
241 float* out
= static_cast<float*>(aOut
);
242 for (uint32_t i
= 0; i
< aFrames
; i
++) {
245 for (uint32_t j
= 0; j
< inChannels
; j
++) {
246 sampL
+= in
[i
* inChannels
+ j
] * dmatrix
[inChannels
- 3][j
][0];
247 sampR
+= in
[i
* inChannels
+ j
] * dmatrix
[inChannels
- 3][j
][1];
249 if (outChannels
== 2) {
253 *out
++ = (sampL
+ sampR
) * 0.5;
256 } else if (mIn
.Format() == AudioConfig::FORMAT_S16
) {
257 // Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows
258 // 5-8. Coefficients in Q14.
259 static const int16_t dmatrix
[6][8][2] = {
260 /*3*/ {{9598, 0}, {0, 9598}, {6786, 6786}},
261 /*4*/ {{6925, 0}, {0, 6925}, {5997, 3462}, {3462, 5997}},
263 {{10663, 0}, {0, 10663}, {7540, 7540}, {9234, 5331}, {5331, 9234}},
288 // Re-write the buffer with downmixed data
289 const int16_t* in
= static_cast<const int16_t*>(aIn
);
290 int16_t* out
= static_cast<int16_t*>(aOut
);
291 for (uint32_t i
= 0; i
< aFrames
; i
++) {
294 for (uint32_t j
= 0; j
< inChannels
; j
++) {
295 sampL
+= in
[i
* inChannels
+ j
] * dmatrix
[inChannels
- 3][j
][0];
296 sampR
+= in
[i
* inChannels
+ j
] * dmatrix
[inChannels
- 3][j
][1];
298 sampL
= clipTo15((sampL
+ 8192) >> 14);
299 sampR
= clipTo15((sampR
+ 8192) >> 14);
300 if (outChannels
== 2) {
304 *out
++ = (sampL
+ sampR
) * 0.5;
308 MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
313 MOZ_DIAGNOSTIC_ASSERT(inChannels
== 2 && outChannels
== 1);
314 if (mIn
.Format() == AudioConfig::FORMAT_FLT
) {
315 const float* in
= static_cast<const float*>(aIn
);
316 float* out
= static_cast<float*>(aOut
);
317 for (size_t fIdx
= 0; fIdx
< aFrames
; ++fIdx
) {
319 // The sample of the buffer would be interleaved.
320 sample
= (in
[fIdx
* inChannels
] + in
[fIdx
* inChannels
+ 1]) * 0.5;
323 } else if (mIn
.Format() == AudioConfig::FORMAT_S16
) {
324 const int16_t* in
= static_cast<const int16_t*>(aIn
);
325 int16_t* out
= static_cast<int16_t*>(aOut
);
326 for (size_t fIdx
= 0; fIdx
< aFrames
; ++fIdx
) {
327 int32_t sample
= 0.0;
328 // The sample of the buffer would be interleaved.
329 sample
= (in
[fIdx
* inChannels
] + in
[fIdx
* inChannels
+ 1]) * 0.5;
333 MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
338 size_t AudioConverter::ResampleAudio(void* aOut
, const void* aIn
,
343 uint32_t outframes
= ResampleRecipientFrames(aFrames
);
344 uint32_t inframes
= aFrames
;
347 if (mOut
.Format() == AudioConfig::FORMAT_FLT
) {
348 const float* in
= reinterpret_cast<const float*>(aIn
);
349 float* out
= reinterpret_cast<float*>(aOut
);
350 error
= speex_resampler_process_interleaved_float(mResampler
, in
, &inframes
,
352 } else if (mOut
.Format() == AudioConfig::FORMAT_S16
) {
353 const int16_t* in
= reinterpret_cast<const int16_t*>(aIn
);
354 int16_t* out
= reinterpret_cast<int16_t*>(aOut
);
355 error
= speex_resampler_process_interleaved_int(mResampler
, in
, &inframes
,
358 MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
359 error
= RESAMPLER_ERR_ALLOC_FAILED
;
361 MOZ_ASSERT(error
== RESAMPLER_ERR_SUCCESS
);
362 if (error
!= RESAMPLER_ERR_SUCCESS
) {
363 speex_resampler_destroy(mResampler
);
364 mResampler
= nullptr;
367 MOZ_ASSERT(inframes
== aFrames
, "Some frames will be dropped");
371 void AudioConverter::RecreateResampler() {
373 speex_resampler_destroy(mResampler
);
376 mResampler
= speex_resampler_init(mOut
.Channels(), mIn
.Rate(), mOut
.Rate(),
377 SPEEX_RESAMPLER_QUALITY_DEFAULT
, &error
);
379 if (error
== RESAMPLER_ERR_SUCCESS
) {
380 speex_resampler_skip_zeros(mResampler
);
382 NS_WARNING("Failed to initialize resampler.");
383 mResampler
= nullptr;
387 size_t AudioConverter::DrainResampler(void* aOut
) {
391 int frames
= speex_resampler_get_input_latency(mResampler
);
392 AlignedByteBuffer
buffer(FramesOutToBytes(frames
));
397 frames
= ResampleAudio(aOut
, buffer
.Data(), frames
);
398 // Tore down the resampler as it's easier than handling follow-up.
403 size_t AudioConverter::UpmixAudio(void* aOut
, const void* aIn
,
404 size_t aFrames
) const {
405 MOZ_ASSERT(mIn
.Format() == AudioConfig::FORMAT_S16
||
406 mIn
.Format() == AudioConfig::FORMAT_FLT
);
407 MOZ_ASSERT(mIn
.Channels() < mOut
.Channels());
408 MOZ_ASSERT(mIn
.Channels() == 1, "Can only upmix mono for now");
409 MOZ_ASSERT(mOut
.Channels() == 2, "Can only upmix to stereo for now");
411 if (!mIn
.Layout().IsValid() || !mOut
.Layout().IsValid() ||
412 mOut
.Channels() != 2) {
413 // Dumb copy the channels and insert silence for the extra channels.
414 if (mIn
.Format() == AudioConfig::FORMAT_FLT
) {
415 dumbUpDownMix(static_cast<float*>(aOut
), mOut
.Channels(),
416 static_cast<const float*>(aIn
), mIn
.Channels(), aFrames
);
417 } else if (mIn
.Format() == AudioConfig::FORMAT_S16
) {
418 dumbUpDownMix(static_cast<int16_t*>(aOut
), mOut
.Channels(),
419 static_cast<const int16_t*>(aIn
), mIn
.Channels(), aFrames
);
421 MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
426 // Upmix mono to stereo.
427 // This is a very dumb mono to stereo upmixing, power levels are preserved
428 // following the calculation: left = right = -3dB*mono.
429 if (mIn
.Format() == AudioConfig::FORMAT_FLT
) {
430 const float m3db
= std::sqrt(0.5); // -3dB = sqrt(1/2)
431 const float* in
= static_cast<const float*>(aIn
);
432 float* out
= static_cast<float*>(aOut
);
433 for (size_t fIdx
= 0; fIdx
< aFrames
; ++fIdx
) {
434 float sample
= in
[fIdx
] * m3db
;
435 // The samples of the buffer would be interleaved.
439 } else if (mIn
.Format() == AudioConfig::FORMAT_S16
) {
440 const int16_t* in
= static_cast<const int16_t*>(aIn
);
441 int16_t* out
= static_cast<int16_t*>(aOut
);
442 for (size_t fIdx
= 0; fIdx
< aFrames
; ++fIdx
) {
444 ((int32_t)in
[fIdx
] * 11585) >> 14; // close enough to i*sqrt(0.5)
445 // The samples of the buffer would be interleaved.
450 MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
456 size_t AudioConverter::ResampleRecipientFrames(size_t aFrames
) const {
457 if (!aFrames
&& mIn
.Rate() != mOut
.Rate()) {
461 // We drain by pushing in get_input_latency() samples of 0
462 aFrames
= speex_resampler_get_input_latency(mResampler
);
464 return (uint64_t)aFrames
* mOut
.Rate() / mIn
.Rate() + 1;
467 size_t AudioConverter::FramesOutToSamples(size_t aFrames
) const {
468 return aFrames
* mOut
.Channels();
471 size_t AudioConverter::SamplesInToFrames(size_t aSamples
) const {
472 return aSamples
/ mIn
.Channels();
475 size_t AudioConverter::FramesOutToBytes(size_t aFrames
) const {
476 return FramesOutToSamples(aFrames
) * AudioConfig::SampleSize(mOut
.Format());
478 } // namespace mozilla