Bug 989921 - Allow the MediaStreamGraph mixer to send data back to multiple consumers. r=jesup

This commit is contained in:
Paul Adenot 2014-05-22 13:40:51 +02:00
parent 601228fc42
commit 6b08a02243
6 changed files with 101 additions and 53 deletions

View File

@ -9,13 +9,17 @@
#include "AudioSampleFormat.h" #include "AudioSampleFormat.h"
#include "nsTArray.h" #include "nsTArray.h"
#include "mozilla/PodOperations.h" #include "mozilla/PodOperations.h"
#include "mozilla/LinkedList.h"
namespace mozilla { namespace mozilla {
typedef void(*MixerFunc)(AudioDataValue* aMixedBuffer,
AudioSampleFormat aFormat, struct MixerCallbackReceiver {
uint32_t aChannels, virtual void MixerCallback(AudioDataValue* aMixedBuffer,
uint32_t aFrames, AudioSampleFormat aFormat,
uint32_t aSampleRate); uint32_t aChannels,
uint32_t aFrames,
uint32_t aSampleRate) = 0;
};
/** /**
* This class mixes multiple streams of audio together to output a single audio * This class mixes multiple streams of audio together to output a single audio
@ -32,21 +36,29 @@ typedef void(*MixerFunc)(AudioDataValue* aMixedBuffer,
class AudioMixer class AudioMixer
{ {
public: public:
AudioMixer(MixerFunc aCallback) AudioMixer()
: mCallback(aCallback), : mFrames(0),
mFrames(0),
mChannels(0), mChannels(0),
mSampleRate(0) mSampleRate(0)
{ } { }
~AudioMixer()
{
mCallbacks.clear();
}
/* Get the data from the mixer. This is supposed to be called when all the /* 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. */ * tracks have been mixed in. The caller should not hold onto the data. */
void FinishMixing() { void FinishMixing() {
mCallback(mMixedAudio.Elements(), MOZ_ASSERT(mChannels && mFrames && mSampleRate, "Mix not called for this cycle?");
AudioSampleTypeToFormat<AudioDataValue>::Format, for (MixerCallback* cb = mCallbacks.getFirst();
mChannels, cb != nullptr; cb = cb->getNext()) {
mFrames, cb->mReceiver->MixerCallback(mMixedAudio.Elements(),
mSampleRate); AudioSampleTypeToFormat<AudioDataValue>::Format,
mChannels,
mFrames,
mSampleRate);
}
PodZero(mMixedAudio.Elements(), mMixedAudio.Length()); PodZero(mMixedAudio.Elements(), mMixedAudio.Length());
mSampleRate = mChannels = mFrames = 0; mSampleRate = mChannels = mFrames = 0;
} }
@ -71,6 +83,21 @@ public:
mMixedAudio[i] += aSamples[i]; mMixedAudio[i] += aSamples[i];
} }
} }
void AddCallback(MixerCallbackReceiver* aReceiver) {
mCallbacks.insertBack(new MixerCallback(aReceiver));
}
bool RemoveCallback(MixerCallbackReceiver* aReceiver) {
for (MixerCallback* cb = mCallbacks.getFirst();
cb != nullptr; cb = cb->getNext()) {
if (cb->mReceiver == aReceiver) {
cb->remove();
return true;
}
}
return false;
}
private: private:
void EnsureCapacityAndSilence() { void EnsureCapacityAndSilence() {
if (mFrames * mChannels > mMixedAudio.Length()) { if (mFrames * mChannels > mMixedAudio.Length()) {
@ -79,8 +106,17 @@ private:
PodZero(mMixedAudio.Elements(), mMixedAudio.Length()); PodZero(mMixedAudio.Elements(), mMixedAudio.Length());
} }
class MixerCallback : public LinkedListElement<MixerCallback>
{
public:
MixerCallback(MixerCallbackReceiver* aReceiver)
: mReceiver(aReceiver)
{ }
MixerCallbackReceiver* mReceiver;
};
/* Function that is called when the mixing is done. */ /* Function that is called when the mixing is done. */
MixerFunc mCallback; LinkedList<MixerCallback> mCallbacks;
/* Number of frames for this mixing block. */ /* Number of frames for this mixing block. */
uint32_t mFrames; uint32_t mFrames;
/* Number of channels for this mixing block. */ /* Number of channels for this mixing block. */

View File

@ -586,24 +586,6 @@ MediaStreamGraphImpl::UpdateStreamOrderForStream(mozilla::LinkedList<MediaStream
*mStreams.AppendElement() = stream.forget(); *mStreams.AppendElement() = stream.forget();
} }
static void AudioMixerCallback(AudioDataValue* aMixedBuffer,
AudioSampleFormat aFormat,
uint32_t aChannels,
uint32_t aFrames,
uint32_t aSampleRate)
{
// Need an api to register mixer callbacks, bug 989921
#ifdef MOZ_WEBRTC
if (aFrames > 0 && aChannels > 0) {
// XXX need Observer base class and registration API
if (gFarendObserver) {
gFarendObserver->InsertFarEnd(aMixedBuffer, aFrames, false,
aSampleRate, aChannels, aFormat);
}
}
#endif
}
void void
MediaStreamGraphImpl::UpdateStreamOrder() MediaStreamGraphImpl::UpdateStreamOrder()
{ {
@ -631,8 +613,12 @@ MediaStreamGraphImpl::UpdateStreamOrder()
} }
if (!mMixer && shouldMix) { if (!mMixer && shouldMix) {
mMixer = new AudioMixer(AudioMixerCallback); mMixer = new AudioMixer();
if (gFarendObserver) {
mMixer->AddCallback(gFarendObserver);
}
} else if (mMixer && !shouldMix) { } else if (mMixer && !shouldMix) {
mMixer->RemoveCallback(gFarendObserver);
mMixer = nullptr; mMixer = nullptr;
} }

View File

@ -18,7 +18,6 @@
#include "MainThreadUtils.h" #include "MainThreadUtils.h"
#include "nsAutoRef.h" #include "nsAutoRef.h"
#include "speex/speex_resampler.h" #include "speex/speex_resampler.h"
#include "AudioMixer.h"
#include "mozilla/dom/AudioChannelBinding.h" #include "mozilla/dom/AudioChannelBinding.h"
class nsIRunnable; class nsIRunnable;

View File

@ -9,25 +9,28 @@
using mozilla::AudioDataValue; using mozilla::AudioDataValue;
using mozilla::AudioSampleFormat; using mozilla::AudioSampleFormat;
struct MixerConsumer : public mozilla::MixerCallbackReceiver
{
/* In this test, the different audio stream and channels are always created to /* In this test, the different audio stream and channels are always created to
* cancel each other. */ * cancel each other. */
void MixingDone(AudioDataValue* aData, AudioSampleFormat aFormat, uint32_t aChannels, uint32_t aFrames, uint32_t aSampleRate) void MixerCallback(AudioDataValue* aData, AudioSampleFormat aFormat, uint32_t aChannels, uint32_t aFrames, uint32_t aSampleRate)
{ {
bool silent = true; bool silent = true;
for (uint32_t i = 0; i < aChannels * aFrames; i++) { for (uint32_t i = 0; i < aChannels * aFrames; i++) {
if (aData[i] != 0.0) { if (aData[i] != 0.0) {
if (aFormat == mozilla::AUDIO_FORMAT_S16) { if (aFormat == mozilla::AUDIO_FORMAT_S16) {
fprintf(stderr, "Sample at %d is not silent: %d\n", i, (short)aData[i]); fprintf(stderr, "Sample at %d is not silent: %d\n", i, (short)aData[i]);
} else { } else {
fprintf(stderr, "Sample at %d is not silent: %f\n", i, (float)aData[i]); fprintf(stderr, "Sample at %d is not silent: %f\n", i, (float)aData[i]);
}
silent = false;
} }
silent = false; }
if (!silent) {
MOZ_CRASH();
} }
} }
if (!silent) { };
MOZ_CRASH();
}
}
/* Helper function to give us the maximum and minimum value that don't clip, /* Helper function to give us the maximum and minimum value that don't clip,
* for a given sample format (integer or floating-point). */ * for a given sample format (integer or floating-point). */
@ -68,6 +71,7 @@ void FillBuffer(AudioDataValue* aBuffer, uint32_t aLength, AudioDataValue aValue
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
const uint32_t CHANNEL_LENGTH = 256; const uint32_t CHANNEL_LENGTH = 256;
const uint32_t AUDIO_RATE = 44100; const uint32_t AUDIO_RATE = 44100;
MixerConsumer consumer;
AudioDataValue a[CHANNEL_LENGTH * 2]; AudioDataValue a[CHANNEL_LENGTH * 2];
AudioDataValue b[CHANNEL_LENGTH * 2]; AudioDataValue b[CHANNEL_LENGTH * 2];
FillBuffer(a, CHANNEL_LENGTH, GetLowValue<AudioDataValue>()); FillBuffer(a, CHANNEL_LENGTH, GetLowValue<AudioDataValue>());
@ -77,7 +81,8 @@ int main(int argc, char* argv[]) {
{ {
int iterations = 2; int iterations = 2;
mozilla::AudioMixer mixer(MixingDone); mozilla::AudioMixer mixer;
mixer.AddCallback(&consumer);
fprintf(stderr, "Test AudioMixer constant buffer length.\n"); fprintf(stderr, "Test AudioMixer constant buffer length.\n");
@ -89,7 +94,8 @@ int main(int argc, char* argv[]) {
} }
{ {
mozilla::AudioMixer mixer(MixingDone); mozilla::AudioMixer mixer;
mixer.AddCallback(&consumer);
fprintf(stderr, "Test AudioMixer variable buffer length.\n"); fprintf(stderr, "Test AudioMixer variable buffer length.\n");
@ -120,7 +126,9 @@ int main(int argc, char* argv[]) {
FillBuffer(b, CHANNEL_LENGTH, GetHighValue<AudioDataValue>()); FillBuffer(b, CHANNEL_LENGTH, GetHighValue<AudioDataValue>());
{ {
mozilla::AudioMixer mixer(MixingDone); mozilla::AudioMixer mixer;
mixer.AddCallback(&consumer);
fprintf(stderr, "Test AudioMixer variable channel count.\n"); fprintf(stderr, "Test AudioMixer variable channel count.\n");
mixer.Mix(a, 1, CHANNEL_LENGTH, AUDIO_RATE); mixer.Mix(a, 1, CHANNEL_LENGTH, AUDIO_RATE);
@ -135,7 +143,8 @@ int main(int argc, char* argv[]) {
} }
{ {
mozilla::AudioMixer mixer(MixingDone); mozilla::AudioMixer mixer;
mixer.AddCallback(&consumer);
fprintf(stderr, "Test AudioMixer variable stream count.\n"); fprintf(stderr, "Test AudioMixer variable stream count.\n");
mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE); mixer.Mix(a, 2, CHANNEL_LENGTH, AUDIO_RATE);

View File

@ -6,6 +6,7 @@
#define AUDIOOUTPUTOBSERVER_H_ #define AUDIOOUTPUTOBSERVER_H_
#include "mozilla/StaticPtr.h" #include "mozilla/StaticPtr.h"
#include "AudioMixer.h"
namespace webrtc { namespace webrtc {
class SingleRwFifo; class SingleRwFifo;
@ -20,12 +21,18 @@ typedef struct FarEndAudioChunk_ {
} FarEndAudioChunk; } FarEndAudioChunk;
// XXX Really a singleton currently // XXX Really a singleton currently
class AudioOutputObserver // : public MSGOutputObserver class AudioOutputObserver : public MixerCallbackReceiver
{ {
public: public:
AudioOutputObserver(); AudioOutputObserver();
virtual ~AudioOutputObserver(); virtual ~AudioOutputObserver();
void MixerCallback(AudioDataValue* aMixedBuffer,
AudioSampleFormat aFormat,
uint32_t aChannels,
uint32_t aFrames,
uint32_t aSampleRate) MOZ_OVERRIDE;
void Clear(); void Clear();
void InsertFarEnd(const AudioDataValue *aBuffer, uint32_t aSamples, bool aOverran, void InsertFarEnd(const AudioDataValue *aBuffer, uint32_t aSamples, bool aOverran,
int aFreq, int aChannels, AudioSampleFormat aFormat); int aFreq, int aChannels, AudioSampleFormat aFormat);

View File

@ -86,6 +86,17 @@ AudioOutputObserver::Size()
return mPlayoutFifo->size(); return mPlayoutFifo->size();
} }
void
AudioOutputObserver::MixerCallback(AudioDataValue* aMixedBuffer,
AudioSampleFormat aFormat,
uint32_t aChannels,
uint32_t aFrames,
uint32_t aSampleRate)
{
gFarendObserver->InsertFarEnd(aMixedBuffer, aFrames, false,
aSampleRate, aChannels, aFormat);
}
// static // static
void void
AudioOutputObserver::InsertFarEnd(const AudioDataValue *aBuffer, uint32_t aSamples, bool aOverran, AudioOutputObserver::InsertFarEnd(const AudioDataValue *aBuffer, uint32_t aSamples, bool aOverran,