зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1238038 - Allow opening multiple devices r=padenot
This patch allows website users to open multiple microphones in the same MediaTrackGraph. Depends on D138189 Differential Revision: https://phabricator.services.mozilla.com/D138726
This commit is contained in:
Родитель
9fcad09de8
Коммит
2c01e7fdbf
|
@ -70,6 +70,9 @@ void AudioInputSource::Start() {
|
|||
// operations to the task thread.
|
||||
MOZ_ASSERT(mTaskThread);
|
||||
|
||||
// mSPSCQueue will have a new consumer.
|
||||
mSPSCQueue.ResetConsumerThreadId();
|
||||
|
||||
LOG("AudioInputSource %p, start", this);
|
||||
MOZ_ALWAYS_SUCCEEDS(mTaskThread->Dispatch(
|
||||
NS_NewRunnableFunction(__func__, [self = RefPtr(this)]() mutable {
|
||||
|
@ -116,7 +119,14 @@ void AudioInputSource::Stop() {
|
|||
})));
|
||||
}
|
||||
|
||||
AudioSegment AudioInputSource::GetAudioSegment(TrackTime aDuration) {
|
||||
AudioSegment AudioInputSource::GetAudioSegment(TrackTime aDuration,
|
||||
Consumer aConsumer) {
|
||||
if (aConsumer == Consumer::Changed) {
|
||||
// Reset queue's consumer to avoid hitting the assertion for checking the
|
||||
// consistency of mSPSCQueue's mConsumerId in Dequeue.
|
||||
mSPSCQueue.ResetConsumerThreadId();
|
||||
}
|
||||
|
||||
AudioSegment raw;
|
||||
while (mSPSCQueue.AvailableRead()) {
|
||||
AudioChunk chunk;
|
||||
|
|
|
@ -58,7 +58,11 @@ class AudioInputSource : public CubebInputStream::Listener {
|
|||
// Stops producing audio data.
|
||||
void Stop();
|
||||
// Returns the AudioSegment with aDuration of data inside.
|
||||
AudioSegment GetAudioSegment(TrackTime aDuration);
|
||||
// The graph thread can change behind the scene, e.g., cubeb stream reinit due
|
||||
// to default output device changed). When this happens, we need to notify
|
||||
// mSPSCQueue to change its data consumer.
|
||||
enum class Consumer { Same, Changed };
|
||||
AudioSegment GetAudioSegment(TrackTime aDuration, Consumer aConsumer);
|
||||
|
||||
// CubebInputStream::Listener interface: These are used only for the
|
||||
// underlying audio stream. No user should call these APIs.
|
||||
|
|
|
@ -55,60 +55,222 @@ namespace mozilla {
|
|||
#define TRACK_GRAPH_LOGE(msg, ...) \
|
||||
TRACK_GRAPH_LOG_INTERNAL(Error, msg, ##__VA_ARGS__)
|
||||
|
||||
/* static */
|
||||
Result<RefPtr<DeviceInputTrack>, nsresult> DeviceInputTrack::OpenAudio(
|
||||
MediaTrackGraphImpl* aGraph, CubebUtils::AudioDeviceID aDeviceId,
|
||||
const PrincipalHandle& aPrincipalHandle, AudioDataListener* aListener) {
|
||||
#ifdef CONSUMER_GRAPH_LOG_INTERNAL
|
||||
# undef CONSUMER_GRAPH_LOG_INTERNAL
|
||||
#endif // CONSUMER_GRAPH_LOG_INTERNAL
|
||||
#define CONSUMER_GRAPH_LOG_INTERNAL(level, msg, ...) \
|
||||
LOG_INTERNAL( \
|
||||
level, "(Graph %p, Driver %p) DeviceInputConsumerTrack %p, " msg, \
|
||||
this->mGraph, this->mGraph->CurrentDriver(), this, ##__VA_ARGS__)
|
||||
|
||||
#ifdef CONSUMER_GRAPH_LOGV
|
||||
# undef CONSUMER_GRAPH_LOGV
|
||||
#endif // CONSUMER_GRAPH_LOGV
|
||||
#define CONSUMER_GRAPH_LOGV(msg, ...) \
|
||||
CONSUMER_GRAPH_LOG_INTERNAL(Verbose, msg, ##__VA_ARGS__)
|
||||
|
||||
DeviceInputConsumerTrack::DeviceInputConsumerTrack(TrackRate aSampleRate)
|
||||
: ProcessedMediaTrack(aSampleRate, MediaSegment::AUDIO,
|
||||
new AudioSegment()) {}
|
||||
|
||||
void DeviceInputConsumerTrack::ConnectDeviceInput(
|
||||
CubebUtils::AudioDeviceID aId, AudioDataListener* aListener,
|
||||
const PrincipalHandle& aPrincipal) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(GraphImpl());
|
||||
MOZ_ASSERT(aListener);
|
||||
MOZ_ASSERT(!mListener);
|
||||
MOZ_ASSERT(!mDeviceInputTrack);
|
||||
MOZ_ASSERT(mDeviceId.isNothing());
|
||||
|
||||
RefPtr<DeviceInputTrack> track = aGraph->GetNativeInputTrack();
|
||||
if (!track) {
|
||||
track =
|
||||
new NativeInputTrack(aGraph->GraphRate(), aDeviceId, aPrincipalHandle);
|
||||
LOG("Create NativeInputTrack %p in MTG %p for device %p", track.get(),
|
||||
aGraph, aDeviceId);
|
||||
aGraph->AddTrack(track);
|
||||
// Add the listener before opening the device so an open device always has a
|
||||
// non-zero input channel count.
|
||||
track->AddDataListener(aListener);
|
||||
aGraph->OpenAudioInput(track);
|
||||
} else if (track->mDeviceId != aDeviceId) {
|
||||
// We only allows one device per MediaTrackGraph for now.
|
||||
LOGE("Device %p is not native device", aDeviceId);
|
||||
return Err(NS_ERROR_INVALID_ARG);
|
||||
} else {
|
||||
MOZ_ASSERT(track->mUserCount > 0);
|
||||
track->AddDataListener(aListener);
|
||||
}
|
||||
MOZ_ASSERT(track->mDeviceId == aDeviceId);
|
||||
mListener = aListener;
|
||||
mDeviceId.emplace(aId);
|
||||
|
||||
track->mUserCount += 1;
|
||||
LOG("DeviceInputTrack %p (device %p) in MTG %p has %d users now", track.get(),
|
||||
track->mDeviceId, aGraph, track->mUserCount);
|
||||
if (track->mUserCount > 1) {
|
||||
track->ReevaluateInputDevice();
|
||||
mDeviceInputTrack =
|
||||
DeviceInputTrack::OpenAudio(GraphImpl(), aId, aPrincipal, this);
|
||||
LOG("Open device %p (DeviceInputTrack %p) for consumer %p", aId,
|
||||
mDeviceInputTrack.get(), this);
|
||||
mPort = AllocateInputPort(mDeviceInputTrack.get());
|
||||
}
|
||||
|
||||
void DeviceInputConsumerTrack::DisconnectDeviceInput() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(GraphImpl());
|
||||
|
||||
if (!mListener) {
|
||||
MOZ_ASSERT(mDeviceId.isNothing());
|
||||
return;
|
||||
}
|
||||
|
||||
return track;
|
||||
MOZ_ASSERT(mPort);
|
||||
MOZ_ASSERT(mDeviceInputTrack);
|
||||
MOZ_ASSERT(mDeviceId.isSome());
|
||||
|
||||
LOG("Close device %p (DeviceInputTrack %p) for consumer %p ", *mDeviceId,
|
||||
mDeviceInputTrack.get(), this);
|
||||
mPort->Destroy();
|
||||
DeviceInputTrack::CloseAudio(mDeviceInputTrack.forget(), this);
|
||||
mListener = nullptr;
|
||||
mDeviceId = Nothing();
|
||||
}
|
||||
|
||||
Maybe<CubebUtils::AudioDeviceID> DeviceInputConsumerTrack::DeviceId() const {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mDeviceId;
|
||||
}
|
||||
|
||||
NotNull<AudioDataListener*> DeviceInputConsumerTrack::GetAudioDataListener()
|
||||
const {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return WrapNotNull(mListener.get());
|
||||
}
|
||||
|
||||
bool DeviceInputConsumerTrack::ConnectToNativeDevice() const {
|
||||
return mPort &&
|
||||
mPort->GetSource()->AsDeviceInputTrack()->AsNativeInputTrack();
|
||||
}
|
||||
|
||||
bool DeviceInputConsumerTrack::ConnectToNonNativeDevice() const {
|
||||
return mPort &&
|
||||
mPort->GetSource()->AsDeviceInputTrack()->AsNonNativeInputTrack();
|
||||
}
|
||||
|
||||
void DeviceInputConsumerTrack::GetInputSourceData(AudioSegment& aOutput,
|
||||
const MediaInputPort* aPort,
|
||||
GraphTime aFrom,
|
||||
GraphTime aTo) const {
|
||||
MOZ_ASSERT(mGraph->OnGraphThread());
|
||||
MOZ_ASSERT(aOutput.IsEmpty());
|
||||
|
||||
MediaTrack* source = aPort->GetSource();
|
||||
GraphTime next;
|
||||
for (GraphTime t = aFrom; t < aTo; t = next) {
|
||||
MediaInputPort::InputInterval interval =
|
||||
MediaInputPort::GetNextInputInterval(aPort, t);
|
||||
interval.mEnd = std::min(interval.mEnd, aTo);
|
||||
|
||||
const bool inputEnded =
|
||||
source->Ended() &&
|
||||
source->GetEnd() <=
|
||||
source->GraphTimeToTrackTimeWithBlocking(interval.mStart);
|
||||
|
||||
TrackTime ticks = interval.mEnd - interval.mStart;
|
||||
next = interval.mEnd;
|
||||
|
||||
if (interval.mStart >= interval.mEnd) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (inputEnded) {
|
||||
aOutput.AppendNullData(ticks);
|
||||
CONSUMER_GRAPH_LOGV(
|
||||
"Getting %" PRId64
|
||||
" ticks of null data from input port source (ended input)",
|
||||
ticks);
|
||||
} else if (interval.mInputIsBlocked) {
|
||||
aOutput.AppendNullData(ticks);
|
||||
CONSUMER_GRAPH_LOGV(
|
||||
"Getting %" PRId64
|
||||
" ticks of null data from input port source (blocked input)",
|
||||
ticks);
|
||||
} else if (source->IsSuspended()) {
|
||||
aOutput.AppendNullData(ticks);
|
||||
CONSUMER_GRAPH_LOGV(
|
||||
"Getting %" PRId64
|
||||
" ticks of null data from input port source (source is suspended)",
|
||||
ticks);
|
||||
} else {
|
||||
TrackTime start =
|
||||
source->GraphTimeToTrackTimeWithBlocking(interval.mStart);
|
||||
TrackTime end = source->GraphTimeToTrackTimeWithBlocking(interval.mEnd);
|
||||
MOZ_ASSERT(source->GetData<AudioSegment>()->GetDuration() >= end);
|
||||
aOutput.AppendSlice(*source->GetData<AudioSegment>(), start, end);
|
||||
CONSUMER_GRAPH_LOGV("Getting %" PRId64
|
||||
" ticks of real data from input port source %p",
|
||||
end - start, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
void DeviceInputTrack::CloseAudio(RefPtr<DeviceInputTrack>&& aTrack,
|
||||
AudioDataListener* aListener) {
|
||||
NotNull<RefPtr<DeviceInputTrack>> DeviceInputTrack::OpenAudio(
|
||||
MediaTrackGraphImpl* aGraph, CubebUtils::AudioDeviceID aDeviceId,
|
||||
const PrincipalHandle& aPrincipalHandle,
|
||||
DeviceInputConsumerTrack* aConsumer) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aTrack);
|
||||
MOZ_ASSERT(aTrack->mUserCount > 0);
|
||||
MOZ_ASSERT(aConsumer);
|
||||
MOZ_ASSERT(aGraph == aConsumer->GraphImpl());
|
||||
|
||||
aTrack->RemoveDataListener(aListener);
|
||||
aTrack->mUserCount -= 1;
|
||||
LOG("DeviceInputTrack %p (device %p) in MTG %p has %d users now",
|
||||
aTrack.get(), aTrack->mDeviceId, aTrack->GraphImpl(), aTrack->mUserCount);
|
||||
if (aTrack->mUserCount == 0) {
|
||||
aTrack->GraphImpl()->CloseAudioInput(aTrack);
|
||||
aTrack->Destroy();
|
||||
RefPtr<DeviceInputTrack> track =
|
||||
aGraph->GetDeviceInputTrackMainThread(aDeviceId);
|
||||
if (track) {
|
||||
MOZ_ASSERT(!track->mConsumerTracks.IsEmpty());
|
||||
track->AddDataListener(aConsumer->GetAudioDataListener());
|
||||
} else {
|
||||
aTrack->ReevaluateInputDevice();
|
||||
// Create a NativeInputTrack or NonNativeInputTrack, depending on whether
|
||||
// the given graph already has a native device or not.
|
||||
if (aGraph->GetNativeInputTrackMainThread()) {
|
||||
// A native device is already in use. This device will be a non-native
|
||||
// device.
|
||||
track = new NonNativeInputTrack(aGraph->GraphRate(), aDeviceId,
|
||||
aPrincipalHandle);
|
||||
} else {
|
||||
// No native device is in use. This device will be the native device.
|
||||
track = new NativeInputTrack(aGraph->GraphRate(), aDeviceId,
|
||||
aPrincipalHandle);
|
||||
}
|
||||
LOG("Create %sNativeInputTrack %p in MTG %p for device %p",
|
||||
(track->AsNativeInputTrack() ? "" : "Non"), track.get(), aGraph,
|
||||
aDeviceId);
|
||||
aGraph->AddTrack(track);
|
||||
// Add the listener before opening the device so the device passed to
|
||||
// OpenAudioInput always has a non-zero input channel count.
|
||||
track->AddDataListener(aConsumer->GetAudioDataListener());
|
||||
aGraph->OpenAudioInput(track);
|
||||
}
|
||||
MOZ_ASSERT(track->AsNativeInputTrack() || track->AsNonNativeInputTrack());
|
||||
MOZ_ASSERT(track->mDeviceId == aDeviceId);
|
||||
|
||||
MOZ_ASSERT(!track->mConsumerTracks.Contains(aConsumer));
|
||||
track->mConsumerTracks.AppendElement(aConsumer);
|
||||
|
||||
LOG("DeviceInputTrack %p (device %p: %snative) in MTG %p has %zu users now",
|
||||
track.get(), track->mDeviceId,
|
||||
(track->AsNativeInputTrack() ? "" : "non-"), aGraph,
|
||||
track->mConsumerTracks.Length());
|
||||
if (track->mConsumerTracks.Length() > 1) {
|
||||
track->ReevaluateInputDevice();
|
||||
}
|
||||
|
||||
return WrapNotNull(track);
|
||||
}
|
||||
|
||||
/* static */
|
||||
void DeviceInputTrack::CloseAudio(already_AddRefed<DeviceInputTrack> aTrack,
|
||||
DeviceInputConsumerTrack* aConsumer) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
RefPtr<DeviceInputTrack> track = aTrack;
|
||||
MOZ_ASSERT(track);
|
||||
|
||||
track->RemoveDataListener(aConsumer->GetAudioDataListener());
|
||||
DebugOnly<bool> removed = track->mConsumerTracks.RemoveElement(aConsumer);
|
||||
MOZ_ASSERT(removed);
|
||||
LOG("DeviceInputTrack %p (device %p) in MTG %p has %zu users now",
|
||||
track.get(), track->mDeviceId, track->GraphImpl(),
|
||||
track->mConsumerTracks.Length());
|
||||
if (track->mConsumerTracks.IsEmpty()) {
|
||||
track->GraphImpl()->CloseAudioInput(track);
|
||||
track->Destroy();
|
||||
} else {
|
||||
track->ReevaluateInputDevice();
|
||||
}
|
||||
}
|
||||
|
||||
const nsTArray<RefPtr<DeviceInputConsumerTrack>>&
|
||||
DeviceInputTrack::GetConsumerTracks() const {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mConsumerTracks;
|
||||
}
|
||||
|
||||
DeviceInputTrack::DeviceInputTrack(TrackRate aSampleRate,
|
||||
|
@ -116,8 +278,7 @@ DeviceInputTrack::DeviceInputTrack(TrackRate aSampleRate,
|
|||
const PrincipalHandle& aPrincipalHandle)
|
||||
: ProcessedMediaTrack(aSampleRate, MediaSegment::AUDIO, new AudioSegment()),
|
||||
mDeviceId(aDeviceId),
|
||||
mPrincipalHandle(aPrincipalHandle),
|
||||
mUserCount(0) {}
|
||||
mPrincipalHandle(aPrincipalHandle) {}
|
||||
|
||||
uint32_t DeviceInputTrack::MaxRequestedInputChannels() const {
|
||||
MOZ_ASSERT(mGraph->OnGraphThreadOrNotRunning());
|
||||
|
@ -153,15 +314,15 @@ void DeviceInputTrack::ReevaluateInputDevice() {
|
|||
MOZ_ASSERT(NS_IsMainThread());
|
||||
class Message : public ControlMessage {
|
||||
public:
|
||||
explicit Message(MediaTrackGraphImpl* aGraph)
|
||||
: ControlMessage(nullptr), mGraph(aGraph) {}
|
||||
explicit Message(MediaTrack* aTrack, CubebUtils::AudioDeviceID aDeviceId)
|
||||
: ControlMessage(aTrack), mDeviceId(aDeviceId) {}
|
||||
void Run() override {
|
||||
TRACE("DeviceInputTrack::ReevaluateInputDevice ControlMessage");
|
||||
mGraph->ReevaluateInputDevice();
|
||||
mTrack->GraphImpl()->ReevaluateInputDevice(mDeviceId);
|
||||
}
|
||||
MediaTrackGraphImpl* mGraph;
|
||||
CubebUtils::AudioDeviceID mDeviceId;
|
||||
};
|
||||
mGraph->AppendMessage(MakeUnique<Message>(mGraph));
|
||||
mGraph->AppendMessage(MakeUnique<Message>(this, mDeviceId));
|
||||
}
|
||||
|
||||
void DeviceInputTrack::AddDataListener(AudioDataListener* aListener) {
|
||||
|
@ -301,7 +462,9 @@ NonNativeInputTrack::NonNativeInputTrack(
|
|||
TrackRate aSampleRate, CubebUtils::AudioDeviceID aDeviceId,
|
||||
const PrincipalHandle& aPrincipalHandle)
|
||||
: DeviceInputTrack(aSampleRate, aDeviceId, aPrincipalHandle),
|
||||
mAudioSource(nullptr) {}
|
||||
mAudioSource(nullptr),
|
||||
mSourceIdNumber(0),
|
||||
mGraphDriverThreadId(std::thread::id()) {}
|
||||
|
||||
void NonNativeInputTrack::DestroyImpl() {
|
||||
MOZ_ASSERT(mGraph->OnGraphThreadOrNotRunning());
|
||||
|
@ -333,7 +496,18 @@ void NonNativeInputTrack::ProcessInput(GraphTime aFrom, GraphTime aTo,
|
|||
return;
|
||||
}
|
||||
|
||||
AudioSegment data = mAudioSource->GetAudioSegment(delta);
|
||||
// GetAudioSegment only checks the given reader if DEBUG is defined.
|
||||
AudioInputSource::Consumer consumer =
|
||||
#ifdef DEBUG
|
||||
// If we are on GraphRunner, we should always be on the same thread.
|
||||
mGraph->mGraphRunner || !CheckGraphDriverChanged()
|
||||
? AudioInputSource::Consumer::Same
|
||||
: AudioInputSource::Consumer::Changed;
|
||||
#else
|
||||
AudioInputSource::Consumer::Same;
|
||||
#endif
|
||||
|
||||
AudioSegment data = mAudioSource->GetAudioSegment(delta, consumer);
|
||||
MOZ_ASSERT(data.GetDuration() == delta);
|
||||
GetData<AudioSegment>()->AppendFrom(&data);
|
||||
}
|
||||
|
@ -402,6 +576,22 @@ void NonNativeInputTrack::NotifyInputStopped(uint32_t aSourceId) {
|
|||
mAudioSource->Stop();
|
||||
}
|
||||
|
||||
AudioInputSource::Id NonNativeInputTrack::GenerateSourceId() {
|
||||
MOZ_ASSERT(mGraph->OnGraphThread());
|
||||
return mSourceIdNumber++;
|
||||
}
|
||||
|
||||
bool NonNativeInputTrack::CheckGraphDriverChanged() {
|
||||
MOZ_ASSERT(mGraph->CurrentDriver()->OnThread());
|
||||
|
||||
std::thread::id currentId = std::this_thread::get_id();
|
||||
if (mGraphDriverThreadId == currentId) {
|
||||
return false;
|
||||
}
|
||||
mGraphDriverThreadId = currentId;
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioInputSourceListener::AudioInputSourceListener(NonNativeInputTrack* aOwner)
|
||||
: mOwner(aOwner) {}
|
||||
|
||||
|
@ -495,5 +685,7 @@ void AudioInputSourceListener::AudioStateCallback(
|
|||
#undef TRACK_GRAPH_LOG
|
||||
#undef TRACK_GRAPH_LOGV
|
||||
#undef TRACK_GRAPH_LOGE
|
||||
#undef CONSUMER_GRAPH_LOG_INTERNAL
|
||||
#undef CONSUMER_GRAPH_LOGV
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -7,38 +7,143 @@
|
|||
#ifndef DOM_MEDIA_DEVICEINPUTTRACK_H_
|
||||
#define DOM_MEDIA_DEVICEINPUTTRACK_H_
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "AudioDriftCorrection.h"
|
||||
#include "AudioSegment.h"
|
||||
#include "AudioInputSource.h"
|
||||
#include "MediaTrackGraph.h"
|
||||
#include "GraphDriver.h"
|
||||
#include "mozilla/NotNull.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class NativeInputTrack;
|
||||
class NonNativeInputTrack;
|
||||
|
||||
// Any MediaTrack that needs the audio data from the certain device should
|
||||
// inherit the this class and get the raw audio data on graph thread via
|
||||
// GetInputSourceData(), after calling ConnectDeviceInput() and before
|
||||
// DisconnectDeviceInput() on main thread. See more examples in
|
||||
// TestAudioTrackGraph.cpp
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// class RawAudioDataTrack : public DeviceInputConsumerTrack {
|
||||
// public:
|
||||
// ...
|
||||
//
|
||||
// void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override
|
||||
// {
|
||||
// if (aFrom >= aTo) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (mInputs.IsEmpty()) {
|
||||
// GetData<AudioSegment>()->AppendNullData(aTo - aFrom);
|
||||
// } else {
|
||||
// MOZ_ASSERT(mInputs.Length() == 1);
|
||||
// AudioSegment data;
|
||||
// DeviceInputConsumerTrack::GetInputSourceData(data, mInputs[0], aFrom,
|
||||
// aTo);
|
||||
// // You can do audio data processing before appending to mSegment here.
|
||||
// GetData<AudioSegment>()->AppendFrom(&data);
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// uint32_t NumberOfChannels() const override {
|
||||
// if (mInputs.IsEmpty()) {
|
||||
// return 0;
|
||||
// }
|
||||
// DeviceInputTrack* t = mInputs[0]->GetSource()->AsDeviceInputTrack();
|
||||
// MOZ_ASSERT(t);
|
||||
// return t->NumberOfChannels();
|
||||
// }
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// private:
|
||||
// explicit RawAudioDataTrack(TrackRate aSampleRate)
|
||||
// : DeviceInputConsumerTrack(aSampleRate) {}
|
||||
// };
|
||||
class DeviceInputConsumerTrack : public ProcessedMediaTrack {
|
||||
public:
|
||||
explicit DeviceInputConsumerTrack(TrackRate aSampleRate);
|
||||
|
||||
// Main Thread APIs:
|
||||
void ConnectDeviceInput(CubebUtils::AudioDeviceID aId,
|
||||
AudioDataListener* aListener,
|
||||
const PrincipalHandle& aPrincipal);
|
||||
void DisconnectDeviceInput();
|
||||
Maybe<CubebUtils::AudioDeviceID> DeviceId() const;
|
||||
NotNull<AudioDataListener*> GetAudioDataListener() const;
|
||||
|
||||
// Any thread:
|
||||
bool ConnectToNativeDevice() const;
|
||||
bool ConnectToNonNativeDevice() const;
|
||||
DeviceInputConsumerTrack* AsDeviceInputConsumerTrack() override {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Graph thread API:
|
||||
// Get the data in [aFrom, aTo) from aPort->GetSource() to aOutput. aOutput
|
||||
// needs to be empty.
|
||||
void GetInputSourceData(AudioSegment& aOutput, const MediaInputPort* aPort,
|
||||
GraphTime aFrom, GraphTime aTo) const;
|
||||
|
||||
// Main Thread variables:
|
||||
RefPtr<MediaInputPort> mPort;
|
||||
RefPtr<DeviceInputTrack> mDeviceInputTrack;
|
||||
RefPtr<AudioDataListener> mListener;
|
||||
Maybe<CubebUtils::AudioDeviceID> mDeviceId;
|
||||
};
|
||||
|
||||
class DeviceInputTrack : public ProcessedMediaTrack {
|
||||
public:
|
||||
// Main Thread APIs:
|
||||
// Any MediaTrack that needs the audio data from the certain device should
|
||||
// inherit the DeviceInputConsumerTrack class and call GetInputSourceData to
|
||||
// get the data instead of using the below APIs.
|
||||
//
|
||||
// The following two APIs can create and destroy a DeviceInputTrack reference
|
||||
// on main thread, then open and close the underlying audio device accordingly
|
||||
// on the graph thread. The user who wants to read the audio input from a
|
||||
// certain device should use these APIs to obtain a DeviceInputTrack reference
|
||||
// and release the reference when the user no longer needs the audio data.
|
||||
//
|
||||
// Currently, all the DeviceInputTrack is NativeInputTrack.
|
||||
// Once the DeviceInputTrack is created on the main thread, the paired device
|
||||
// will start producing data, so its users can read the data immediately on
|
||||
// the graph thread, once they obtain the reference. The lifetime of
|
||||
// DeviceInputTrack is managed by the MediaTrackGraph itself. When the
|
||||
// DeviceInputTrack has no user any more, MediaTrackGraph will destroy it.
|
||||
// This means, it occurs when the last reference has been released by the API
|
||||
// below.
|
||||
//
|
||||
// There is only one NativeInputTrack per MediaTrackGraph and it will be
|
||||
// created when the first user who requests the audio data. Once the
|
||||
// NativeInputTrack is created, the paired device will start producing data,
|
||||
// so its users can read the data immediately once they obtain the reference.
|
||||
// Currently, we only allow one audio device per MediaTrackGraph. If the user
|
||||
// requests a different device from the one running in the MediaTrackGraph,
|
||||
// the API will return an error. The lifetime of NativeInputTrack is managed
|
||||
// by the MediaTrackGraph. When the NativeInputTrack has no user any more,
|
||||
// MediaTrackGraph will destroy it. In other words, it occurs when the last
|
||||
// reference is returned.
|
||||
// The DeviceInputTrack is either a NativeInputTrack, or a
|
||||
// NonNativeInputTrack. We can have only one NativeInputTrack per
|
||||
// MediaTrackGraph, but multiple NonNativeInputTrack per graph. The audio
|
||||
// device paired with the NativeInputTrack is called "native device", and the
|
||||
// device paired with the NonNativeInputTrack is called "non-native device".
|
||||
// In other words, we can have only one native device per MediaTrackGraph, but
|
||||
// many non-native devices per graph.
|
||||
//
|
||||
// The native device is the first input device created in the MediaTrackGraph.
|
||||
// All other devices created after it will be non-native devices. Once the
|
||||
// native device is destroyed, the first non-native device will be promoted to
|
||||
// the new native device. The switch will be started by the MediaTrackGraph.
|
||||
// The MediaTrackGraph will force DeviceInputTrack's users to re-configure
|
||||
// their DeviceInputTrack connections with the APIs below to execute the
|
||||
// switching.
|
||||
//
|
||||
// The native device is also the audio input device serving the
|
||||
// AudioCallbackDriver, which drives the MediaTrackGraph periodically from
|
||||
// audio callback thread. The audio data produced by the native device and
|
||||
// non-native device is stored in NativeInputTrack and NonNativeInputTrack
|
||||
// respectively, and then accessed by their users. The only difference between
|
||||
// these audio data is that the data from the non-native device is
|
||||
// clock-drift-corrected since the non-native device may run on a different
|
||||
// clock than the native device's one.
|
||||
//
|
||||
// Example:
|
||||
// // On main thread
|
||||
|
@ -48,22 +153,24 @@ class DeviceInputTrack : public ProcessedMediaTrack {
|
|||
// AudioSegmen* data = track->GetData<AudioSegment>();
|
||||
// ...
|
||||
// // On main thread
|
||||
// DeviceInputTrack::CloseAudio(std::move(track), ...);
|
||||
// DeviceInputTrack::CloseAudio(track.forget(), ...);
|
||||
//
|
||||
// Returns a reference of DeviceInputTrack, storing the input audio data from
|
||||
// the given device, in the given MediaTrackGraph, if the MediaTrackGraph has
|
||||
// no audio input device, or the given device is the same as the one currently
|
||||
// running in the MediaTrackGraph. Otherwise, return an error. The paired
|
||||
// audio device will be opened accordingly in the successful case. The
|
||||
// DeviceInputTrack will access its user's audio settings via the attached
|
||||
// AudioDataListener when it needs.
|
||||
static Result<RefPtr<DeviceInputTrack>, nsresult> OpenAudio(
|
||||
// the given device, in the given MediaTrackGraph. The paired audio device
|
||||
// will be opened accordingly. The DeviceInputTrack will access its user's
|
||||
// audio settings via the attached AudioDataListener, and delivers the
|
||||
// notifications when it needs.
|
||||
static NotNull<RefPtr<DeviceInputTrack>> OpenAudio(
|
||||
MediaTrackGraphImpl* aGraph, CubebUtils::AudioDeviceID aDeviceId,
|
||||
const PrincipalHandle& aPrincipalHandle, AudioDataListener* aListener);
|
||||
const PrincipalHandle& aPrincipalHandle,
|
||||
DeviceInputConsumerTrack* aConsumer);
|
||||
// Destroy the DeviceInputTrack reference obtained by the above API. The
|
||||
// paired audio device will be closed accordingly.
|
||||
static void CloseAudio(RefPtr<DeviceInputTrack>&& aTrack,
|
||||
AudioDataListener* aListener);
|
||||
static void CloseAudio(already_AddRefed<DeviceInputTrack> aTrack,
|
||||
DeviceInputConsumerTrack* aConsumer);
|
||||
|
||||
// Main thread API:
|
||||
const nsTArray<RefPtr<DeviceInputConsumerTrack>>& GetConsumerTracks() const;
|
||||
|
||||
// Graph thread APIs:
|
||||
// Query audio settings from its users.
|
||||
|
@ -93,8 +200,8 @@ class DeviceInputTrack : public ProcessedMediaTrack {
|
|||
void RemoveDataListener(AudioDataListener* aListener);
|
||||
|
||||
// Only accessed on the main thread.
|
||||
// When this becomes zero, this DeviceInputTrack is no longer needed.
|
||||
int32_t mUserCount = 0;
|
||||
// When this becomes empty, this DeviceInputTrack is no longer needed.
|
||||
nsTArray<RefPtr<DeviceInputConsumerTrack>> mConsumerTracks;
|
||||
|
||||
// Only accessed on the graph thread.
|
||||
nsTArray<RefPtr<AudioDataListener>> mListeners;
|
||||
|
@ -157,12 +264,20 @@ class NonNativeInputTrack final : public DeviceInputTrack {
|
|||
AudioInputType DevicePreference() const;
|
||||
void NotifyDeviceChanged(AudioInputSource::Id aSourceId);
|
||||
void NotifyInputStopped(AudioInputSource::Id aSourceId);
|
||||
AudioInputSource::Id GenerateSourceId();
|
||||
|
||||
private:
|
||||
~NonNativeInputTrack() = default;
|
||||
|
||||
// Graph driver thread only.
|
||||
bool CheckGraphDriverChanged();
|
||||
|
||||
// Graph thread only.
|
||||
RefPtr<AudioInputSource> mAudioSource;
|
||||
AudioInputSource::Id mSourceIdNumber;
|
||||
|
||||
// Graph driver thread only.
|
||||
std::thread::id mGraphDriverThreadId;
|
||||
};
|
||||
|
||||
class AudioInputSourceListener : public AudioInputSource::EventListener {
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "VideoFrameContainer.h"
|
||||
#include "mozilla/AbstractThread.h"
|
||||
#include "mozilla/StaticPrefs_dom.h"
|
||||
#include "mozilla/StaticPrefs_media.h"
|
||||
#include "transport/runnable_utils.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "GraphRunner.h"
|
||||
|
@ -303,7 +304,7 @@ bool MediaTrackGraphImpl::AudioTrackPresent() {
|
|||
|
||||
// We may not have audio input device when we only have AudioNodeTracks. But
|
||||
// if audioTrackPresent is false, we must have no input device.
|
||||
MOZ_DIAGNOSTIC_ASSERT_IF(!audioTrackPresent, !mNativeInputTrackOnGraph);
|
||||
MOZ_DIAGNOSTIC_ASSERT_IF(!audioTrackPresent, !mNativeInputTrackGraphThread);
|
||||
|
||||
return audioTrackPresent;
|
||||
}
|
||||
|
@ -338,13 +339,21 @@ void MediaTrackGraphImpl::CheckDriver() {
|
|||
return;
|
||||
}
|
||||
|
||||
uint32_t inputChannelCount =
|
||||
mNativeInputTrackGraphThread
|
||||
? AudioInputChannelCount(mNativeInputTrackGraphThread->mDeviceId)
|
||||
: 0;
|
||||
AudioInputType inputPreference =
|
||||
mNativeInputTrackGraphThread
|
||||
? AudioInputDevicePreference(mNativeInputTrackGraphThread->mDeviceId)
|
||||
: AudioInputType::Unknown;
|
||||
|
||||
uint32_t graphOutputChannelCount = AudioOutputChannelCount();
|
||||
if (!audioCallbackDriver) {
|
||||
if (graphOutputChannelCount > 0) {
|
||||
AudioCallbackDriver* driver = new AudioCallbackDriver(
|
||||
this, CurrentDriver(), mSampleRate, graphOutputChannelCount,
|
||||
AudioInputChannelCount(), mOutputDeviceID, mInputDeviceID,
|
||||
AudioInputDevicePreference());
|
||||
inputChannelCount, mOutputDeviceID, mInputDeviceID, inputPreference);
|
||||
SwitchAtNextIteration(driver);
|
||||
}
|
||||
return;
|
||||
|
@ -359,8 +368,7 @@ void MediaTrackGraphImpl::CheckDriver() {
|
|||
if (graphOutputChannelCount != audioCallbackDriver->OutputChannelCount()) {
|
||||
AudioCallbackDriver* driver = new AudioCallbackDriver(
|
||||
this, CurrentDriver(), mSampleRate, graphOutputChannelCount,
|
||||
AudioInputChannelCount(), mOutputDeviceID, mInputDeviceID,
|
||||
AudioInputDevicePreference());
|
||||
inputChannelCount, mOutputDeviceID, mInputDeviceID, inputPreference);
|
||||
SwitchAtNextIteration(driver);
|
||||
}
|
||||
}
|
||||
|
@ -649,79 +657,149 @@ TrackTime MediaTrackGraphImpl::PlayAudio(AudioMixer* aMixer,
|
|||
return ticksWritten;
|
||||
}
|
||||
|
||||
NativeInputTrack* MediaTrackGraphImpl::GetNativeInputTrack() {
|
||||
DeviceInputTrack* MediaTrackGraphImpl::GetDeviceInputTrackMainThread(
|
||||
CubebUtils::AudioDeviceID aID) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mNativeInputTrackOnMain.get();
|
||||
if (mNativeInputTrackMainThread &&
|
||||
mNativeInputTrackMainThread->mDeviceId == aID) {
|
||||
return mNativeInputTrackMainThread.get();
|
||||
}
|
||||
for (const RefPtr<NonNativeInputTrack>& t : mNonNativeInputTracksMainThread) {
|
||||
if (t->mDeviceId == aID) {
|
||||
return t.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MediaTrackGraphImpl::OpenAudioInputImpl(NativeInputTrack* aTrack) {
|
||||
DeviceInputTrack* MediaTrackGraphImpl::GetDeviceInputTrackGraphThread(
|
||||
CubebUtils::AudioDeviceID aID) {
|
||||
MOZ_ASSERT(OnGraphThread());
|
||||
if (mNativeInputTrackGraphThread &&
|
||||
mNativeInputTrackGraphThread->mDeviceId == aID) {
|
||||
return mNativeInputTrackGraphThread.get();
|
||||
}
|
||||
for (const RefPtr<NonNativeInputTrack>& t :
|
||||
mNonNativeInputTracksGraphThread) {
|
||||
if (t->mDeviceId == aID) {
|
||||
return t.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NativeInputTrack* MediaTrackGraphImpl::GetNativeInputTrackMainThread() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mNativeInputTrackMainThread.get();
|
||||
}
|
||||
|
||||
void MediaTrackGraphImpl::OpenAudioInputImpl(DeviceInputTrack* aTrack) {
|
||||
MOZ_ASSERT(OnGraphThread());
|
||||
LOG(LogLevel::Debug,
|
||||
("%p OpenAudioInputImpl: device %p", this, aTrack->mDeviceId));
|
||||
|
||||
if (mNativeInputTrackOnGraph) {
|
||||
MOZ_ASSERT_UNREACHABLE(
|
||||
"We cannot open device twice, and we don't support multiple inputs for "
|
||||
"now");
|
||||
return;
|
||||
if (NativeInputTrack* native = aTrack->AsNativeInputTrack()) {
|
||||
MOZ_ASSERT(!mNativeInputTrackGraphThread);
|
||||
|
||||
mNativeInputTrackGraphThread = native;
|
||||
|
||||
mInputDeviceID = aTrack->mDeviceId;
|
||||
// Switch Drivers since we're adding input (to input-only or full-duplex)
|
||||
AudioCallbackDriver* driver = new AudioCallbackDriver(
|
||||
this, CurrentDriver(), mSampleRate, AudioOutputChannelCount(),
|
||||
AudioInputChannelCount(aTrack->mDeviceId), mOutputDeviceID,
|
||||
mInputDeviceID, AudioInputDevicePreference(aTrack->mDeviceId));
|
||||
LOG(LogLevel::Debug,
|
||||
("%p OpenAudioInputImpl: starting new AudioCallbackDriver(input) %p",
|
||||
this, driver));
|
||||
SwitchAtNextIteration(driver);
|
||||
} else {
|
||||
NonNativeInputTrack* nonNative = aTrack->AsNonNativeInputTrack();
|
||||
MOZ_ASSERT(nonNative);
|
||||
|
||||
class DeviceTrackComparator {
|
||||
public:
|
||||
bool Equals(const RefPtr<NonNativeInputTrack>& aTrack,
|
||||
CubebUtils::AudioDeviceID aDeviceId) const {
|
||||
return aTrack->mDeviceId == aDeviceId;
|
||||
}
|
||||
};
|
||||
MOZ_ASSERT(!mNonNativeInputTracksGraphThread.Contains(
|
||||
aTrack->mDeviceId, DeviceTrackComparator()));
|
||||
mNonNativeInputTracksGraphThread.AppendElement(nonNative);
|
||||
|
||||
// Start non-native input right away.
|
||||
nonNative->StartAudio(MakeRefPtr<AudioInputSource>(
|
||||
MakeRefPtr<AudioInputSourceListener>(nonNative),
|
||||
nonNative->GenerateSourceId(), nonNative->mDeviceId,
|
||||
AudioInputChannelCount(nonNative->mDeviceId),
|
||||
AudioInputDevicePreference(nonNative->mDeviceId) ==
|
||||
AudioInputType::Voice,
|
||||
nonNative->mPrincipalHandle, nonNative->mSampleRate, GraphRate(),
|
||||
StaticPrefs::media_clockdrift_buffering()));
|
||||
}
|
||||
|
||||
LOG(LogLevel::Debug,
|
||||
("%p Open audio input on device %p", this, aTrack->mDeviceId));
|
||||
|
||||
mNativeInputTrackOnGraph = aTrack;
|
||||
mInputDeviceID = aTrack->mDeviceId;
|
||||
// Switch Drivers since we're adding input (to input-only or full-duplex)
|
||||
AudioCallbackDriver* driver = new AudioCallbackDriver(
|
||||
this, CurrentDriver(), mSampleRate, AudioOutputChannelCount(),
|
||||
AudioInputChannelCount(), mOutputDeviceID, mInputDeviceID,
|
||||
AudioInputDevicePreference());
|
||||
LOG(LogLevel::Debug,
|
||||
("%p OpenAudioInput: starting new AudioCallbackDriver(input) %p", this,
|
||||
driver));
|
||||
SwitchAtNextIteration(driver);
|
||||
}
|
||||
|
||||
void MediaTrackGraphImpl::OpenAudioInput(DeviceInputTrack* aTrack) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aTrack);
|
||||
MOZ_ASSERT(aTrack->AsNativeInputTrack());
|
||||
|
||||
LOG(LogLevel::Debug, ("%p OpenInput: DeviceInputTrack %p for device %p", this,
|
||||
aTrack, aTrack->mDeviceId));
|
||||
|
||||
class Message : public ControlMessage {
|
||||
public:
|
||||
Message(MediaTrackGraphImpl* aGraph, NativeInputTrack* aInputTrack)
|
||||
Message(MediaTrackGraphImpl* aGraph, DeviceInputTrack* aInputTrack)
|
||||
: ControlMessage(nullptr), mGraph(aGraph), mInputTrack(aInputTrack) {}
|
||||
void Run() override {
|
||||
TRACE("MTG::OpenAudioInputImpl ControlMessage");
|
||||
mGraph->OpenAudioInputImpl(mInputTrack);
|
||||
}
|
||||
MediaTrackGraphImpl* mGraph;
|
||||
NativeInputTrack* mInputTrack;
|
||||
DeviceInputTrack* mInputTrack;
|
||||
};
|
||||
|
||||
MOZ_ASSERT(!mNativeInputTrackOnMain);
|
||||
mNativeInputTrackOnMain = aTrack->AsNativeInputTrack();
|
||||
if (NativeInputTrack* native = aTrack->AsNativeInputTrack()) {
|
||||
MOZ_ASSERT(!mNativeInputTrackMainThread);
|
||||
mNativeInputTrackMainThread = native;
|
||||
} else {
|
||||
NonNativeInputTrack* nonNative = aTrack->AsNonNativeInputTrack();
|
||||
MOZ_ASSERT(nonNative);
|
||||
struct DeviceTrackComparator {
|
||||
public:
|
||||
bool Equals(const RefPtr<NonNativeInputTrack>& aTrack,
|
||||
CubebUtils::AudioDeviceID aDeviceId) const {
|
||||
return aTrack->mDeviceId == aDeviceId;
|
||||
}
|
||||
};
|
||||
MOZ_ASSERT(!mNonNativeInputTracksMainThread.Contains(
|
||||
aTrack->mDeviceId, DeviceTrackComparator()));
|
||||
mNonNativeInputTracksMainThread.AppendElement(nonNative);
|
||||
}
|
||||
|
||||
// XXX Check not destroyed!
|
||||
this->AppendMessage(MakeUnique<Message>(this, aTrack->AsNativeInputTrack()));
|
||||
this->AppendMessage(MakeUnique<Message>(this, aTrack));
|
||||
}
|
||||
|
||||
void MediaTrackGraphImpl::CloseAudioInputImpl(CubebUtils::AudioDeviceID aID) {
|
||||
void MediaTrackGraphImpl::CloseAudioInputImpl(DeviceInputTrack* aTrack) {
|
||||
MOZ_ASSERT(OnGraphThread());
|
||||
|
||||
LOG(LogLevel::Debug, ("%p CloseAudioInputImpl: device %p", this, aID));
|
||||
LOG(LogLevel::Debug,
|
||||
("%p CloseAudioInputImpl: device %p", this, aTrack->mDeviceId));
|
||||
|
||||
if (!mNativeInputTrackOnGraph || mNativeInputTrackOnGraph->mDeviceId != aID) {
|
||||
LOG(LogLevel::Debug, ("%p Device %p is not native device", this, aID));
|
||||
if (NonNativeInputTrack* nonNative = aTrack->AsNonNativeInputTrack()) {
|
||||
nonNative->StopAudio();
|
||||
MOZ_ASSERT(mNonNativeInputTracksGraphThread.Contains(nonNative));
|
||||
DebugOnly<bool> removed =
|
||||
mNonNativeInputTracksGraphThread.RemoveElement(nonNative);
|
||||
MOZ_ASSERT(removed);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(LogLevel::Debug, ("%p Close device %p", this, aID));
|
||||
MOZ_ASSERT(aTrack->AsNativeInputTrack());
|
||||
MOZ_ASSERT(aTrack->AsNativeInputTrack() ==
|
||||
mNativeInputTrackGraphThread.get());
|
||||
|
||||
mNativeInputTrackOnGraph = nullptr; // reset to default
|
||||
mNativeInputTrackGraphThread = nullptr; // reset to default
|
||||
mInputDeviceID = nullptr; // reset to default
|
||||
|
||||
// Switch Drivers since we're adding or removing an input (to nothing/system
|
||||
|
@ -736,8 +814,8 @@ void MediaTrackGraphImpl::CloseAudioInputImpl(CubebUtils::AudioDeviceID aID) {
|
|||
|
||||
driver = new AudioCallbackDriver(
|
||||
this, CurrentDriver(), mSampleRate, AudioOutputChannelCount(),
|
||||
AudioInputChannelCount(), mOutputDeviceID, mInputDeviceID,
|
||||
AudioInputDevicePreference());
|
||||
AudioInputChannelCount(aTrack->mDeviceId), mOutputDeviceID,
|
||||
mInputDeviceID, AudioInputDevicePreference(aTrack->mDeviceId));
|
||||
SwitchAtNextIteration(driver);
|
||||
} else if (CurrentDriver()->AsAudioCallbackDriver()) {
|
||||
LOG(LogLevel::Debug,
|
||||
|
@ -757,10 +835,19 @@ void MediaTrackGraphImpl::RegisterAudioOutput(MediaTrack* aTrack, void* aKey) {
|
|||
tkv->mVolume = 1.0;
|
||||
|
||||
if (!CurrentDriver()->AsAudioCallbackDriver() && !Switching()) {
|
||||
uint32_t inputChannelCount =
|
||||
mNativeInputTrackGraphThread
|
||||
? AudioInputChannelCount(mNativeInputTrackGraphThread->mDeviceId)
|
||||
: 0;
|
||||
AudioInputType inputPreference =
|
||||
mNativeInputTrackGraphThread
|
||||
? AudioInputDevicePreference(
|
||||
mNativeInputTrackGraphThread->mDeviceId)
|
||||
: AudioInputType::Unknown;
|
||||
|
||||
AudioCallbackDriver* driver = new AudioCallbackDriver(
|
||||
this, CurrentDriver(), mSampleRate, AudioOutputChannelCount(),
|
||||
AudioInputChannelCount(), mOutputDeviceID, mInputDeviceID,
|
||||
AudioInputDevicePreference());
|
||||
inputChannelCount, mOutputDeviceID, mInputDeviceID, inputPreference);
|
||||
SwitchAtNextIteration(driver);
|
||||
}
|
||||
}
|
||||
|
@ -786,34 +873,54 @@ void MediaTrackGraphImpl::UnregisterAudioOutput(MediaTrack* aTrack,
|
|||
void MediaTrackGraphImpl::CloseAudioInput(DeviceInputTrack* aTrack) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aTrack);
|
||||
MOZ_ASSERT(aTrack->AsNativeInputTrack());
|
||||
|
||||
LOG(LogLevel::Debug, ("%p CloseInput: DeviceInputTrack %p for device %p",
|
||||
this, aTrack, aTrack->mDeviceId));
|
||||
|
||||
class Message : public ControlMessage {
|
||||
public:
|
||||
Message(MediaTrackGraphImpl* aGraph, CubebUtils::AudioDeviceID aID)
|
||||
: ControlMessage(nullptr), mGraph(aGraph), mID(aID) {}
|
||||
Message(MediaTrackGraphImpl* aGraph, DeviceInputTrack* aInputTrack)
|
||||
: ControlMessage(nullptr), mGraph(aGraph), mInputTrack(aInputTrack) {}
|
||||
void Run() override {
|
||||
TRACE("MTG::CloseAudioInputImpl ControlMessage");
|
||||
mGraph->CloseAudioInputImpl(mID);
|
||||
mGraph->CloseAudioInputImpl(mInputTrack);
|
||||
}
|
||||
MediaTrackGraphImpl* mGraph;
|
||||
CubebUtils::AudioDeviceID mID;
|
||||
DeviceInputTrack* mInputTrack;
|
||||
};
|
||||
|
||||
// NativeInputTrack is still alive (in mTracks) even we remove it here.
|
||||
mNativeInputTrackOnMain = nullptr;
|
||||
// DeviceInputTrack is still alive (in mTracks) even we remove it here, since
|
||||
// aTrack->Destroy() is called after this. See DeviceInputTrack::CloseAudio
|
||||
// for more details.
|
||||
if (NativeInputTrack* native = aTrack->AsNativeInputTrack()) {
|
||||
MOZ_ASSERT(mNativeInputTrackMainThread);
|
||||
mNativeInputTrackMainThread = nullptr;
|
||||
} else {
|
||||
NonNativeInputTrack* nonNative = aTrack->AsNonNativeInputTrack();
|
||||
MOZ_ASSERT(nonNative);
|
||||
MOZ_ASSERT(mNonNativeInputTracksMainThread.Contains(nonNative));
|
||||
DebugOnly<bool> removed =
|
||||
mNonNativeInputTracksMainThread.RemoveElement(nonNative);
|
||||
MOZ_ASSERT(removed);
|
||||
}
|
||||
|
||||
this->AppendMessage(MakeUnique<Message>(this, aTrack->mDeviceId));
|
||||
this->AppendMessage(MakeUnique<Message>(this, aTrack));
|
||||
|
||||
if (aTrack->AsNativeInputTrack()) {
|
||||
LOG(LogLevel::Debug,
|
||||
("%p Native input device %p is closed!", this, aTrack->mDeviceId));
|
||||
SetNewNativeInput();
|
||||
}
|
||||
}
|
||||
|
||||
// All AudioInput listeners get the same speaker data (at least for now).
|
||||
void MediaTrackGraphImpl::NotifyOutputData(AudioDataValue* aBuffer,
|
||||
size_t aFrames, TrackRate aRate,
|
||||
uint32_t aChannels) {
|
||||
if (!mNativeInputTrackOnGraph) {
|
||||
if (!mNativeInputTrackGraphThread) {
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mNativeInputTrackOnGraph->mDeviceId == mInputDeviceID);
|
||||
MOZ_ASSERT(mNativeInputTrackGraphThread->mDeviceId == mInputDeviceID);
|
||||
|
||||
#if defined(MOZ_WEBRTC)
|
||||
for (const auto& track : mTracks) {
|
||||
|
@ -825,11 +932,11 @@ void MediaTrackGraphImpl::NotifyOutputData(AudioDataValue* aBuffer,
|
|||
}
|
||||
|
||||
void MediaTrackGraphImpl::NotifyInputStopped() {
|
||||
if (!mNativeInputTrackOnGraph) {
|
||||
if (!mNativeInputTrackGraphThread) {
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mNativeInputTrackOnGraph->mDeviceId == mInputDeviceID);
|
||||
mNativeInputTrackOnGraph->NotifyInputStopped(this);
|
||||
MOZ_ASSERT(mNativeInputTrackGraphThread->mDeviceId == mInputDeviceID);
|
||||
mNativeInputTrackGraphThread->NotifyInputStopped(this);
|
||||
}
|
||||
|
||||
void MediaTrackGraphImpl::NotifyInputData(const AudioDataValue* aBuffer,
|
||||
|
@ -839,22 +946,22 @@ void MediaTrackGraphImpl::NotifyInputData(const AudioDataValue* aBuffer,
|
|||
// Either we have an audio input device, or we just removed the audio input
|
||||
// this iteration, and we're switching back to an output-only driver next
|
||||
// iteration.
|
||||
MOZ_ASSERT(mNativeInputTrackOnGraph || Switching());
|
||||
if (!mNativeInputTrackOnGraph) {
|
||||
MOZ_ASSERT(mNativeInputTrackGraphThread || Switching());
|
||||
if (!mNativeInputTrackGraphThread) {
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mNativeInputTrackOnGraph->mDeviceId == mInputDeviceID);
|
||||
mNativeInputTrackOnGraph->NotifyInputData(this, aBuffer, aFrames, aRate,
|
||||
aChannels, aAlreadyBuffered);
|
||||
MOZ_ASSERT(mNativeInputTrackGraphThread->mDeviceId == mInputDeviceID);
|
||||
mNativeInputTrackGraphThread->NotifyInputData(this, aBuffer, aFrames, aRate,
|
||||
aChannels, aAlreadyBuffered);
|
||||
}
|
||||
|
||||
void MediaTrackGraphImpl::DeviceChangedImpl() {
|
||||
MOZ_ASSERT(OnGraphThread());
|
||||
if (!mNativeInputTrackOnGraph) {
|
||||
if (!mNativeInputTrackGraphThread) {
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mNativeInputTrackOnGraph->mDeviceId == mInputDeviceID);
|
||||
mNativeInputTrackOnGraph->DeviceChanged(this);
|
||||
MOZ_ASSERT(mNativeInputTrackGraphThread->mDeviceId == mInputDeviceID);
|
||||
mNativeInputTrackGraphThread->DeviceChanged(this);
|
||||
}
|
||||
|
||||
void MediaTrackGraphImpl::SetMaxOutputChannelCount(uint32_t aMaxChannelCount) {
|
||||
|
@ -938,22 +1045,66 @@ static const char* GetAudioInputTypeString(const AudioInputType& aType) {
|
|||
return aType == AudioInputType::Voice ? "Voice" : "Unknown";
|
||||
}
|
||||
|
||||
void MediaTrackGraphImpl::ReevaluateInputDevice() {
|
||||
void MediaTrackGraphImpl::ReevaluateInputDevice(CubebUtils::AudioDeviceID aID) {
|
||||
MOZ_ASSERT(OnGraphThread());
|
||||
|
||||
LOG(LogLevel::Debug, ("%p: ReevaluateInputDevice: device %p", this, aID));
|
||||
|
||||
DeviceInputTrack* track = GetDeviceInputTrackGraphThread(aID);
|
||||
if (!track) {
|
||||
LOG(LogLevel::Debug,
|
||||
("%p: No DeviceInputTrack for this device. Ignore", this));
|
||||
return;
|
||||
}
|
||||
|
||||
bool needToSwitch = false;
|
||||
|
||||
if (NonNativeInputTrack* nonNative = track->AsNonNativeInputTrack()) {
|
||||
if (nonNative->NumberOfChannels() != AudioInputChannelCount(aID)) {
|
||||
LOG(LogLevel::Debug,
|
||||
("%p: %u-channel non-native input device %p (track %p) is "
|
||||
"re-configured to %d-channel",
|
||||
this, nonNative->NumberOfChannels(), aID, track,
|
||||
AudioInputChannelCount(aID)));
|
||||
needToSwitch = true;
|
||||
}
|
||||
if (nonNative->DevicePreference() != AudioInputDevicePreference(aID)) {
|
||||
LOG(LogLevel::Debug,
|
||||
("%p: %s-type non-native input device %p (track %p) is re-configured "
|
||||
"to %s-type",
|
||||
this, GetAudioInputTypeString(nonNative->DevicePreference()), aID,
|
||||
track, GetAudioInputTypeString(AudioInputDevicePreference(aID))));
|
||||
needToSwitch = true;
|
||||
}
|
||||
|
||||
if (needToSwitch) {
|
||||
nonNative->StopAudio();
|
||||
nonNative->StartAudio(MakeRefPtr<AudioInputSource>(
|
||||
MakeRefPtr<AudioInputSourceListener>(nonNative),
|
||||
nonNative->GenerateSourceId(), aID, AudioInputChannelCount(aID),
|
||||
AudioInputDevicePreference(aID) == AudioInputType::Voice,
|
||||
nonNative->mPrincipalHandle, nonNative->mSampleRate, GraphRate(),
|
||||
StaticPrefs::media_clockdrift_buffering()));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(track->AsNativeInputTrack());
|
||||
|
||||
if (AudioCallbackDriver* audioCallbackDriver =
|
||||
CurrentDriver()->AsAudioCallbackDriver()) {
|
||||
if (audioCallbackDriver->InputChannelCount() != AudioInputChannelCount()) {
|
||||
if (audioCallbackDriver->InputChannelCount() !=
|
||||
AudioInputChannelCount(aID)) {
|
||||
LOG(LogLevel::Debug,
|
||||
("%p: ReevaluateInputDevice: %u-channel AudioCallbackDriver %p is "
|
||||
"re-configured to %d-channel",
|
||||
this, audioCallbackDriver->InputChannelCount(), audioCallbackDriver,
|
||||
AudioInputChannelCount()));
|
||||
AudioInputChannelCount(aID)));
|
||||
needToSwitch = true;
|
||||
}
|
||||
if (audioCallbackDriver->InputDevicePreference() !=
|
||||
AudioInputDevicePreference()) {
|
||||
AudioInputDevicePreference(aID)) {
|
||||
LOG(LogLevel::Debug,
|
||||
("%p: ReevaluateInputDevice: %s-type AudioCallbackDriver %p is "
|
||||
"re-configured to %s-type",
|
||||
|
@ -961,7 +1112,7 @@ void MediaTrackGraphImpl::ReevaluateInputDevice() {
|
|||
GetAudioInputTypeString(
|
||||
audioCallbackDriver->InputDevicePreference()),
|
||||
audioCallbackDriver,
|
||||
GetAudioInputTypeString(AudioInputDevicePreference())));
|
||||
GetAudioInputTypeString(AudioInputDevicePreference(aID))));
|
||||
needToSwitch = true;
|
||||
}
|
||||
} else if (Switching() && NextDriver()->AsAudioCallbackDriver()) {
|
||||
|
@ -975,8 +1126,8 @@ void MediaTrackGraphImpl::ReevaluateInputDevice() {
|
|||
if (needToSwitch) {
|
||||
AudioCallbackDriver* newDriver = new AudioCallbackDriver(
|
||||
this, CurrentDriver(), mSampleRate, AudioOutputChannelCount(),
|
||||
AudioInputChannelCount(), mOutputDeviceID, mInputDeviceID,
|
||||
AudioInputDevicePreference());
|
||||
AudioInputChannelCount(aID), mOutputDeviceID, mInputDeviceID,
|
||||
AudioInputDevicePreference(aID));
|
||||
SwitchAtNextIteration(newDriver);
|
||||
}
|
||||
}
|
||||
|
@ -3887,25 +4038,69 @@ GraphTime MediaTrackGraph::ProcessedTime() const {
|
|||
return static_cast<const MediaTrackGraphImpl*>(this)->mProcessedTime;
|
||||
}
|
||||
|
||||
uint32_t MediaTrackGraphImpl::AudioInputChannelCount() {
|
||||
uint32_t MediaTrackGraphImpl::AudioInputChannelCount(
|
||||
CubebUtils::AudioDeviceID aID) {
|
||||
MOZ_ASSERT(OnGraphThreadOrNotRunning());
|
||||
|
||||
if (!mNativeInputTrackOnGraph) {
|
||||
return 0;
|
||||
}
|
||||
MOZ_ASSERT(mNativeInputTrackOnGraph->mDeviceId == mInputDeviceID);
|
||||
return mNativeInputTrackOnGraph->MaxRequestedInputChannels();
|
||||
DeviceInputTrack* t = GetDeviceInputTrackGraphThread(aID);
|
||||
return t ? t->MaxRequestedInputChannels() : 0;
|
||||
}
|
||||
|
||||
AudioInputType MediaTrackGraphImpl::AudioInputDevicePreference() {
|
||||
AudioInputType MediaTrackGraphImpl::AudioInputDevicePreference(
|
||||
CubebUtils::AudioDeviceID aID) {
|
||||
MOZ_ASSERT(OnGraphThreadOrNotRunning());
|
||||
DeviceInputTrack* t = GetDeviceInputTrackGraphThread(aID);
|
||||
return t && t->HasVoiceInput() ? AudioInputType::Voice
|
||||
: AudioInputType::Unknown;
|
||||
}
|
||||
|
||||
if (!mNativeInputTrackOnGraph) {
|
||||
return AudioInputType::Unknown;
|
||||
void MediaTrackGraphImpl::SetNewNativeInput() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!mNativeInputTrackMainThread);
|
||||
|
||||
LOG(LogLevel::Debug, ("%p SetNewNativeInput", this));
|
||||
|
||||
if (mNonNativeInputTracksMainThread.IsEmpty()) {
|
||||
LOG(LogLevel::Debug, ("%p No other devices opened. Do nothing", this));
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mNativeInputTrackOnGraph->mDeviceId == mInputDeviceID);
|
||||
return mNativeInputTrackOnGraph->HasVoiceInput() ? AudioInputType::Voice
|
||||
: AudioInputType::Unknown;
|
||||
|
||||
const CubebUtils::AudioDeviceID deviceId =
|
||||
mNonNativeInputTracksMainThread[0]->mDeviceId;
|
||||
const PrincipalHandle principal =
|
||||
mNonNativeInputTracksMainThread[0]->mPrincipalHandle;
|
||||
|
||||
LOG(LogLevel::Debug,
|
||||
("%p Select device %p as the new native input device", this, deviceId));
|
||||
|
||||
struct TrackListener {
|
||||
DeviceInputConsumerTrack* track;
|
||||
// Keep its reference so it won't be dropped when after
|
||||
// DisconnectDeviceInput().
|
||||
RefPtr<AudioDataListener> listener;
|
||||
};
|
||||
nsTArray<TrackListener> pairs;
|
||||
|
||||
for (const auto& track :
|
||||
mNonNativeInputTracksMainThread[0]->GetConsumerTracks()) {
|
||||
pairs.AppendElement(
|
||||
TrackListener{track.get(), track->GetAudioDataListener().get()});
|
||||
}
|
||||
|
||||
for (TrackListener& pair : pairs) {
|
||||
pair.track->DisconnectDeviceInput();
|
||||
}
|
||||
|
||||
for (TrackListener& pair : pairs) {
|
||||
pair.track->ConnectDeviceInput(deviceId, pair.listener.get(), principal);
|
||||
LOG(LogLevel::Debug,
|
||||
("%p: Reinitialize AudioProcessingTrack %p for device %p", this,
|
||||
pair.track, deviceId));
|
||||
}
|
||||
|
||||
LOG(LogLevel::Debug,
|
||||
("%p Native input device is set to device %p now", this, deviceId));
|
||||
|
||||
MOZ_ASSERT(mNativeInputTrackMainThread);
|
||||
}
|
||||
|
||||
// nsIThreadObserver methods
|
||||
|
|
|
@ -97,6 +97,7 @@ class MediaTrack;
|
|||
class MediaTrackGraph;
|
||||
class MediaTrackGraphImpl;
|
||||
class MediaTrackListener;
|
||||
class DeviceInputConsumerTrack;
|
||||
class DeviceInputTrack;
|
||||
class ProcessedMediaTrack;
|
||||
class SourceMediaTrack;
|
||||
|
@ -360,6 +361,9 @@ class MediaTrack : public mozilla::LinkedListElement<MediaTrack> {
|
|||
virtual CrossGraphTransmitter* AsCrossGraphTransmitter() { return nullptr; }
|
||||
virtual CrossGraphReceiver* AsCrossGraphReceiver() { return nullptr; }
|
||||
virtual DeviceInputTrack* AsDeviceInputTrack() { return nullptr; }
|
||||
virtual DeviceInputConsumerTrack* AsDeviceInputConsumerTrack() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// These Impl methods perform the core functionality of the control methods
|
||||
// above, on the media graph thread.
|
||||
|
|
|
@ -399,16 +399,21 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
|
|||
TrackTime PlayAudio(AudioMixer* aMixer, const TrackKeyAndVolume& aTkv,
|
||||
GraphTime aPlayedTime);
|
||||
|
||||
/* Do not call this directly. For users who need to get a NativeInputTrack,
|
||||
* use NativeInputTrack::OpenAudio() instead. This should only be used in
|
||||
* NativeInputTrack to get the existing NativeInputTrack paired with the given
|
||||
/* Do not call this directly. For users who need to get a DeviceInputTrack,
|
||||
* use DeviceInputTrack::OpenAudio() instead. This should only be used in
|
||||
* DeviceInputTrack to get the existing DeviceInputTrack paired with the given
|
||||
* device in this graph. Main thread only.*/
|
||||
NativeInputTrack* GetNativeInputTrack();
|
||||
DeviceInputTrack* GetDeviceInputTrackMainThread(
|
||||
CubebUtils::AudioDeviceID aID);
|
||||
|
||||
/* Do not call this directly. This should only be used in DeviceInputTrack to
|
||||
* get the existing NativeInputTrackMain thread only.*/
|
||||
NativeInputTrack* GetNativeInputTrackMainThread();
|
||||
|
||||
/* 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
|
||||
* aListener. */
|
||||
void OpenAudioInputImpl(NativeInputTrack* aTrack);
|
||||
void OpenAudioInputImpl(DeviceInputTrack* aTrack);
|
||||
/* Called on the main thread when something requests audio from an input
|
||||
* audio device aID. */
|
||||
virtual void OpenAudioInput(DeviceInputTrack* aTrack) override;
|
||||
|
@ -416,7 +421,7 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
|
|||
/* Runs off a message on the graph when input audio from aID is not needed
|
||||
* anymore, for a particular track. It can be that other tracks still need
|
||||
* audio from this audio input device. */
|
||||
void CloseAudioInputImpl(CubebUtils::AudioDeviceID aID);
|
||||
void CloseAudioInputImpl(DeviceInputTrack* aTrack);
|
||||
/* Called on the main thread when input audio from aID is not needed
|
||||
* anymore, for a particular track. It can be that other tracks still need
|
||||
* audio from this audio input device. */
|
||||
|
@ -432,7 +437,7 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
|
|||
/* Called on the graph thread when the input device settings should be
|
||||
* reevaluated, for example, if the channel count of the input track should
|
||||
* be changed. */
|
||||
void ReevaluateInputDevice();
|
||||
void ReevaluateInputDevice(CubebUtils::AudioDeviceID aID);
|
||||
|
||||
/* Called on the graph thread when there is new output data for listeners.
|
||||
* This is the mixed audio output of this MediaTrackGraph. */
|
||||
|
@ -506,11 +511,9 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
|
|||
* channel counts requested by the listeners. The max channel count is
|
||||
* delivered to the listeners themselves, and they take care of downmixing.
|
||||
*/
|
||||
uint32_t AudioInputChannelCount();
|
||||
uint32_t AudioInputChannelCount(CubebUtils::AudioDeviceID aID);
|
||||
|
||||
AudioInputType AudioInputDevicePreference();
|
||||
|
||||
CubebUtils::AudioDeviceID InputDeviceID() { return mInputDeviceID; }
|
||||
AudioInputType AudioInputDevicePreference(CubebUtils::AudioDeviceID aID);
|
||||
|
||||
double MediaTimeToSeconds(GraphTime aTime) const {
|
||||
NS_ASSERTION(aTime > -TRACK_TIME_MAX && aTime <= TRACK_TIME_MAX,
|
||||
|
@ -726,23 +729,18 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
|
|||
/**
|
||||
* Devices to use for cubeb input & output, or nullptr for default device.
|
||||
* A MediaTrackGraph always has an output (even if silent).
|
||||
* If `mNativeInputTrackOnGraph` is not NULL, this MediaTrackGraph wants audio
|
||||
* input.
|
||||
* If `mNativeInputTrackGraphThread` is not NULL, this MediaTrackGraph wants
|
||||
* audio input.
|
||||
*
|
||||
* All mInputDeviceID access is on the graph thread except for reads via
|
||||
* InputDeviceID(), which are racy but used only for comparison.
|
||||
*
|
||||
* In any case, the number of channels to use can be queried (on the graph
|
||||
* thread) by AudioInputChannelCount() and AudioOutputChannelCount().
|
||||
* All mInputDeviceID access is on the graph thread.
|
||||
*/
|
||||
std::atomic<CubebUtils::AudioDeviceID> mInputDeviceID;
|
||||
CubebUtils::AudioDeviceID mInputDeviceID;
|
||||
CubebUtils::AudioDeviceID mOutputDeviceID;
|
||||
|
||||
// Track the native input device in graph. Graph thread only.
|
||||
// TODO: Once multiple input devices is supported,
|
||||
// mNativeInputTrackOnGraph->mDeviceId could replace mInputDeviceID since no
|
||||
// other thread will read mInputDeviceID.
|
||||
RefPtr<NativeInputTrack> mNativeInputTrackOnGraph;
|
||||
// Track the native and non-native input device in graph. Graph thread only.
|
||||
// TODO: Replace mInputDeviceID by mNativeInputTrackGraphThread->mDeviceId.
|
||||
RefPtr<NativeInputTrack> mNativeInputTrackGraphThread;
|
||||
nsTArray<RefPtr<NonNativeInputTrack>> mNonNativeInputTracksGraphThread;
|
||||
|
||||
/**
|
||||
* List of resume operations waiting for a switch to an AudioCallbackDriver.
|
||||
|
@ -953,6 +951,15 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
|
|||
private:
|
||||
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
|
||||
|
||||
// Returns the DeviceInputTrack paired with the device of aID if it exists in
|
||||
// the graph. Otherwise, return nullptr. Graph thread only.
|
||||
DeviceInputTrack* GetDeviceInputTrackGraphThread(
|
||||
CubebUtils::AudioDeviceID aID);
|
||||
|
||||
// Set a new native iput device when the current native input device is close.
|
||||
// Main thread only.
|
||||
void SetNewNativeInput();
|
||||
|
||||
/**
|
||||
* This class uses manual memory management, and all pointers to it are raw
|
||||
* pointers. However, in order for it to implement nsIMemoryReporter, it needs
|
||||
|
@ -1016,9 +1023,10 @@ class MediaTrackGraphImpl : public MediaTrackGraph,
|
|||
uint32_t mMaxOutputChannelCount;
|
||||
|
||||
/*
|
||||
* Hold the NativeInputTrack for a certain device.
|
||||
* Hold the NativeInputTrack or NonNativeInputTrack for a certain device.
|
||||
*/
|
||||
RefPtr<NativeInputTrack> mNativeInputTrackOnMain;
|
||||
RefPtr<NativeInputTrack> mNativeInputTrackMainThread;
|
||||
nsTArray<RefPtr<NonNativeInputTrack>> mNonNativeInputTracksMainThread;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -127,7 +127,8 @@ TEST(TestAudioInputSource, DataOutputBeforeStartAndAfterStop)
|
|||
|
||||
// It's ok to call GetAudioSegment before starting
|
||||
{
|
||||
AudioSegment data = ais->GetAudioSegment(requestFrames);
|
||||
AudioSegment data =
|
||||
ais->GetAudioSegment(requestFrames, AudioInputSource::Consumer::Same);
|
||||
EXPECT_EQ(data.GetDuration(), requestFrames);
|
||||
EXPECT_TRUE(data.IsNull());
|
||||
}
|
||||
|
@ -161,7 +162,8 @@ TEST(TestAudioInputSource, DataOutputBeforeStartAndAfterStop)
|
|||
size_t expectedSamples =
|
||||
expectedSegment.WriteToInterleavedBuffer(expected, channels);
|
||||
|
||||
AudioSegment actualSegment = ais->GetAudioSegment(requestFrames);
|
||||
AudioSegment actualSegment =
|
||||
ais->GetAudioSegment(requestFrames, AudioInputSource::Consumer::Same);
|
||||
EXPECT_EQ(actualSegment.GetDuration(), requestFrames);
|
||||
nsTArray<AudioDataValue> actual;
|
||||
size_t actualSamples =
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -79,6 +79,91 @@ class TestDeviceInputTrack : public testing::Test {
|
|||
RefPtr<MockGraphImpl> mGraph;
|
||||
};
|
||||
|
||||
TEST_F(TestDeviceInputTrack, DeviceInputConsumerTrack) {
|
||||
class TestDeviceInputConsumerTrack : public DeviceInputConsumerTrack {
|
||||
public:
|
||||
static TestDeviceInputConsumerTrack* Create(MediaTrackGraph* aGraph) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
TestDeviceInputConsumerTrack* track =
|
||||
new TestDeviceInputConsumerTrack(aGraph->GraphRate());
|
||||
aGraph->AddTrack(track);
|
||||
return track;
|
||||
}
|
||||
|
||||
void Destroy() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
DisconnectDeviceInput();
|
||||
DeviceInputConsumerTrack::Destroy();
|
||||
}
|
||||
|
||||
void ProcessInput(GraphTime aFrom, GraphTime aTo,
|
||||
uint32_t aFlags) override{/* Ignored */};
|
||||
|
||||
uint32_t NumberOfChannels() const override {
|
||||
if (mInputs.IsEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
DeviceInputTrack* t = mInputs[0]->GetSource()->AsDeviceInputTrack();
|
||||
MOZ_ASSERT(t);
|
||||
return t->NumberOfChannels();
|
||||
}
|
||||
|
||||
private:
|
||||
explicit TestDeviceInputConsumerTrack(TrackRate aSampleRate)
|
||||
: DeviceInputConsumerTrack(aSampleRate) {}
|
||||
};
|
||||
|
||||
class TestAudioDataListener : public AudioDataListener {
|
||||
public:
|
||||
TestAudioDataListener(uint32_t aChannelCount, bool aIsVoice)
|
||||
: mChannelCount(aChannelCount), mIsVoice(aIsVoice) {}
|
||||
// Graph thread APIs: AudioDataListenerInterface implementations.
|
||||
uint32_t RequestedInputChannelCount(MediaTrackGraphImpl* aGraph) override {
|
||||
MOZ_ASSERT(aGraph->OnGraphThread());
|
||||
return mChannelCount;
|
||||
}
|
||||
bool IsVoiceInput(MediaTrackGraphImpl* aGraph) const override {
|
||||
return mIsVoice;
|
||||
};
|
||||
void DeviceChanged(MediaTrackGraphImpl* aGraph) override { /* Ignored */
|
||||
}
|
||||
void Disconnect(MediaTrackGraphImpl* aGraph) override{/* Ignored */};
|
||||
|
||||
private:
|
||||
~TestAudioDataListener() = default;
|
||||
|
||||
// Graph thread-only.
|
||||
uint32_t mChannelCount;
|
||||
// Any thread.
|
||||
const bool mIsVoice;
|
||||
};
|
||||
|
||||
const PrincipalHandle testPrincipal =
|
||||
MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
|
||||
|
||||
const CubebUtils::AudioDeviceID device1 = (void*)1;
|
||||
RefPtr<TestAudioDataListener> listener1 = new TestAudioDataListener(1, false);
|
||||
RefPtr<TestDeviceInputConsumerTrack> track1 =
|
||||
TestDeviceInputConsumerTrack::Create(mGraph);
|
||||
track1->ConnectDeviceInput(device1, listener1.get(), testPrincipal);
|
||||
EXPECT_TRUE(track1->ConnectToNativeDevice());
|
||||
EXPECT_FALSE(track1->ConnectToNonNativeDevice());
|
||||
|
||||
const CubebUtils::AudioDeviceID device2 = (void*)2;
|
||||
RefPtr<TestAudioDataListener> listener2 = new TestAudioDataListener(2, false);
|
||||
RefPtr<TestDeviceInputConsumerTrack> track2 =
|
||||
TestDeviceInputConsumerTrack::Create(mGraph);
|
||||
track2->ConnectDeviceInput(device2, listener2.get(), testPrincipal);
|
||||
EXPECT_FALSE(track2->ConnectToNativeDevice());
|
||||
EXPECT_TRUE(track2->ConnectToNonNativeDevice());
|
||||
|
||||
track2->Destroy();
|
||||
mGraph->RemoveTrackGraphThread(track2);
|
||||
|
||||
track1->Destroy();
|
||||
mGraph->RemoveTrackGraphThread(track1);
|
||||
}
|
||||
|
||||
TEST_F(TestDeviceInputTrack, NativeInputTrackData) {
|
||||
const uint32_t flags = 0;
|
||||
const CubebUtils::AudioDeviceID deviceId = (void*)1;
|
||||
|
@ -138,26 +223,6 @@ TEST_F(TestDeviceInputTrack, NativeInputTrackData) {
|
|||
mGraph->RemoveTrackGraphThread(track);
|
||||
}
|
||||
|
||||
TEST_F(TestDeviceInputTrack, OpenTwiceWithoutCloseFirst) {
|
||||
const CubebUtils::AudioDeviceID deviceId1 = (void*)1;
|
||||
const CubebUtils::AudioDeviceID deviceId2 = (void*)2;
|
||||
|
||||
const PrincipalHandle testPrincipal =
|
||||
MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
|
||||
|
||||
auto r1 = NativeInputTrack::OpenAudio(mGraph.get(), deviceId1, testPrincipal,
|
||||
nullptr);
|
||||
ASSERT_TRUE(r1.isOk());
|
||||
RefPtr<NativeInputTrack> track1 = r1.unwrap()->AsNativeInputTrack();
|
||||
ASSERT_TRUE(track1);
|
||||
|
||||
auto r2 = NativeInputTrack::OpenAudio(mGraph.get(), deviceId2, testPrincipal,
|
||||
nullptr);
|
||||
EXPECT_TRUE(r2.isErr());
|
||||
|
||||
NativeInputTrack::CloseAudio(std::move(track1), nullptr);
|
||||
}
|
||||
|
||||
class MockEventListener : public AudioInputSource::EventListener {
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MockEventListener, override);
|
||||
|
|
|
@ -215,8 +215,9 @@ void MediaEngineWebRTCMicrophoneSource::ApplySettings(
|
|||
aPrefs.mResidualEchoOn;
|
||||
|
||||
RefPtr<MediaEngineWebRTCMicrophoneSource> that = this;
|
||||
CubebUtils::AudioDeviceID deviceID = mDeviceInfo->DeviceID();
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
||||
__func__, [this, that, track = mTrack, prefs = aPrefs,
|
||||
__func__, [this, that, deviceID, track = mTrack, prefs = aPrefs,
|
||||
audioProcessingConfig = mAudioProcessingConfig] {
|
||||
mSettings->mEchoCancellation.Value() = prefs.mAecOn;
|
||||
mSettings->mAutoGainControl.Value() = prefs.mAgcOn;
|
||||
|
@ -224,16 +225,19 @@ void MediaEngineWebRTCMicrophoneSource::ApplySettings(
|
|||
mSettings->mChannelCount.Value() = prefs.mChannels;
|
||||
|
||||
class Message : public ControlMessage {
|
||||
CubebUtils::AudioDeviceID mDeviceID;
|
||||
const RefPtr<AudioInputProcessing> mInputProcessing;
|
||||
const AudioProcessing::Config mAudioProcessingConfig;
|
||||
const bool mPassThrough;
|
||||
const uint32_t mRequestedInputChannelCount;
|
||||
|
||||
public:
|
||||
Message(MediaTrack* aTrack, AudioInputProcessing* aInputProcessing,
|
||||
Message(MediaTrack* aTrack, CubebUtils::AudioDeviceID aDeviceID,
|
||||
AudioInputProcessing* aInputProcessing,
|
||||
const AudioProcessing::Config& aAudioProcessingConfig,
|
||||
bool aPassThrough, uint32_t aRequestedInputChannelCount)
|
||||
: ControlMessage(aTrack),
|
||||
mDeviceID(aDeviceID),
|
||||
mInputProcessing(aInputProcessing),
|
||||
mAudioProcessingConfig(aAudioProcessingConfig),
|
||||
mPassThrough(aPassThrough),
|
||||
|
@ -245,7 +249,7 @@ void MediaEngineWebRTCMicrophoneSource::ApplySettings(
|
|||
{
|
||||
TRACE("SetRequestedInputChannelCount");
|
||||
mInputProcessing->SetRequestedInputChannelCount(
|
||||
mTrack->GraphImpl(), mRequestedInputChannelCount);
|
||||
mTrack->GraphImpl(), mDeviceID, mRequestedInputChannelCount);
|
||||
}
|
||||
{
|
||||
TRACE("SetPassThrough")
|
||||
|
@ -262,9 +266,9 @@ void MediaEngineWebRTCMicrophoneSource::ApplySettings(
|
|||
if (track->IsDestroyed()) {
|
||||
return;
|
||||
}
|
||||
track->GraphImpl()->AppendMessage(
|
||||
MakeUnique<Message>(track, mInputProcessing, audioProcessingConfig,
|
||||
passThrough, prefs.mChannels));
|
||||
track->GraphImpl()->AppendMessage(MakeUnique<Message>(
|
||||
track, deviceID, mInputProcessing, audioProcessingConfig,
|
||||
passThrough, prefs.mChannels));
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -405,20 +409,9 @@ nsresult MediaEngineWebRTCMicrophoneSource::Start() {
|
|||
|
||||
MOZ_ASSERT(mState == kAllocated || mState == kStopped);
|
||||
|
||||
// This check is unreliable due to potential in-flight device updates.
|
||||
// Multiple input devices are reliably excluded in ConnectDeviceInputImpl(),
|
||||
// but the check here provides some error reporting most of the
|
||||
// time.
|
||||
CubebUtils::AudioDeviceID deviceID = mDeviceInfo->DeviceID();
|
||||
if (mTrack->GraphImpl()->InputDeviceID() &&
|
||||
mTrack->GraphImpl()->InputDeviceID() != deviceID) {
|
||||
// For now, we only allow opening a single audio input device per document,
|
||||
// because we can only have one MTG per document.
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
ApplySettings(mCurrentPrefs);
|
||||
|
||||
CubebUtils::AudioDeviceID deviceID = mDeviceInfo->DeviceID();
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
||||
__func__, [inputProcessing = mInputProcessing, deviceID, track = mTrack,
|
||||
principal = mPrincipal] {
|
||||
|
@ -428,7 +421,7 @@ nsresult MediaEngineWebRTCMicrophoneSource::Start() {
|
|||
|
||||
track->GraphImpl()->AppendMessage(MakeUnique<StartStopMessage>(
|
||||
track, inputProcessing, StartStopMessage::Start));
|
||||
track->ConnectDeviceInput(deviceID, inputProcessing, principal);
|
||||
track->ConnectDeviceInput(deviceID, inputProcessing.get(), principal);
|
||||
}));
|
||||
|
||||
MOZ_ASSERT(mState != kReleased);
|
||||
|
@ -521,10 +514,11 @@ uint32_t AudioInputProcessing::GetRequestedInputChannelCount() {
|
|||
}
|
||||
|
||||
void AudioInputProcessing::SetRequestedInputChannelCount(
|
||||
MediaTrackGraphImpl* aGraph, uint32_t aRequestedInputChannelCount) {
|
||||
MediaTrackGraphImpl* aGraph, CubebUtils::AudioDeviceID aDeviceId,
|
||||
uint32_t aRequestedInputChannelCount) {
|
||||
mRequestedInputChannelCount = aRequestedInputChannelCount;
|
||||
|
||||
aGraph->ReevaluateInputDevice();
|
||||
aGraph->ReevaluateInputDevice(aDeviceId);
|
||||
}
|
||||
|
||||
void AudioInputProcessing::Start(MediaTrackGraphImpl* aGraph) {
|
||||
|
@ -1237,7 +1231,8 @@ void AudioProcessingTrack::ProcessInput(GraphTime aFrom, GraphTime aTo,
|
|||
} else {
|
||||
MOZ_ASSERT(mInputs.Length() == 1);
|
||||
AudioSegment data;
|
||||
GetInputSourceData(data, mInputs[0], aFrom, aTo);
|
||||
DeviceInputConsumerTrack::GetInputSourceData(data, mInputs[0], aFrom,
|
||||
aTo);
|
||||
mInputProcessing->Process(GraphImpl(), aFrom, aTo, &data,
|
||||
GetData<AudioSegment>());
|
||||
}
|
||||
|
@ -1249,61 +1244,6 @@ void AudioProcessingTrack::ProcessInput(GraphTime aFrom, GraphTime aTo,
|
|||
}
|
||||
}
|
||||
|
||||
void AudioProcessingTrack::GetInputSourceData(AudioSegment& aOutput,
|
||||
const MediaInputPort* aPort,
|
||||
GraphTime aFrom,
|
||||
GraphTime aTo) const {
|
||||
MOZ_ASSERT(mGraph->OnGraphThread());
|
||||
MOZ_ASSERT(aOutput.IsEmpty());
|
||||
|
||||
MediaTrack* source = aPort->GetSource();
|
||||
GraphTime next;
|
||||
for (GraphTime t = aFrom; t < aTo; t = next) {
|
||||
MediaInputPort::InputInterval interval =
|
||||
MediaInputPort::GetNextInputInterval(aPort, t);
|
||||
interval.mEnd = std::min(interval.mEnd, aTo);
|
||||
|
||||
const bool inputEnded =
|
||||
source->Ended() &&
|
||||
source->GetEnd() <=
|
||||
source->GraphTimeToTrackTimeWithBlocking(interval.mStart);
|
||||
|
||||
TrackTime ticks = interval.mEnd - interval.mStart;
|
||||
next = interval.mEnd;
|
||||
|
||||
if (interval.mStart >= interval.mEnd) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (inputEnded) {
|
||||
aOutput.AppendNullData(ticks);
|
||||
LOG_FRAME("(Graph %p, Driver %p) AudioProcessingTrack %p Getting %" PRId64
|
||||
" ticks of null data from input port source (ended input)",
|
||||
mGraph, mGraph->CurrentDriver(), this, ticks);
|
||||
} else if (interval.mInputIsBlocked) {
|
||||
aOutput.AppendNullData(ticks);
|
||||
LOG_FRAME("(Graph %p, Driver %p) AudioProcessingTrack %p Getting %" PRId64
|
||||
" ticks of null data from input port source (blocked input)",
|
||||
mGraph, mGraph->CurrentDriver(), this, ticks);
|
||||
} else if (source->IsSuspended()) {
|
||||
aOutput.AppendNullData(ticks);
|
||||
LOG_FRAME(
|
||||
"(Graph %p, Driver %p) AudioProcessingTrack %p Getting %" PRId64
|
||||
" ticks of null data from input port source (source is suspended)",
|
||||
mGraph, mGraph->CurrentDriver(), this, ticks);
|
||||
} else {
|
||||
TrackTime start =
|
||||
source->GraphTimeToTrackTimeWithBlocking(interval.mStart);
|
||||
TrackTime end = source->GraphTimeToTrackTimeWithBlocking(interval.mEnd);
|
||||
MOZ_ASSERT(source->GetData<AudioSegment>()->GetDuration() >= end);
|
||||
aOutput.AppendSlice(*source->GetData<AudioSegment>(), start, end);
|
||||
LOG_FRAME("(Graph %p, Driver %p) AudioInputTrack %p Getting %" PRId64
|
||||
" ticks of real data from input port source %p",
|
||||
mGraph, mGraph->CurrentDriver(), this, end - start, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioProcessingTrack::NotifyOutputData(MediaTrackGraphImpl* aGraph,
|
||||
AudioDataValue* aBuffer,
|
||||
size_t aFrames, TrackRate aRate,
|
||||
|
@ -1322,54 +1262,6 @@ void AudioProcessingTrack::SetInputProcessingImpl(
|
|||
mInputProcessing = std::move(aInputProcessing);
|
||||
}
|
||||
|
||||
nsresult AudioProcessingTrack::ConnectDeviceInput(
|
||||
CubebUtils::AudioDeviceID aId, AudioDataListener* aListener,
|
||||
const PrincipalHandle& aPrincipal) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(GraphImpl());
|
||||
MOZ_ASSERT(!mInputListener);
|
||||
MOZ_ASSERT(mDeviceId.isNothing());
|
||||
mInputListener = aListener;
|
||||
mDeviceId.emplace(aId);
|
||||
|
||||
auto r = DeviceInputTrack::OpenAudio(GraphImpl(), aId, aPrincipal,
|
||||
mInputListener.get());
|
||||
if (r.isErr()) {
|
||||
NS_WARNING("Failed to open audio device.");
|
||||
return r.unwrapErr();
|
||||
}
|
||||
|
||||
mDeviceInputTrack = r.unwrap();
|
||||
MOZ_ASSERT(mDeviceInputTrack);
|
||||
LOG("Open device %p (InputTrack=%p) for Mic source %p", aId,
|
||||
mDeviceInputTrack.get(), this);
|
||||
mPort = AllocateInputPort(mDeviceInputTrack.get());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void AudioProcessingTrack::DisconnectDeviceInput() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(GraphImpl());
|
||||
if (!mInputListener) {
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mPort);
|
||||
MOZ_ASSERT(mDeviceId.isSome());
|
||||
MOZ_ASSERT(mDeviceInputTrack);
|
||||
LOG("Close device %p (InputTrack=%p) for Mic source %p ", *mDeviceId,
|
||||
mDeviceInputTrack.get(), this);
|
||||
mPort->Destroy();
|
||||
DeviceInputTrack::CloseAudio(std::move(mDeviceInputTrack),
|
||||
mInputListener.get());
|
||||
mInputListener = nullptr;
|
||||
mDeviceId = Nothing();
|
||||
}
|
||||
|
||||
Maybe<CubebUtils::AudioDeviceID> AudioProcessingTrack::DeviceId() const {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mDeviceId;
|
||||
}
|
||||
|
||||
MediaEngineWebRTCAudioCaptureSource::MediaEngineWebRTCAudioCaptureSource(
|
||||
const MediaDevice* aMediaDevice) {
|
||||
MOZ_ASSERT(aMediaDevice->mMediaSource == MediaSourceEnum::AudioCapture);
|
||||
|
|
|
@ -141,6 +141,7 @@ class AudioInputProcessing : public AudioDataListener {
|
|||
void SetPassThrough(MediaTrackGraphImpl* aGraph, bool aPassThrough);
|
||||
uint32_t GetRequestedInputChannelCount();
|
||||
void SetRequestedInputChannelCount(MediaTrackGraphImpl* aGraph,
|
||||
CubebUtils::AudioDeviceID aDeviceId,
|
||||
uint32_t aRequestedInputChannelCount);
|
||||
// This is true when all processing is disabled, we can skip
|
||||
// packetization, resampling and other processing passes.
|
||||
|
@ -218,43 +219,17 @@ class AudioInputProcessing : public AudioDataListener {
|
|||
};
|
||||
|
||||
// MediaTrack subclass tailored for MediaEngineWebRTCMicrophoneSource.
|
||||
class AudioProcessingTrack : public ProcessedMediaTrack {
|
||||
class AudioProcessingTrack : public DeviceInputConsumerTrack {
|
||||
// Only accessed on the graph thread.
|
||||
RefPtr<AudioInputProcessing> mInputProcessing;
|
||||
|
||||
// Only accessed on the main thread. Link to the track producing raw audio
|
||||
// input data. Graph thread should use mInputs to get the source
|
||||
RefPtr<MediaInputPort> mPort;
|
||||
|
||||
// Only accessed on the main thread. This is the track producing raw audio
|
||||
// input data. Graph thread should MediaInputPort::GetSource() to get this
|
||||
RefPtr<DeviceInputTrack> mDeviceInputTrack;
|
||||
|
||||
// Only accessed on the main thread. Used for bookkeeping on main thread, such
|
||||
// that DisconnectDeviceInput can be idempotent.
|
||||
// XXX Should really be a CubebUtils::AudioDeviceID, but they aren't
|
||||
// copyable (opaque pointers)
|
||||
RefPtr<AudioDataListener> mInputListener;
|
||||
|
||||
// Only accessed on the main thread.
|
||||
Maybe<CubebUtils::AudioDeviceID> mDeviceId;
|
||||
|
||||
explicit AudioProcessingTrack(TrackRate aSampleRate)
|
||||
: ProcessedMediaTrack(aSampleRate, MediaSegment::AUDIO,
|
||||
new AudioSegment()) {}
|
||||
: DeviceInputConsumerTrack(aSampleRate) {}
|
||||
|
||||
~AudioProcessingTrack() = default;
|
||||
|
||||
public:
|
||||
// Main Thread API
|
||||
// Users of audio inputs go through the track so it can track when the
|
||||
// last track referencing an input goes away, so it can close the cubeb
|
||||
// input. Main thread only.
|
||||
nsresult ConnectDeviceInput(CubebUtils::AudioDeviceID aId,
|
||||
AudioDataListener* aListener,
|
||||
const PrincipalHandle& aPrincipal);
|
||||
void DisconnectDeviceInput();
|
||||
Maybe<CubebUtils::AudioDeviceID> DeviceId() const;
|
||||
void Destroy() override;
|
||||
void SetInputProcessing(RefPtr<AudioInputProcessing> aInputProcessing);
|
||||
static AudioProcessingTrack* Create(MediaTrackGraph* aGraph);
|
||||
|
@ -268,10 +243,6 @@ class AudioProcessingTrack : public ProcessedMediaTrack {
|
|||
"Must set mInputProcessing before exposing to content");
|
||||
return mInputProcessing->GetRequestedInputChannelCount();
|
||||
}
|
||||
// Get the data in [aFrom, aTo) from aPort->GetSource() to aOutput. aOutput
|
||||
// needs to be empty.
|
||||
void GetInputSourceData(AudioSegment& aOutput, const MediaInputPort* aPort,
|
||||
GraphTime aFrom, GraphTime aTo) const;
|
||||
// Pass the graph's mixed audio output to mInputProcessing for processing as
|
||||
// the reverse stream.
|
||||
void NotifyOutputData(MediaTrackGraphImpl* aGraph, AudioDataValue* aBuffer,
|
||||
|
|
|
@ -8489,7 +8489,7 @@
|
|||
|
||||
# ClockDrift desired buffering in milliseconds
|
||||
- name: media.clockdrift.buffering
|
||||
type: uint32_t
|
||||
type: RelaxedAtomicUint32
|
||||
mirror: always
|
||||
value: 50
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче