1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #ifndef MOZILLA_AUDIOSEGMENT_H_
7 #define MOZILLA_AUDIOSEGMENT_H_
9 #include <speex/speex_resampler.h>
10 #include "MediaSegment.h"
11 #include "AudioSampleFormat.h"
12 #include "AudioChannelFormat.h"
13 #include "SharedBuffer.h"
14 #include "WebAudioUtils.h"
15 #include "mozilla/ScopeExit.h"
16 #include "nsAutoRef.h"
17 #ifdef MOZILLA_INTERNAL_API
18 # include "mozilla/TimeStamp.h"
25 } // namespace mozilla
26 MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::AudioChunk
)
29 * This allows compilation of nsTArray<AudioSegment> and
30 * AutoTArray<AudioSegment> since without it, static analysis fails on the
31 * mChunks member being a non-memmovable AutoTArray.
33 * Note that AudioSegment(const AudioSegment&) is deleted, so this should
34 * never come into effect.
36 MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::AudioSegment
)
41 class SharedChannelArrayBuffer
: public ThreadSharedObject
{
43 explicit SharedChannelArrayBuffer(nsTArray
<nsTArray
<T
> >&& aBuffers
)
44 : mBuffers(std::move(aBuffers
)) {}
46 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const override
{
48 amount
+= mBuffers
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
49 for (size_t i
= 0; i
< mBuffers
.Length(); i
++) {
50 amount
+= mBuffers
[i
].ShallowSizeOfExcludingThis(aMallocSizeOf
);
56 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const override
{
57 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
60 nsTArray
<nsTArray
<T
> > mBuffers
;
66 * For auto-arrays etc, guess this as the common number of channels.
68 const int GUESS_AUDIO_CHANNELS
= 2;
70 // We ensure that the graph advances in steps that are multiples of the Web
72 const uint32_t WEBAUDIO_BLOCK_SIZE_BITS
= 7;
73 const uint32_t WEBAUDIO_BLOCK_SIZE
= 1 << WEBAUDIO_BLOCK_SIZE_BITS
;
75 template <typename SrcT
, typename DestT
>
76 static void InterleaveAndConvertBuffer(const SrcT
* const* aSourceChannels
,
77 uint32_t aLength
, float aVolume
,
78 uint32_t aChannels
, DestT
* aOutput
) {
79 DestT
* output
= aOutput
;
80 for (size_t i
= 0; i
< aLength
; ++i
) {
81 for (size_t channel
= 0; channel
< aChannels
; ++channel
) {
83 ConvertAudioSample
<float>(aSourceChannels
[channel
][i
]) * aVolume
;
84 *output
= FloatToAudioSample
<DestT
>(v
);
90 template <typename SrcT
, typename DestT
>
91 static void DeinterleaveAndConvertBuffer(const SrcT
* aSourceBuffer
,
92 uint32_t aFrames
, uint32_t aChannels
,
94 for (size_t i
= 0; i
< aChannels
; i
++) {
95 size_t interleavedIndex
= i
;
96 for (size_t j
= 0; j
< aFrames
; j
++) {
98 ConvertAudioSample
<DestT
>(aSourceBuffer
[interleavedIndex
]);
99 interleavedIndex
+= aChannels
;
104 class SilentChannel
{
106 static const int AUDIO_PROCESSING_FRAMES
= 640; /* > 10ms of 48KHz audio */
108 gZeroChannel
[MAX_AUDIO_SAMPLE_SIZE
* AUDIO_PROCESSING_FRAMES
];
109 // We take advantage of the fact that zero in float and zero in int have the
110 // same all-zeros bit layout.
111 template <typename T
>
112 static const T
* ZeroChannel();
116 * Given an array of input channels (aChannelData), downmix to aOutputChannels,
117 * interleave the channel data. A total of aOutputChannels*aDuration
118 * interleaved samples will be copied to a channel buffer in aOutput.
120 template <typename SrcT
, typename DestT
>
121 void DownmixAndInterleave(Span
<const SrcT
* const> aChannelData
,
122 int32_t aDuration
, float aVolume
,
123 uint32_t aOutputChannels
, DestT
* aOutput
) {
124 if (aChannelData
.Length() == aOutputChannels
) {
125 InterleaveAndConvertBuffer(aChannelData
.Elements(), aDuration
, aVolume
,
126 aOutputChannels
, aOutput
);
128 AutoTArray
<SrcT
*, GUESS_AUDIO_CHANNELS
> outputChannelData
;
130 SilentChannel::AUDIO_PROCESSING_FRAMES
* GUESS_AUDIO_CHANNELS
>
132 outputChannelData
.SetLength(aOutputChannels
);
133 outputBuffers
.SetLength(aDuration
* aOutputChannels
);
134 for (uint32_t i
= 0; i
< aOutputChannels
; i
++) {
135 outputChannelData
[i
] = outputBuffers
.Elements() + aDuration
* i
;
137 AudioChannelsDownMix
<SrcT
, SrcT
>(aChannelData
, outputChannelData
,
139 InterleaveAndConvertBuffer(outputChannelData
.Elements(), aDuration
, aVolume
,
140 aOutputChannels
, aOutput
);
145 * An AudioChunk represents a multi-channel buffer of audio samples.
146 * It references an underlying ThreadSharedObject which manages the lifetime
147 * of the buffer. An AudioChunk maintains its own duration and channel data
148 * pointers so it can represent a subinterval of a buffer without copying.
149 * An AudioChunk can store its individual channels anywhere; it maintains
150 * separate pointers to each channel's buffer.
153 using SampleFormat
= mozilla::AudioSampleFormat
;
155 AudioChunk() = default;
157 template <typename T
>
158 AudioChunk(already_AddRefed
<ThreadSharedObject
> aBuffer
,
159 const nsTArray
<const T
*>& aChannelData
, TrackTime aDuration
,
160 PrincipalHandle aPrincipalHandle
)
161 : mDuration(aDuration
),
163 mBufferFormat(AudioSampleTypeToFormat
<T
>::Format
),
164 mPrincipalHandle(std::move(aPrincipalHandle
)) {
165 MOZ_ASSERT(!mBuffer
== aChannelData
.IsEmpty(), "Appending invalid data ?");
166 for (const T
* data
: aChannelData
) {
167 mChannelData
.AppendElement(data
);
172 void SliceTo(TrackTime aStart
, TrackTime aEnd
) {
173 MOZ_ASSERT(aStart
>= 0, "Slice out of bounds: invalid start");
174 MOZ_ASSERT(aStart
< aEnd
, "Slice out of bounds: invalid range");
175 MOZ_ASSERT(aEnd
<= mDuration
, "Slice out of bounds: invalid end");
178 MOZ_ASSERT(aStart
< INT32_MAX
,
179 "Can't slice beyond 32-bit sample lengths");
180 for (uint32_t channel
= 0; channel
< mChannelData
.Length(); ++channel
) {
181 mChannelData
[channel
] = AddAudioSampleOffset(
182 mChannelData
[channel
], mBufferFormat
, int32_t(aStart
));
185 mDuration
= aEnd
- aStart
;
187 TrackTime
GetDuration() const { return mDuration
; }
188 bool CanCombineWithFollowing(const AudioChunk
& aOther
) const {
189 if (aOther
.mBuffer
!= mBuffer
) {
195 if (aOther
.mVolume
!= mVolume
) {
198 if (aOther
.mPrincipalHandle
!= mPrincipalHandle
) {
201 NS_ASSERTION(aOther
.mBufferFormat
== mBufferFormat
,
202 "Wrong metadata about buffer");
203 NS_ASSERTION(aOther
.mChannelData
.Length() == mChannelData
.Length(),
204 "Mismatched channel count");
205 if (mDuration
> INT32_MAX
) {
208 for (uint32_t channel
= 0; channel
< mChannelData
.Length(); ++channel
) {
209 if (aOther
.mChannelData
[channel
] !=
210 AddAudioSampleOffset(mChannelData
[channel
], mBufferFormat
,
211 int32_t(mDuration
))) {
217 bool IsNull() const { return mBuffer
== nullptr; }
218 void SetNull(TrackTime aDuration
) {
220 mChannelData
.Clear();
221 mDuration
= aDuration
;
223 mBufferFormat
= AUDIO_FORMAT_SILENCE
;
224 mPrincipalHandle
= PRINCIPAL_HANDLE_NONE
;
227 uint32_t ChannelCount() const { return mChannelData
.Length(); }
229 bool IsMuted() const { return mVolume
== 0.0f
; }
231 size_t SizeOfExcludingThisIfUnshared(MallocSizeOf aMallocSizeOf
) const {
232 return SizeOfExcludingThis(aMallocSizeOf
, true);
235 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
, bool aUnshared
) const {
239 // - mBuffer - Can hold data that is also in the decoded audio queue. If it
240 // is not shared, or unshared == false it gets counted.
241 if (mBuffer
&& (!aUnshared
|| !mBuffer
->IsShared())) {
242 amount
+= mBuffer
->SizeOfIncludingThis(aMallocSizeOf
);
245 // Memory in the array is owned by mBuffer.
246 amount
+= mChannelData
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
250 template <typename T
>
251 Span
<const T
* const> ChannelData() const {
252 MOZ_ASSERT(AudioSampleTypeToFormat
<T
>::Format
== mBufferFormat
);
253 return Span(reinterpret_cast<const T
* const*>(mChannelData
.Elements()),
254 mChannelData
.Length());
258 * ChannelFloatsForWrite() should be used only when mBuffer is owned solely
259 * by the calling thread.
261 template <typename T
>
262 T
* ChannelDataForWrite(size_t aChannel
) {
263 MOZ_ASSERT(AudioSampleTypeToFormat
<T
>::Format
== mBufferFormat
);
264 MOZ_ASSERT(!mBuffer
->IsShared());
265 return static_cast<T
*>(const_cast<void*>(mChannelData
[aChannel
]));
268 template <typename T
>
269 static AudioChunk
FromInterleavedBuffer(
270 const T
* aBuffer
, size_t aFrames
, uint32_t aChannels
,
271 const PrincipalHandle
& aPrincipalHandle
) {
272 CheckedInt
<size_t> bufferSize(sizeof(T
));
273 bufferSize
*= aFrames
;
274 bufferSize
*= aChannels
;
275 RefPtr
<SharedBuffer
> buffer
= SharedBuffer::Create(bufferSize
);
277 AutoTArray
<T
*, 8> deinterleaved
;
278 if (aChannels
== 1) {
279 PodCopy(static_cast<T
*>(buffer
->Data()), aBuffer
, aFrames
);
280 deinterleaved
.AppendElement(static_cast<T
*>(buffer
->Data()));
282 deinterleaved
.SetLength(aChannels
);
283 T
* samples
= static_cast<T
*>(buffer
->Data());
286 for (uint32_t i
= 0; i
< aChannels
; ++i
) {
287 deinterleaved
[i
] = samples
+ offset
;
291 DeinterleaveAndConvertBuffer(aBuffer
, static_cast<uint32_t>(aFrames
),
292 aChannels
, deinterleaved
.Elements());
295 AutoTArray
<const T
*, GUESS_AUDIO_CHANNELS
> channelData
;
296 channelData
.AppendElements(deinterleaved
);
297 return AudioChunk(buffer
.forget(), channelData
,
298 static_cast<TrackTime
>(aFrames
), aPrincipalHandle
);
301 const PrincipalHandle
& GetPrincipalHandle() const { return mPrincipalHandle
; }
303 // aOutputChannels must contain pointers to channel data of length mDuration.
304 void DownMixTo(Span
<AudioDataValue
* const> aOutputChannels
) const;
306 TrackTime mDuration
= 0; // in frames within the buffer
307 RefPtr
<ThreadSharedObject
> mBuffer
; // the buffer object whose lifetime is
308 // managed; null means data is all zeroes
309 // one pointer per channel; empty if and only if mBuffer is null
310 CopyableAutoTArray
<const void*, GUESS_AUDIO_CHANNELS
> mChannelData
;
311 float mVolume
= 1.0f
; // volume multiplier to apply
312 // format of frames in mBuffer (or silence if mBuffer is null)
313 SampleFormat mBufferFormat
= AUDIO_FORMAT_SILENCE
;
314 // principalHandle for the data in this chunk.
315 // This can be compared to an nsIPrincipal* when back on main thread.
316 PrincipalHandle mPrincipalHandle
= PRINCIPAL_HANDLE_NONE
;
320 * A list of audio samples consisting of a sequence of slices of SharedBuffers.
321 * The audio rate is determined by the track, not stored in this class.
323 class AudioSegment final
: public MediaSegmentBase
<AudioSegment
, AudioChunk
> {
324 // The channel count that MaxChannelCount() returned last time it was called.
325 uint32_t mMemoizedMaxChannelCount
= 0;
328 typedef mozilla::AudioSampleFormat SampleFormat
;
330 AudioSegment() : MediaSegmentBase
<AudioSegment
, AudioChunk
>(AUDIO
) {}
332 AudioSegment(AudioSegment
&& aSegment
) = default;
334 AudioSegment(const AudioSegment
&) = delete;
335 AudioSegment
& operator=(const AudioSegment
&) = delete;
337 ~AudioSegment() = default;
339 // Resample the whole segment in place. `aResampler` is an instance of a
340 // resampler, initialized with `aResamplerChannelCount` channels. If this
341 // function finds a chunk with more channels, `aResampler` is destroyed and a
342 // new resampler is created, and `aResamplerChannelCount` is updated with the
343 // new channel count value.
344 void ResampleChunks(nsAutoRef
<SpeexResamplerState
>& aResampler
,
345 uint32_t* aResamplerChannelCount
, uint32_t aInRate
,
348 template <typename T
>
349 void AppendFrames(already_AddRefed
<ThreadSharedObject
> aBuffer
,
350 const nsTArray
<const T
*>& aChannelData
, TrackTime aDuration
,
351 const PrincipalHandle
& aPrincipalHandle
) {
352 AppendAndConsumeChunk(AudioChunk(std::move(aBuffer
), aChannelData
,
353 aDuration
, aPrincipalHandle
));
355 void AppendSegment(const AudioSegment
* aSegment
) {
356 MOZ_ASSERT(aSegment
);
358 for (const AudioChunk
& c
: aSegment
->mChunks
) {
359 AudioChunk
* chunk
= AppendChunk(c
.GetDuration());
360 chunk
->mBuffer
= c
.mBuffer
;
361 chunk
->mChannelData
= c
.mChannelData
;
362 chunk
->mBufferFormat
= c
.mBufferFormat
;
363 chunk
->mPrincipalHandle
= c
.mPrincipalHandle
;
366 template <typename T
>
367 void AppendFromInterleavedBuffer(const T
* aBuffer
, size_t aFrames
,
369 const PrincipalHandle
& aPrincipalHandle
) {
370 AppendAndConsumeChunk(AudioChunk::FromInterleavedBuffer
<T
>(
371 aBuffer
, aFrames
, aChannels
, aPrincipalHandle
));
373 // Write the segement data into an interleaved buffer. Do mixing if the
374 // AudioChunk's channel count in the segment is different from aChannels.
375 // Returns sample count of the converted audio data. The converted data will
376 // be stored into aBuffer.
377 size_t WriteToInterleavedBuffer(nsTArray
<AudioDataValue
>& aBuffer
,
378 uint32_t aChannels
) const;
379 // Consumes aChunk, and append it to the segment if its duration is not zero.
380 void AppendAndConsumeChunk(AudioChunk
&& aChunk
) {
382 AudioChunk
* chunk
= &unused
;
384 // Always consume aChunk. The chunk's mBuffer can be non-null even if its
386 auto consume
= MakeScopeExit([&] {
387 chunk
->mBuffer
= std::move(aChunk
.mBuffer
);
388 chunk
->mChannelData
= std::move(aChunk
.mChannelData
);
390 MOZ_ASSERT(chunk
->mBuffer
|| chunk
->mChannelData
.IsEmpty(),
391 "Appending invalid data ?");
393 chunk
->mVolume
= aChunk
.mVolume
;
394 chunk
->mBufferFormat
= aChunk
.mBufferFormat
;
395 chunk
->mPrincipalHandle
= std::move(aChunk
.mPrincipalHandle
);
398 if (aChunk
.GetDuration() == 0) {
402 if (!mChunks
.IsEmpty() &&
403 mChunks
.LastElement().CanCombineWithFollowing(aChunk
)) {
404 mChunks
.LastElement().mDuration
+= aChunk
.GetDuration();
405 mDuration
+= aChunk
.GetDuration();
409 chunk
= AppendChunk(aChunk
.mDuration
);
411 void ApplyVolume(float aVolume
);
412 // Mix the segment into a mixer, keeping it planar, up or down mixing to
413 // aChannelCount channels.
414 void Mix(AudioMixer
& aMixer
, uint32_t aChannelCount
, uint32_t aSampleRate
);
416 // Returns the maximum channel count across all chunks in this segment.
417 // Should there be no chunk with a channel count we return the memoized return
418 // value from last time this method was called.
419 uint32_t MaxChannelCount() {
420 uint32_t channelCount
= 0;
421 for (ChunkIterator
ci(*this); !ci
.IsEnded(); ci
.Next()) {
422 if (ci
->ChannelCount()) {
423 channelCount
= std::max(channelCount
, ci
->ChannelCount());
426 if (channelCount
== 0) {
427 return mMemoizedMaxChannelCount
;
429 return mMemoizedMaxChannelCount
= channelCount
;
432 static Type
StaticType() { return AUDIO
; }
434 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const override
{
435 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
438 PrincipalHandle
GetOldestPrinciple() const {
439 const AudioChunk
* chunk
= mChunks
.IsEmpty() ? nullptr : &mChunks
[0];
440 return chunk
? chunk
->GetPrincipalHandle() : PRINCIPAL_HANDLE_NONE
;
443 // Iterate on each chunks until the input function returns true.
444 template <typename Function
>
445 void IterateOnChunks(const Function
&& aFunction
) {
446 for (uint32_t idx
= 0; idx
< mChunks
.Length(); idx
++) {
447 if (aFunction(&mChunks
[idx
])) {
454 template <typename T
>
455 void Resample(nsAutoRef
<SpeexResamplerState
>& aResampler
,
456 uint32_t* aResamplerChannelCount
, uint32_t aInRate
,
460 template <typename SrcT
>
461 void WriteChunk(const AudioChunk
& aChunk
, uint32_t aOutputChannels
,
462 float aVolume
, AudioDataValue
* aOutputBuffer
) {
463 CopyableAutoTArray
<const SrcT
*, GUESS_AUDIO_CHANNELS
> channelData
;
464 channelData
.AppendElements(aChunk
.ChannelData
<SrcT
>());
466 if (channelData
.Length() < aOutputChannels
) {
467 // Up-mix. Note that this might actually make channelData have more
468 // than aOutputChannels temporarily.
469 AudioChannelsUpMix(&channelData
, aOutputChannels
,
470 SilentChannel::ZeroChannel
<SrcT
>());
472 if (channelData
.Length() > aOutputChannels
) {
474 DownmixAndInterleave
<SrcT
>(channelData
, aChunk
.mDuration
, aVolume
,
475 aOutputChannels
, aOutputBuffer
);
477 InterleaveAndConvertBuffer(channelData
.Elements(), aChunk
.mDuration
,
478 aVolume
, aOutputChannels
, aOutputBuffer
);
482 } // namespace mozilla
484 #endif /* MOZILLA_AUDIOSEGMENT_H_ */