mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
Bug 1869043 pull mixed audio from AudioMixer instead of using a callback r=padenot
AudioCallbackDriver is the only client to benefit from a callback. StartMixing() is now required if reusing the AudioMixer because getting the mixed data after mixing no longer collapses the duration to zero. Differential Revision: https://phabricator.services.mozilla.com/D198221
This commit is contained in:
parent
7fa20ab1f1
commit
0a8a3cbddb
@ -34,13 +34,9 @@ AudioCaptureTrack::AudioCaptureTrack(TrackRate aRate)
|
||||
mStarted(false) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_COUNT_CTOR(AudioCaptureTrack);
|
||||
mMixer.AddCallback(WrapNotNull(this));
|
||||
}
|
||||
|
||||
AudioCaptureTrack::~AudioCaptureTrack() {
|
||||
MOZ_COUNT_DTOR(AudioCaptureTrack);
|
||||
mMixer.RemoveCallback(this);
|
||||
}
|
||||
AudioCaptureTrack::~AudioCaptureTrack() { MOZ_COUNT_DTOR(AudioCaptureTrack); }
|
||||
|
||||
void AudioCaptureTrack::Start() {
|
||||
QueueControlMessageWithNoShutdown(
|
||||
@ -87,8 +83,10 @@ void AudioCaptureTrack::ProcessInput(GraphTime aFrom, GraphTime aTo,
|
||||
}
|
||||
toMix.Mix(mMixer, MONO, Graph()->GraphRate());
|
||||
}
|
||||
// This calls MixerCallback below
|
||||
mMixer.FinishMixing();
|
||||
AudioChunk* mixed = mMixer.MixedChunk();
|
||||
MOZ_ASSERT(mixed->ChannelCount() == MONO);
|
||||
// Now we have mixed data, simply append it.
|
||||
GetData<AudioSegment>()->AppendAndConsumeChunk(std::move(*mixed));
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,10 +94,4 @@ uint32_t AudioCaptureTrack::NumberOfChannels() const {
|
||||
return GetData<AudioSegment>()->MaxChannelCount();
|
||||
}
|
||||
|
||||
void AudioCaptureTrack::MixerCallback(AudioChunk* aMixedBuffer,
|
||||
uint32_t aSampleRate) {
|
||||
MOZ_ASSERT(aMixedBuffer->ChannelCount() == MONO);
|
||||
// Now we have mixed data, simply append it.
|
||||
GetData<AudioSegment>()->AppendAndConsumeChunk(std::move(*aMixedBuffer));
|
||||
}
|
||||
} // namespace mozilla
|
||||
|
@ -18,8 +18,7 @@ class DOMMediaStream;
|
||||
/**
|
||||
* See MediaTrackGraph::CreateAudioCaptureTrack.
|
||||
*/
|
||||
class AudioCaptureTrack : public ProcessedMediaTrack,
|
||||
public MixerCallbackReceiver {
|
||||
class AudioCaptureTrack : public ProcessedMediaTrack {
|
||||
public:
|
||||
explicit AudioCaptureTrack(TrackRate aRate);
|
||||
virtual ~AudioCaptureTrack();
|
||||
@ -31,7 +30,6 @@ class AudioCaptureTrack : public ProcessedMediaTrack,
|
||||
uint32_t NumberOfChannels() const override;
|
||||
|
||||
protected:
|
||||
void MixerCallback(AudioChunk* aMixedBuffer, uint32_t aSampleRate) override;
|
||||
AudioMixer mMixer;
|
||||
bool mStarted;
|
||||
bool mTrackCreated;
|
||||
|
@ -10,7 +10,6 @@
|
||||
#include "AudioSegment.h"
|
||||
#include "AudioStream.h"
|
||||
#include "nsTArray.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
|
||||
@ -31,7 +30,7 @@ struct MixerCallbackReceiver {
|
||||
* length, sample rate, sample format and channel count. This class works with
|
||||
* planar buffers.
|
||||
*
|
||||
* When all the tracks have been mixed, calling FinishMixing will call back with
|
||||
* When all the tracks have been mixed, calling MixedChunk() will provide
|
||||
* a buffer containing the mixed audio data.
|
||||
*
|
||||
* This class is not thread safe.
|
||||
@ -40,12 +39,7 @@ class AudioMixer {
|
||||
public:
|
||||
AudioMixer() { mChunk.mBufferFormat = AUDIO_OUTPUT_FORMAT; }
|
||||
|
||||
~AudioMixer() {
|
||||
MixerCallback* cb;
|
||||
while ((cb = mCallbacks.popFirst())) {
|
||||
delete cb;
|
||||
}
|
||||
}
|
||||
~AudioMixer() = default;
|
||||
|
||||
void StartMixing() {
|
||||
mChunk.mDuration = 0;
|
||||
@ -53,18 +47,13 @@ class AudioMixer {
|
||||
}
|
||||
|
||||
/* Get the data from the mixer. This is supposed to be called when all the
|
||||
* tracks have been mixed in. The caller should not hold onto the data. */
|
||||
void FinishMixing() {
|
||||
* tracks have been mixed in. The caller MAY modify the chunk but MUST clear
|
||||
* mBuffer if its data needs to survive the next call to Mix(). */
|
||||
AudioChunk* MixedChunk() {
|
||||
MOZ_ASSERT(mSampleRate, "Mix not called for this cycle?");
|
||||
for (MixerCallback* cb = mCallbacks.getFirst(); cb != nullptr;
|
||||
cb = cb->getNext()) {
|
||||
MixerCallbackReceiver* receiver = cb->mReceiver;
|
||||
MOZ_ASSERT(receiver);
|
||||
receiver->MixerCallback(&mChunk, mSampleRate);
|
||||
}
|
||||
mChunk.mDuration = 0;
|
||||
mSampleRate = 0;
|
||||
}
|
||||
return &mChunk;
|
||||
};
|
||||
|
||||
/* Add a buffer to the mix. The buffer can be null if there's nothing to mix
|
||||
* but the callback is still needed. */
|
||||
@ -91,32 +80,6 @@ class AudioMixer {
|
||||
}
|
||||
}
|
||||
|
||||
void AddCallback(NotNull<MixerCallbackReceiver*> aReceiver) {
|
||||
mCallbacks.insertBack(new MixerCallback(aReceiver));
|
||||
}
|
||||
|
||||
bool FindCallback(MixerCallbackReceiver* aReceiver) {
|
||||
for (MixerCallback* cb = mCallbacks.getFirst(); cb != nullptr;
|
||||
cb = cb->getNext()) {
|
||||
if (cb->mReceiver == aReceiver) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RemoveCallback(MixerCallbackReceiver* aReceiver) {
|
||||
for (MixerCallback* cb = mCallbacks.getFirst(); cb != nullptr;
|
||||
cb = cb->getNext()) {
|
||||
if (cb->mReceiver == aReceiver) {
|
||||
cb->remove();
|
||||
delete cb;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void EnsureCapacityAndSilence() {
|
||||
uint32_t sampleCount = mChunk.mDuration * mChunk.ChannelCount();
|
||||
@ -136,15 +99,6 @@ class AudioMixer {
|
||||
PodZero(mChunk.ChannelDataForWrite<AudioDataValue>(0), sampleCount);
|
||||
}
|
||||
|
||||
class MixerCallback : public LinkedListElement<MixerCallback> {
|
||||
public:
|
||||
explicit MixerCallback(NotNull<MixerCallbackReceiver*> aReceiver)
|
||||
: mReceiver(aReceiver) {}
|
||||
NotNull<MixerCallbackReceiver*> mReceiver;
|
||||
};
|
||||
|
||||
/* Function that is called when the mixing is done. */
|
||||
LinkedList<MixerCallback> mCallbacks;
|
||||
/* Buffer containing the mixed audio data. */
|
||||
AudioChunk mChunk;
|
||||
/* Size allocated for mChunk.mBuffer. */
|
||||
|
@ -356,15 +356,15 @@ class AudioCallbackDriver::FallbackWrapper : public GraphInterface {
|
||||
#endif
|
||||
IterationResult OneIteration(GraphTime aStateComputedEnd,
|
||||
GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer) override {
|
||||
MOZ_ASSERT(!aMixer);
|
||||
MixerCallbackReceiver* aMixerReceiver) override {
|
||||
MOZ_ASSERT(!aMixerReceiver);
|
||||
|
||||
#ifdef DEBUG
|
||||
AutoInCallback aic(mOwner);
|
||||
#endif
|
||||
|
||||
IterationResult result =
|
||||
mGraph->OneIteration(aStateComputedEnd, aIterationEnd, aMixer);
|
||||
mGraph->OneIteration(aStateComputedEnd, aIterationEnd, aMixerReceiver);
|
||||
|
||||
AudioStreamState audioState = mOwner->mAudioStreamState;
|
||||
|
||||
@ -475,8 +475,6 @@ AudioCallbackDriver::AudioCallbackDriver(
|
||||
} else {
|
||||
mInputDevicePreference = CUBEB_DEVICE_PREF_ALL;
|
||||
}
|
||||
|
||||
mMixer.AddCallback(WrapNotNull(this));
|
||||
}
|
||||
|
||||
AudioCallbackDriver::~AudioCallbackDriver() {
|
||||
@ -948,7 +946,7 @@ long AudioCallbackDriver::DataCallback(const AudioDataValue* aInputBuffer,
|
||||
}
|
||||
|
||||
IterationResult result =
|
||||
Graph()->OneIteration(nextStateComputedTime, mIterationEnd, &mMixer);
|
||||
Graph()->OneIteration(nextStateComputedTime, mIterationEnd, this);
|
||||
|
||||
mStateComputedTime = nextStateComputedTime;
|
||||
|
||||
|
@ -193,11 +193,11 @@ struct GraphInterface : public nsISupports {
|
||||
* plug/unplug etc. This can be called on any thread, and posts a message to
|
||||
* the main thread so that it can post a message to the graph thread. */
|
||||
virtual void DeviceChanged() = 0;
|
||||
/* Called by GraphDriver to iterate the graph. Output from the graph gets
|
||||
* mixed into aMixer, if it is non-null. */
|
||||
virtual IterationResult OneIteration(GraphTime aStateComputedEnd,
|
||||
GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer) = 0;
|
||||
/* Called by GraphDriver to iterate the graph. Mixed audio output from the
|
||||
* graph is passed into aMixerReceiver, if it is non-null. */
|
||||
virtual IterationResult OneIteration(
|
||||
GraphTime aStateComputedEnd, GraphTime aIterationEnd,
|
||||
MixerCallbackReceiver* aMixerReceiver) = 0;
|
||||
#ifdef DEBUG
|
||||
/* True if we're on aDriver's thread, or if we're on mGraphRunner's thread
|
||||
* and mGraphRunner is currently run by aDriver. */
|
||||
@ -719,9 +719,6 @@ class AudioCallbackDriver : public GraphDriver, public MixerCallbackReceiver {
|
||||
* must run serially for access to mAudioStream. */
|
||||
const RefPtr<SharedThreadPool> mCubebOperationThread;
|
||||
cubeb_device_pref mInputDevicePreference;
|
||||
/* The mixer that the graph mixes into during an iteration. Audio thread only.
|
||||
*/
|
||||
AudioMixer mMixer;
|
||||
/* Contains the id of the audio thread, from profiler_current_thread_id. */
|
||||
std::atomic<ProfilerThreadId> mAudioThreadId;
|
||||
/* This allows implementing AutoInCallback. This is equal to the current
|
||||
|
@ -60,12 +60,14 @@ void GraphRunner::Shutdown() {
|
||||
}
|
||||
|
||||
auto GraphRunner::OneIteration(GraphTime aStateTime, GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer) -> IterationResult {
|
||||
MixerCallbackReceiver* aMixerReceiver)
|
||||
-> IterationResult {
|
||||
TRACE("GraphRunner::OneIteration");
|
||||
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
MOZ_ASSERT(mThreadState == ThreadState::Wait);
|
||||
mIterationState = Some(IterationState(aStateTime, aIterationEnd, aMixer));
|
||||
mIterationState =
|
||||
Some(IterationState(aStateTime, aIterationEnd, aMixerReceiver));
|
||||
|
||||
#ifdef DEBUG
|
||||
if (const auto* audioDriver =
|
||||
@ -136,9 +138,9 @@ NS_IMETHODIMP GraphRunner::Run() {
|
||||
}
|
||||
MOZ_DIAGNOSTIC_ASSERT(mIterationState.isSome());
|
||||
TRACE("GraphRunner::Run");
|
||||
mIterationResult = mGraph->OneIterationImpl(mIterationState->StateTime(),
|
||||
mIterationState->IterationEnd(),
|
||||
mIterationState->Mixer());
|
||||
mIterationResult = mGraph->OneIterationImpl(
|
||||
mIterationState->StateTime(), mIterationState->IterationEnd(),
|
||||
mIterationState->MixerReceiver());
|
||||
// Signal that mIterationResult was updated
|
||||
mThreadState = ThreadState::Wait;
|
||||
mMonitor.Notify();
|
||||
|
@ -36,7 +36,7 @@ class GraphRunner final : public Runnable {
|
||||
* the iteration there.
|
||||
*/
|
||||
IterationResult OneIteration(GraphTime aStateTime, GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer);
|
||||
MixerCallbackReceiver* aMixerReceiver);
|
||||
|
||||
/**
|
||||
* Runs mGraph until it shuts down.
|
||||
@ -64,18 +64,18 @@ class GraphRunner final : public Runnable {
|
||||
class IterationState {
|
||||
GraphTime mStateTime;
|
||||
GraphTime mIterationEnd;
|
||||
AudioMixer* MOZ_NON_OWNING_REF mMixer;
|
||||
MixerCallbackReceiver* MOZ_NON_OWNING_REF mMixerReceiver;
|
||||
|
||||
public:
|
||||
IterationState(GraphTime aStateTime, GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer)
|
||||
MixerCallbackReceiver* aMixerReceiver)
|
||||
: mStateTime(aStateTime),
|
||||
mIterationEnd(aIterationEnd),
|
||||
mMixer(aMixer) {}
|
||||
mMixerReceiver(aMixerReceiver) {}
|
||||
IterationState& operator=(const IterationState& aOther) = default;
|
||||
GraphTime StateTime() const { return mStateTime; }
|
||||
GraphTime IterationEnd() const { return mIterationEnd; }
|
||||
AudioMixer* Mixer() const { return mMixer; }
|
||||
MixerCallbackReceiver* MixerReceiver() const { return mMixerReceiver; }
|
||||
};
|
||||
|
||||
// Monitor used for yielding mThread through Wait(), and scheduling mThread
|
||||
|
@ -653,12 +653,10 @@ void MediaTrackGraphImpl::UpdateTrackOrder() {
|
||||
MOZ_ASSERT(orderedTrackCount == mFirstCycleBreaker);
|
||||
}
|
||||
|
||||
TrackTime MediaTrackGraphImpl::PlayAudio(AudioMixer* aMixer,
|
||||
const TrackKeyAndVolume& aTkv,
|
||||
TrackTime MediaTrackGraphImpl::PlayAudio(const TrackKeyAndVolume& aTkv,
|
||||
GraphTime aPlayedTime) {
|
||||
MOZ_ASSERT(OnGraphThread());
|
||||
MOZ_ASSERT(mRealtime, "Should only attempt to play audio in realtime mode");
|
||||
MOZ_ASSERT(aMixer, "Can only play audio if there's a mixer");
|
||||
|
||||
TrackTime ticksWritten = 0;
|
||||
|
||||
@ -745,7 +743,7 @@ TrackTime MediaTrackGraphImpl::PlayAudio(AudioMixer* aMixer,
|
||||
} else {
|
||||
outputChannels = AudioOutputChannelCount();
|
||||
}
|
||||
output.Mix(*aMixer, outputChannels, mSampleRate);
|
||||
output.Mix(mMixer, outputChannels, mSampleRate);
|
||||
}
|
||||
return ticksWritten;
|
||||
}
|
||||
@ -1428,7 +1426,7 @@ void MediaTrackGraphImpl::UpdateGraph(GraphTime aEndBlockingDecisions) {
|
||||
}
|
||||
}
|
||||
|
||||
void MediaTrackGraphImpl::Process(AudioMixer* aMixer) {
|
||||
void MediaTrackGraphImpl::Process(MixerCallbackReceiver* aMixerReceiver) {
|
||||
TRACE("MTG::Process");
|
||||
MOZ_ASSERT(OnGraphThread());
|
||||
// Play track contents.
|
||||
@ -1477,15 +1475,14 @@ void MediaTrackGraphImpl::Process(AudioMixer* aMixer) {
|
||||
}
|
||||
mProcessedTime = mStateComputedTime;
|
||||
|
||||
if (aMixer) {
|
||||
if (aMixerReceiver) {
|
||||
MOZ_ASSERT(mRealtime, "If there's a mixer, this graph must be realtime");
|
||||
aMixer->StartMixing();
|
||||
mMixer.StartMixing();
|
||||
// This is the number of frames that are written to the output buffer, for
|
||||
// this iteration.
|
||||
TrackTime ticksPlayed = 0;
|
||||
for (auto& t : mAudioOutputs) {
|
||||
TrackTime ticksPlayedForThisTrack =
|
||||
PlayAudio(aMixer, t, oldProcessedTime);
|
||||
TrackTime ticksPlayedForThisTrack = PlayAudio(t, oldProcessedTime);
|
||||
if (ticksPlayed == 0) {
|
||||
ticksPlayed = ticksPlayedForThisTrack;
|
||||
} else {
|
||||
@ -1499,12 +1496,11 @@ void MediaTrackGraphImpl::Process(AudioMixer* aMixer) {
|
||||
// Nothing was played, so the mixer doesn't know how many frames were
|
||||
// processed. We still tell it so AudioCallbackDriver knows how much has
|
||||
// been processed. (bug 1406027)
|
||||
aMixer->Mix(
|
||||
nullptr,
|
||||
CurrentDriver()->AsAudioCallbackDriver()->OutputChannelCount(),
|
||||
mStateComputedTime - oldProcessedTime, mSampleRate);
|
||||
mMixer.Mix(nullptr,
|
||||
CurrentDriver()->AsAudioCallbackDriver()->OutputChannelCount(),
|
||||
mStateComputedTime - oldProcessedTime, mSampleRate);
|
||||
}
|
||||
aMixer->FinishMixing();
|
||||
aMixerReceiver->MixerCallback(mMixer.MixedChunk(), mSampleRate);
|
||||
}
|
||||
|
||||
if (!allBlockedForever) {
|
||||
@ -1541,18 +1537,19 @@ bool MediaTrackGraphImpl::UpdateMainThreadState() {
|
||||
|
||||
auto MediaTrackGraphImpl::OneIteration(GraphTime aStateTime,
|
||||
GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer) -> IterationResult {
|
||||
MixerCallbackReceiver* aMixerReceiver)
|
||||
-> IterationResult {
|
||||
if (mGraphRunner) {
|
||||
return mGraphRunner->OneIteration(aStateTime, aIterationEnd, aMixer);
|
||||
return mGraphRunner->OneIteration(aStateTime, aIterationEnd,
|
||||
aMixerReceiver);
|
||||
}
|
||||
|
||||
return OneIterationImpl(aStateTime, aIterationEnd, aMixer);
|
||||
return OneIterationImpl(aStateTime, aIterationEnd, aMixerReceiver);
|
||||
}
|
||||
|
||||
auto MediaTrackGraphImpl::OneIterationImpl(GraphTime aStateTime,
|
||||
GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer)
|
||||
-> IterationResult {
|
||||
auto MediaTrackGraphImpl::OneIterationImpl(
|
||||
GraphTime aStateTime, GraphTime aIterationEnd,
|
||||
MixerCallbackReceiver* aMixerReceiver) -> IterationResult {
|
||||
TRACE("MTG::OneIterationImpl");
|
||||
|
||||
mIterationEndTime = aIterationEnd;
|
||||
@ -1596,7 +1593,7 @@ auto MediaTrackGraphImpl::OneIterationImpl(GraphTime aStateTime,
|
||||
mStateComputedTime = stateTime;
|
||||
|
||||
GraphTime oldProcessedTime = mProcessedTime;
|
||||
Process(aMixer);
|
||||
Process(aMixerReceiver);
|
||||
MOZ_ASSERT(mProcessedTime == stateTime);
|
||||
|
||||
UpdateCurrentTimeForTracks(oldProcessedTime);
|
||||
|
@ -258,17 +258,18 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
|
||||
* Proxy method called by GraphDriver to iterate the graph.
|
||||
* If this graph was created with GraphRunType SINGLE_THREAD, mGraphRunner
|
||||
* will take care of calling OneIterationImpl from its thread. Otherwise,
|
||||
* OneIterationImpl is called directly. Output from the graph gets mixed into
|
||||
* aMixer, if it is non-null.
|
||||
* OneIterationImpl is called directly. Mixed audio output from the graph is
|
||||
* passed into aMixerReceiver, if it is non-null.
|
||||
*/
|
||||
IterationResult OneIteration(GraphTime aStateTime, GraphTime aIterationEnd,
|
||||
AudioMixer* aMixer) override;
|
||||
MixerCallbackReceiver* aMixerReceiver) override;
|
||||
|
||||
/**
|
||||
* Returns true if this MediaTrackGraph should keep running
|
||||
*/
|
||||
IterationResult OneIterationImpl(GraphTime aStateTime,
|
||||
GraphTime aIterationEnd, AudioMixer* aMixer);
|
||||
GraphTime aIterationEnd,
|
||||
MixerCallbackReceiver* aMixerReceiver);
|
||||
|
||||
/**
|
||||
* Called from the driver, when the graph thread is about to stop, to tell
|
||||
@ -340,7 +341,7 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
|
||||
* Do all the processing and play the audio and video, from
|
||||
* mProcessedTime to mStateComputedTime.
|
||||
*/
|
||||
void Process(AudioMixer* aMixer);
|
||||
void Process(MixerCallbackReceiver* aMixerReceiver);
|
||||
|
||||
/**
|
||||
* For use during ProcessedMediaTrack::ProcessInput() or
|
||||
@ -430,8 +431,7 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
|
||||
void* mKey;
|
||||
float mVolume;
|
||||
};
|
||||
TrackTime PlayAudio(AudioMixer* aMixer, const TrackKeyAndVolume& aTkv,
|
||||
GraphTime aPlayedTime);
|
||||
TrackTime PlayAudio(const TrackKeyAndVolume& aTkv, GraphTime aPlayedTime);
|
||||
|
||||
/* Runs off a message on the graph thread when something requests audio from
|
||||
* an input audio device of ID aID, and delivers the input audio frames to
|
||||
@ -1049,6 +1049,12 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
|
||||
* Manage the native or non-native input device in graph. Graph thread only.
|
||||
*/
|
||||
DeviceInputTrackManager mDeviceInputTrackManagerGraphThread;
|
||||
/**
|
||||
* The mixer that the graph mixes into during an iteration. This is here
|
||||
* rather than on the stack so that its buffer is not allocated each
|
||||
* iteration. Graph thread only.
|
||||
*/
|
||||
AudioMixer mMixer;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -35,14 +35,13 @@ class MockGraphInterface : public GraphInterface {
|
||||
/* OneIteration cannot be mocked because IterationResult is non-memmovable and
|
||||
* cannot be passed as a parameter, which GMock does internally. */
|
||||
IterationResult OneIteration(GraphTime aStateComputedTime, GraphTime,
|
||||
AudioMixer* aMixer) {
|
||||
MixerCallbackReceiver* aMixerReceiver) {
|
||||
GraphDriver* driver = mCurrentDriver;
|
||||
if (aMixer) {
|
||||
aMixer->StartMixing();
|
||||
aMixer->Mix(nullptr,
|
||||
driver->AsAudioCallbackDriver()->OutputChannelCount(),
|
||||
aStateComputedTime - mStateComputedTime, mSampleRate);
|
||||
aMixer->FinishMixing();
|
||||
if (aMixerReceiver) {
|
||||
mMixer.StartMixing();
|
||||
mMixer.Mix(nullptr, driver->AsAudioCallbackDriver()->OutputChannelCount(),
|
||||
aStateComputedTime - mStateComputedTime, mSampleRate);
|
||||
aMixerReceiver->MixerCallback(mMixer.MixedChunk(), mSampleRate);
|
||||
}
|
||||
if (aStateComputedTime != mStateComputedTime) {
|
||||
mFramesIteratedEvent.Notify(aStateComputedTime - mStateComputedTime);
|
||||
@ -93,6 +92,7 @@ class MockGraphInterface : public GraphInterface {
|
||||
Atomic<bool> mKeepProcessing{true};
|
||||
Atomic<GraphDriver*> mNextDriver{nullptr};
|
||||
MediaEventProducer<uint32_t> mFramesIteratedEvent;
|
||||
AudioMixer mMixer;
|
||||
virtual ~MockGraphInterface() = default;
|
||||
};
|
||||
|
||||
|
@ -83,20 +83,19 @@ TEST(AudioMixer, Test)
|
||||
{
|
||||
int iterations = 2;
|
||||
mozilla::AudioMixer mixer;
|
||||
mixer.AddCallback(WrapNotNull(&consumer));
|
||||
|
||||
fprintf(stderr, "Test AudioMixer constant buffer length.\n");
|
||||
|
||||
while (iterations--) {
|
||||
mixer.StartMixing();
|
||||
mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.FinishMixing();
|
||||
consumer.MixerCallback(mixer.MixedChunk(), AUDIO_RATE);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
mozilla::AudioMixer mixer;
|
||||
mixer.AddCallback(WrapNotNull(&consumer));
|
||||
|
||||
fprintf(stderr, "Test AudioMixer variable buffer length.\n");
|
||||
|
||||
@ -106,27 +105,30 @@ TEST(AudioMixer, Test)
|
||||
FillBuffer(b, CHANNEL_LENGTH / 2, GetHighValue<AudioDataValue>());
|
||||
FillBuffer(b + CHANNEL_LENGTH / 2, CHANNEL_LENGTH / 2,
|
||||
GetHighValue<AudioDataValue>());
|
||||
mixer.StartMixing();
|
||||
mixer.Mix(a, 2, CHANNEL_LENGTH / 2, AUDIO_RATE);
|
||||
mixer.Mix(b, 2, CHANNEL_LENGTH / 2, AUDIO_RATE);
|
||||
mixer.FinishMixing();
|
||||
consumer.MixerCallback(mixer.MixedChunk(), AUDIO_RATE);
|
||||
FillBuffer(a, CHANNEL_LENGTH, GetLowValue<AudioDataValue>());
|
||||
FillBuffer(a + CHANNEL_LENGTH, CHANNEL_LENGTH,
|
||||
GetHighValue<AudioDataValue>());
|
||||
FillBuffer(b, CHANNEL_LENGTH, GetHighValue<AudioDataValue>());
|
||||
FillBuffer(b + CHANNEL_LENGTH, CHANNEL_LENGTH,
|
||||
GetLowValue<AudioDataValue>());
|
||||
mixer.StartMixing();
|
||||
mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.FinishMixing();
|
||||
consumer.MixerCallback(mixer.MixedChunk(), AUDIO_RATE);
|
||||
FillBuffer(a, CHANNEL_LENGTH / 2, GetLowValue<AudioDataValue>());
|
||||
FillBuffer(a + CHANNEL_LENGTH / 2, CHANNEL_LENGTH / 2,
|
||||
GetLowValue<AudioDataValue>());
|
||||
FillBuffer(b, CHANNEL_LENGTH / 2, GetHighValue<AudioDataValue>());
|
||||
FillBuffer(b + CHANNEL_LENGTH / 2, CHANNEL_LENGTH / 2,
|
||||
GetHighValue<AudioDataValue>());
|
||||
mixer.StartMixing();
|
||||
mixer.Mix(a, 2, CHANNEL_LENGTH / 2, AUDIO_RATE);
|
||||
mixer.Mix(b, 2, CHANNEL_LENGTH / 2, AUDIO_RATE);
|
||||
mixer.FinishMixing();
|
||||
consumer.MixerCallback(mixer.MixedChunk(), AUDIO_RATE);
|
||||
}
|
||||
|
||||
FillBuffer(a, CHANNEL_LENGTH, GetLowValue<AudioDataValue>());
|
||||
@ -134,37 +136,41 @@ TEST(AudioMixer, Test)
|
||||
|
||||
{
|
||||
mozilla::AudioMixer mixer;
|
||||
mixer.AddCallback(WrapNotNull(&consumer));
|
||||
|
||||
fprintf(stderr, "Test AudioMixer variable channel count.\n");
|
||||
|
||||
mixer.StartMixing();
|
||||
mixer.Mix(a, 1, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.Mix(b, 1, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.FinishMixing();
|
||||
consumer.MixerCallback(mixer.MixedChunk(), AUDIO_RATE);
|
||||
mixer.StartMixing();
|
||||
mixer.Mix(a, 1, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.Mix(b, 1, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.FinishMixing();
|
||||
consumer.MixerCallback(mixer.MixedChunk(), AUDIO_RATE);
|
||||
mixer.StartMixing();
|
||||
mixer.Mix(a, 1, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.Mix(b, 1, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.FinishMixing();
|
||||
consumer.MixerCallback(mixer.MixedChunk(), AUDIO_RATE);
|
||||
}
|
||||
|
||||
{
|
||||
mozilla::AudioMixer mixer;
|
||||
mixer.AddCallback(WrapNotNull(&consumer));
|
||||
fprintf(stderr, "Test AudioMixer variable stream count.\n");
|
||||
|
||||
mixer.StartMixing();
|
||||
mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.FinishMixing();
|
||||
consumer.MixerCallback(mixer.MixedChunk(), AUDIO_RATE);
|
||||
mixer.StartMixing();
|
||||
mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.FinishMixing();
|
||||
consumer.MixerCallback(mixer.MixedChunk(), AUDIO_RATE);
|
||||
mixer.StartMixing();
|
||||
mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.Mix(b, 2, CHANNEL_LENGTH, AUDIO_RATE);
|
||||
mixer.FinishMixing();
|
||||
consumer.MixerCallback(mixer.MixedChunk(), AUDIO_RATE);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user