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 "MediaSegment.h"
10 #include "AudioSampleFormat.h"
11 #include "AudioChannelFormat.h"
12 #include "SharedBuffer.h"
13 #include "WebAudioUtils.h"
14 #ifdef MOZILLA_INTERNAL_API
15 #include "mozilla/TimeStamp.h"
23 DECLARE_USE_COPY_CONSTRUCTORS(mozilla::AudioChunk
)
26 * This allows compilation of nsTArray<AudioSegment> and
27 * AutoTArray<AudioSegment> since without it, static analysis fails on the
28 * mChunks member being a non-memmovable AutoTArray.
30 * Note that AudioSegment(const AudioSegment&) is deleted, so this should
31 * never come into effect.
33 DECLARE_USE_COPY_CONSTRUCTORS(mozilla::AudioSegment
)
38 class SharedChannelArrayBuffer
: public ThreadSharedObject
{
40 explicit SharedChannelArrayBuffer(nsTArray
<nsTArray
<T
> >* aBuffers
)
42 mBuffers
.SwapElements(*aBuffers
);
45 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
58 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
61 nsTArray
<nsTArray
<T
> > mBuffers
;
67 * For auto-arrays etc, guess this as the common number of channels.
69 const int GUESS_AUDIO_CHANNELS
= 2;
71 // We ensure that the graph advances in steps that are multiples of the Web
73 const uint32_t WEBAUDIO_BLOCK_SIZE_BITS
= 7;
74 const uint32_t WEBAUDIO_BLOCK_SIZE
= 1 << WEBAUDIO_BLOCK_SIZE_BITS
;
76 template <typename SrcT
, typename DestT
>
78 InterleaveAndConvertBuffer(const SrcT
* const* aSourceChannels
,
79 uint32_t aLength
, float aVolume
,
83 DestT
* output
= aOutput
;
84 for (size_t i
= 0; i
< aLength
; ++i
) {
85 for (size_t channel
= 0; channel
< aChannels
; ++channel
) {
86 float v
= AudioSampleToFloat(aSourceChannels
[channel
][i
])*aVolume
;
87 *output
= FloatToAudioSample
<DestT
>(v
);
93 template <typename SrcT
, typename DestT
>
95 DeinterleaveAndConvertBuffer(const SrcT
* aSourceBuffer
,
96 uint32_t aFrames
, uint32_t aChannels
,
99 for (size_t i
= 0; i
< aChannels
; i
++) {
100 size_t interleavedIndex
= i
;
101 for (size_t j
= 0; j
< aFrames
; j
++) {
102 ConvertAudioSample(aSourceBuffer
[interleavedIndex
],
104 interleavedIndex
+= aChannels
;
112 static const int AUDIO_PROCESSING_FRAMES
= 640; /* > 10ms of 48KHz audio */
113 static const uint8_t gZeroChannel
[MAX_AUDIO_SAMPLE_SIZE
*AUDIO_PROCESSING_FRAMES
];
114 // We take advantage of the fact that zero in float and zero in int have the
115 // same all-zeros bit layout.
117 static const T
* ZeroChannel();
122 * Given an array of input channels (aChannelData), downmix to aOutputChannels,
123 * interleave the channel data. A total of aOutputChannels*aDuration
124 * interleaved samples will be copied to a channel buffer in aOutput.
126 template <typename SrcT
, typename DestT
>
128 DownmixAndInterleave(const nsTArray
<const SrcT
*>& aChannelData
,
129 int32_t aDuration
, float aVolume
, uint32_t aOutputChannels
,
133 if (aChannelData
.Length() == aOutputChannels
) {
134 InterleaveAndConvertBuffer(aChannelData
.Elements(),
135 aDuration
, aVolume
, aOutputChannels
, aOutput
);
137 AutoTArray
<SrcT
*,GUESS_AUDIO_CHANNELS
> outputChannelData
;
138 AutoTArray
<SrcT
, SilentChannel::AUDIO_PROCESSING_FRAMES
* GUESS_AUDIO_CHANNELS
> outputBuffers
;
139 outputChannelData
.SetLength(aOutputChannels
);
140 outputBuffers
.SetLength(aDuration
* aOutputChannels
);
141 for (uint32_t i
= 0; i
< aOutputChannels
; i
++) {
142 outputChannelData
[i
] = outputBuffers
.Elements() + aDuration
* i
;
144 AudioChannelsDownMix(aChannelData
,
145 outputChannelData
.Elements(),
148 InterleaveAndConvertBuffer(outputChannelData
.Elements(),
149 aDuration
, aVolume
, aOutputChannels
, aOutput
);
154 * An AudioChunk represents a multi-channel buffer of audio samples.
155 * It references an underlying ThreadSharedObject which manages the lifetime
156 * of the buffer. An AudioChunk maintains its own duration and channel data
157 * pointers so it can represent a subinterval of a buffer without copying.
158 * An AudioChunk can store its individual channels anywhere; it maintains
159 * separate pointers to each channel's buffer.
162 typedef mozilla::AudioSampleFormat SampleFormat
;
165 void SliceTo(StreamTime aStart
, StreamTime aEnd
)
167 MOZ_ASSERT(aStart
>= 0 && aStart
< aEnd
&& aEnd
<= mDuration
,
168 "Slice out of bounds");
170 MOZ_ASSERT(aStart
< INT32_MAX
, "Can't slice beyond 32-bit sample lengths");
171 for (uint32_t channel
= 0; channel
< mChannelData
.Length(); ++channel
) {
172 mChannelData
[channel
] = AddAudioSampleOffset(mChannelData
[channel
],
173 mBufferFormat
, int32_t(aStart
));
176 mDuration
= aEnd
- aStart
;
178 StreamTime
GetDuration() const { return mDuration
; }
179 bool CanCombineWithFollowing(const AudioChunk
& aOther
) const
181 if (aOther
.mBuffer
!= mBuffer
) {
187 if (aOther
.mVolume
!= mVolume
) {
190 if (aOther
.mPrincipalHandle
!= mPrincipalHandle
) {
193 NS_ASSERTION(aOther
.mBufferFormat
== mBufferFormat
,
194 "Wrong metadata about buffer");
195 NS_ASSERTION(aOther
.mChannelData
.Length() == mChannelData
.Length(),
196 "Mismatched channel count");
197 if (mDuration
> INT32_MAX
) {
200 for (uint32_t channel
= 0; channel
< mChannelData
.Length(); ++channel
) {
201 if (aOther
.mChannelData
[channel
] != AddAudioSampleOffset(mChannelData
[channel
],
202 mBufferFormat
, int32_t(mDuration
))) {
208 bool IsNull() const {
209 return mBuffer
== nullptr;
211 void SetNull(StreamTime aDuration
)
214 mChannelData
.Clear();
215 mDuration
= aDuration
;
217 mBufferFormat
= AUDIO_FORMAT_SILENCE
;
218 mPrincipalHandle
= PRINCIPAL_HANDLE_NONE
;
221 size_t ChannelCount() const { return mChannelData
.Length(); }
223 bool IsMuted() const { return mVolume
== 0.0f
; }
225 size_t SizeOfExcludingThisIfUnshared(MallocSizeOf aMallocSizeOf
) const
227 return SizeOfExcludingThis(aMallocSizeOf
, true);
230 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
, bool aUnshared
) const
235 // - mBuffer - Can hold data that is also in the decoded audio queue. If it
236 // is not shared, or unshared == false it gets counted.
237 if (mBuffer
&& (!aUnshared
|| !mBuffer
->IsShared())) {
238 amount
+= mBuffer
->SizeOfIncludingThis(aMallocSizeOf
);
241 // Memory in the array is owned by mBuffer.
242 amount
+= mChannelData
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
247 const nsTArray
<const T
*>& ChannelData() const
249 MOZ_ASSERT(AudioSampleTypeToFormat
<T
>::Format
== mBufferFormat
);
250 return *reinterpret_cast<const AutoTArray
<const T
*,GUESS_AUDIO_CHANNELS
>*>
255 * ChannelFloatsForWrite() should be used only when mBuffer is owned solely
256 * by the calling thread.
259 T
* ChannelDataForWrite(size_t aChannel
)
261 MOZ_ASSERT(AudioSampleTypeToFormat
<T
>::Format
== mBufferFormat
);
262 MOZ_ASSERT(!mBuffer
->IsShared());
263 return static_cast<T
*>(const_cast<void*>(mChannelData
[aChannel
]));
266 const PrincipalHandle
& GetPrincipalHandle() const { return mPrincipalHandle
; }
268 StreamTime mDuration
= 0; // in frames within the buffer
269 RefPtr
<ThreadSharedObject
> mBuffer
; // the buffer object whose lifetime is managed; null means data is all zeroes
270 // one pointer per channel; empty if and only if mBuffer is null
271 AutoTArray
<const void*,GUESS_AUDIO_CHANNELS
> mChannelData
;
272 float mVolume
= 1.0f
; // volume multiplier to apply
273 // format of frames in mBuffer (or silence if mBuffer is null)
274 SampleFormat mBufferFormat
= AUDIO_FORMAT_SILENCE
;
275 #ifdef MOZILLA_INTERNAL_API
276 mozilla::TimeStamp mTimeStamp
; // time at which this has been fetched from the MediaEngine
278 // principalHandle for the data in this chunk.
279 // This can be compared to an nsIPrincipal* when back on main thread.
280 PrincipalHandle mPrincipalHandle
= PRINCIPAL_HANDLE_NONE
;
284 * A list of audio samples consisting of a sequence of slices of SharedBuffers.
285 * The audio rate is determined by the track, not stored in this class.
287 class AudioSegment
: public MediaSegmentBase
<AudioSegment
, AudioChunk
> {
289 typedef mozilla::AudioSampleFormat SampleFormat
;
291 AudioSegment() : MediaSegmentBase
<AudioSegment
, AudioChunk
>(AUDIO
) {}
293 AudioSegment(AudioSegment
&& aSegment
)
294 : MediaSegmentBase
<AudioSegment
, AudioChunk
>(Move(aSegment
))
297 AudioSegment(const AudioSegment
&)=delete;
298 AudioSegment
& operator= (const AudioSegment
&)=delete;
302 // Resample the whole segment in place.
304 void Resample(SpeexResamplerState
* aResampler
, uint32_t aInRate
, uint32_t aOutRate
)
308 uint32_t segmentChannelCount
= ChannelCount();
311 for (ChunkIterator
ci(*this); !ci
.IsEnded(); ci
.Next()) {
312 AutoTArray
<nsTArray
<T
>, GUESS_AUDIO_CHANNELS
> output
;
313 AutoTArray
<const T
*, GUESS_AUDIO_CHANNELS
> bufferPtrs
;
315 // If this chunk is null, don't bother resampling, just alter its duration
317 c
.mDuration
= (c
.mDuration
* aOutRate
) / aInRate
;
318 mDuration
+= c
.mDuration
;
321 uint32_t channels
= c
.mChannelData
.Length();
322 MOZ_ASSERT(channels
== segmentChannelCount
);
323 output
.SetLength(channels
);
324 bufferPtrs
.SetLength(channels
);
325 uint32_t inFrames
= c
.mDuration
;
326 // Round up to allocate; the last frame may not be used.
327 NS_ASSERTION((UINT32_MAX
- aInRate
+ 1) / c
.mDuration
>= aOutRate
,
329 uint32_t outSize
= (c
.mDuration
* aOutRate
+ aInRate
- 1) / aInRate
;
330 for (uint32_t i
= 0; i
< channels
; i
++) {
331 T
* out
= output
[i
].AppendElements(outSize
);
332 uint32_t outFrames
= outSize
;
334 const T
* in
= static_cast<const T
*>(c
.mChannelData
[i
]);
335 dom::WebAudioUtils::SpeexResamplerProcess(aResampler
, i
,
338 MOZ_ASSERT(inFrames
== c
.mDuration
);
341 output
[i
].SetLength(outFrames
);
343 MOZ_ASSERT(channels
> 0);
344 c
.mDuration
= output
[0].Length();
345 c
.mBuffer
= new mozilla::SharedChannelArrayBuffer
<T
>(&output
);
346 for (uint32_t i
= 0; i
< channels
; i
++) {
347 c
.mChannelData
[i
] = bufferPtrs
[i
];
349 mDuration
+= c
.mDuration
;
353 void ResampleChunks(SpeexResamplerState
* aResampler
,
356 void AppendFrames(already_AddRefed
<ThreadSharedObject
> aBuffer
,
357 const nsTArray
<const float*>& aChannelData
,
358 int32_t aDuration
, const PrincipalHandle
& aPrincipalHandle
)
360 AudioChunk
* chunk
= AppendChunk(aDuration
);
361 chunk
->mBuffer
= aBuffer
;
363 MOZ_ASSERT(chunk
->mBuffer
|| aChannelData
.IsEmpty(), "Appending invalid data ?");
365 for (uint32_t channel
= 0; channel
< aChannelData
.Length(); ++channel
) {
366 chunk
->mChannelData
.AppendElement(aChannelData
[channel
]);
368 chunk
->mBufferFormat
= AUDIO_FORMAT_FLOAT32
;
369 #ifdef MOZILLA_INTERNAL_API
370 chunk
->mTimeStamp
= TimeStamp::Now();
372 chunk
->mPrincipalHandle
= aPrincipalHandle
;
374 void AppendFrames(already_AddRefed
<ThreadSharedObject
> aBuffer
,
375 const nsTArray
<const int16_t*>& aChannelData
,
376 int32_t aDuration
, const PrincipalHandle
& aPrincipalHandle
)
378 AudioChunk
* chunk
= AppendChunk(aDuration
);
379 chunk
->mBuffer
= aBuffer
;
381 MOZ_ASSERT(chunk
->mBuffer
|| aChannelData
.IsEmpty(), "Appending invalid data ?");
383 for (uint32_t channel
= 0; channel
< aChannelData
.Length(); ++channel
) {
384 chunk
->mChannelData
.AppendElement(aChannelData
[channel
]);
386 chunk
->mBufferFormat
= AUDIO_FORMAT_S16
;
387 #ifdef MOZILLA_INTERNAL_API
388 chunk
->mTimeStamp
= TimeStamp::Now();
390 chunk
->mPrincipalHandle
= aPrincipalHandle
;
393 // Consumes aChunk, and returns a pointer to the persistent copy of aChunk
395 AudioChunk
* AppendAndConsumeChunk(AudioChunk
* aChunk
)
397 AudioChunk
* chunk
= AppendChunk(aChunk
->mDuration
);
398 chunk
->mBuffer
= aChunk
->mBuffer
.forget();
399 chunk
->mChannelData
.SwapElements(aChunk
->mChannelData
);
401 MOZ_ASSERT(chunk
->mBuffer
|| aChunk
->mChannelData
.IsEmpty(), "Appending invalid data ?");
403 chunk
->mVolume
= aChunk
->mVolume
;
404 chunk
->mBufferFormat
= aChunk
->mBufferFormat
;
405 #ifdef MOZILLA_INTERNAL_API
406 chunk
->mTimeStamp
= TimeStamp::Now();
408 chunk
->mPrincipalHandle
= aChunk
->mPrincipalHandle
;
411 void ApplyVolume(float aVolume
);
412 // Mix the segment into a mixer, interleaved. This is useful to output a
413 // segment to a system audio callback. It up or down mixes to aChannelCount
415 void WriteTo(uint64_t aID
, AudioMixer
& aMixer
, uint32_t aChannelCount
,
416 uint32_t aSampleRate
);
417 // Mix the segment into a mixer, keeping it planar, up or down mixing to
418 // aChannelCount channels.
419 void Mix(AudioMixer
& aMixer
, uint32_t aChannelCount
, uint32_t aSampleRate
);
422 NS_WARNING_ASSERTION(
424 "Cannot query channel count on a AudioSegment with no chunks.");
425 // Find the first chunk that has non-zero channels. A chunk that hs zero
426 // channels is just silence and we can simply discard it.
427 for (ChunkIterator
ci(*this); !ci
.IsEnded(); ci
.Next()) {
428 if (ci
->ChannelCount()) {
429 return ci
->ChannelCount();
435 static Type
StaticType() { return AUDIO
; }
437 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const override
439 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
443 template<typename SrcT
>
444 void WriteChunk(AudioChunk
& aChunk
,
445 uint32_t aOutputChannels
,
446 AudioDataValue
* aOutputBuffer
)
448 AutoTArray
<const SrcT
*,GUESS_AUDIO_CHANNELS
> channelData
;
450 channelData
= aChunk
.ChannelData
<SrcT
>();
452 if (channelData
.Length() < aOutputChannels
) {
453 // Up-mix. Note that this might actually make channelData have more
454 // than aOutputChannels temporarily.
455 AudioChannelsUpMix(&channelData
, aOutputChannels
, SilentChannel::ZeroChannel
<SrcT
>());
457 if (channelData
.Length() > aOutputChannels
) {
459 DownmixAndInterleave(channelData
, aChunk
.mDuration
,
460 aChunk
.mVolume
, aOutputChannels
, aOutputBuffer
);
462 InterleaveAndConvertBuffer(channelData
.Elements(),
463 aChunk
.mDuration
, aChunk
.mVolume
,
471 } // namespace mozilla
473 #endif /* MOZILLA_AUDIOSEGMENT_H_ */