зеркало из https://github.com/mozilla/gecko-dev.git
571 строка
17 KiB
C++
571 строка
17 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifndef TrackEncoder_h_
|
|
#define TrackEncoder_h_
|
|
|
|
#include "AudioSegment.h"
|
|
#include "EncodedFrameContainer.h"
|
|
#include "MediaStreamGraph.h"
|
|
#include "StreamTracks.h"
|
|
#include "TrackMetadataBase.h"
|
|
#include "VideoSegment.h"
|
|
|
|
namespace mozilla {
|
|
|
|
class AbstractThread;
|
|
class TrackEncoder;
|
|
|
|
class TrackEncoderListener
|
|
{
|
|
public:
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TrackEncoderListener)
|
|
|
|
/**
|
|
* Called when the TrackEncoder's underlying encoder has been successfully
|
|
* initialized and there's non-null data ready to be encoded.
|
|
*/
|
|
virtual void Initialized(TrackEncoder* aEncoder) = 0;
|
|
|
|
/**
|
|
* Called when there's new data ready to be encoded.
|
|
* Always called after Initialized().
|
|
*/
|
|
virtual void DataAvailable(TrackEncoder* aEncoder) = 0;
|
|
|
|
/**
|
|
* Called after the TrackEncoder hit an unexpected error, causing it to
|
|
* abort operation.
|
|
*/
|
|
virtual void Error(TrackEncoder* aEncoder) = 0;
|
|
protected:
|
|
virtual ~TrackEncoderListener() {}
|
|
};
|
|
|
|
/**
|
|
* Base class of AudioTrackEncoder and VideoTrackEncoder. Lifetime managed by
|
|
* MediaEncoder. All methods are to be called only on the worker thread.
|
|
*
|
|
* MediaStreamTrackListeners will get store raw data in mIncomingBuffer, so
|
|
* mIncomingBuffer is protected by a lock. The control APIs are all called by
|
|
* MediaEncoder on its dedicated thread, where GetEncodedTrack is called
|
|
* periodically to swap out mIncomingBuffer, feed it to the encoder, and return
|
|
* the encoded data.
|
|
*/
|
|
class TrackEncoder
|
|
{
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TrackEncoder);
|
|
|
|
public:
|
|
explicit TrackEncoder(TrackRate aTrackRate);
|
|
|
|
virtual void Suspend(TimeStamp aTime) = 0;
|
|
|
|
virtual void Resume(TimeStamp aTime) = 0;
|
|
|
|
/**
|
|
* Called by MediaEncoder to cancel the encoding.
|
|
*/
|
|
virtual void Cancel() = 0;
|
|
|
|
/**
|
|
* Notifies us that we have reached the end of the stream and no more data
|
|
* will be appended.
|
|
*/
|
|
virtual void NotifyEndOfStream() = 0;
|
|
|
|
/**
|
|
* MediaStreamGraph notifies us about the time of the track's start.
|
|
* This gets called on the MediaEncoder thread after a dispatch.
|
|
*/
|
|
virtual void SetStartOffset(StreamTime aStartOffset) = 0;
|
|
|
|
/**
|
|
* Dispatched from MediaStreamGraph when it has run an iteration where the
|
|
* input track of the track this TrackEncoder is associated with didn't have
|
|
* any data.
|
|
*/
|
|
virtual void AdvanceBlockedInput(StreamTime aDuration) = 0;
|
|
|
|
/**
|
|
* MediaStreamGraph notifies us about the duration of data that has just been
|
|
* processed. This gets called on the MediaEncoder thread after a dispatch.
|
|
*/
|
|
virtual void AdvanceCurrentTime(StreamTime aDuration) = 0;
|
|
|
|
/**
|
|
* Creates and sets up meta data for a specific codec, called on the worker
|
|
* thread.
|
|
*/
|
|
virtual already_AddRefed<TrackMetadataBase> GetMetadata() = 0;
|
|
|
|
/**
|
|
* Encodes raw segments. Result data is returned in aData, and called on the
|
|
* worker thread.
|
|
*/
|
|
virtual nsresult GetEncodedTrack(EncodedFrameContainer& aData) = 0;
|
|
|
|
/**
|
|
* Returns true once this TrackEncoder is initialized.
|
|
*/
|
|
bool IsInitialized();
|
|
|
|
/**
|
|
* True if the track encoder has encoded all source segments coming from
|
|
* MediaStreamGraph. Call on the worker thread.
|
|
*/
|
|
bool IsEncodingComplete();
|
|
|
|
/**
|
|
* If this TrackEncoder was not already initialized, it is set to initialized
|
|
* and listeners are notified.
|
|
*/
|
|
void SetInitialized();
|
|
|
|
/**
|
|
* Notifies listeners that there is data available for encoding.
|
|
*/
|
|
void OnDataAvailable();
|
|
|
|
/**
|
|
* Called after an error. Cancels the encoding and notifies listeners.
|
|
*/
|
|
void OnError();
|
|
|
|
/**
|
|
* Registers a listener to events from this TrackEncoder.
|
|
* We hold a strong reference to the listener.
|
|
*/
|
|
void RegisterListener(TrackEncoderListener* aListener);
|
|
|
|
/**
|
|
* Unregisters a listener from events from this TrackEncoder.
|
|
* The listener will stop receiving events synchronously.
|
|
*/
|
|
bool UnregisterListener(TrackEncoderListener* aListener);
|
|
|
|
virtual void SetBitrate(const uint32_t aBitrate) = 0;
|
|
|
|
/**
|
|
* It's optional to set the worker thread, but if you do we'll assert that
|
|
* we are in the worker thread in every method that gets called.
|
|
*/
|
|
void SetWorkerThread(AbstractThread* aWorkerThread);
|
|
|
|
/**
|
|
* Measure size of internal buffers.
|
|
*/
|
|
virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) = 0;
|
|
|
|
protected:
|
|
virtual ~TrackEncoder()
|
|
{
|
|
MOZ_ASSERT(mListeners.IsEmpty());
|
|
}
|
|
|
|
/**
|
|
* True if the track encoder has encoded all source data.
|
|
*/
|
|
bool mEncodingComplete;
|
|
|
|
/**
|
|
* True if flag of EOS or any form of indicating EOS has set in the codec-
|
|
* encoder.
|
|
*/
|
|
bool mEosSetInEncoder;
|
|
|
|
/**
|
|
* True if the track encoder has been initialized successfully.
|
|
*/
|
|
bool mInitialized;
|
|
|
|
/**
|
|
* True once all data until the end of the input track has been received.
|
|
*/
|
|
bool mEndOfStream;
|
|
|
|
/**
|
|
* True once this encoding has been cancelled.
|
|
*/
|
|
bool mCanceled;
|
|
|
|
/**
|
|
* The latest current time reported to us from the MSG.
|
|
*/
|
|
StreamTime mCurrentTime;
|
|
|
|
// How many times we have tried to initialize the encoder.
|
|
uint32_t mInitCounter;
|
|
StreamTime mNotInitDuration;
|
|
|
|
bool mSuspended;
|
|
|
|
/**
|
|
* The track rate of source media.
|
|
*/
|
|
TrackRate mTrackRate;
|
|
|
|
/**
|
|
* If set we assert that all methods are called on this thread.
|
|
*/
|
|
RefPtr<AbstractThread> mWorkerThread;
|
|
|
|
nsTArray<RefPtr<TrackEncoderListener>> mListeners;
|
|
};
|
|
|
|
class AudioTrackEncoder : public TrackEncoder
|
|
{
|
|
public:
|
|
explicit AudioTrackEncoder(TrackRate aTrackRate)
|
|
: TrackEncoder(aTrackRate)
|
|
, mChannels(0)
|
|
, mSamplingRate(0)
|
|
, mAudioBitrate(0)
|
|
{}
|
|
|
|
/**
|
|
* Suspends encoding from mCurrentTime, i.e., all audio data until the next
|
|
* Resume() will be dropped.
|
|
*/
|
|
void Suspend(TimeStamp aTime) override;
|
|
|
|
/**
|
|
* Resumes encoding starting at mCurrentTime.
|
|
*/
|
|
void Resume(TimeStamp aTime) override;
|
|
|
|
/**
|
|
* Appends and consumes track data from aSegment.
|
|
*/
|
|
void AppendAudioSegment(AudioSegment&& aSegment);
|
|
|
|
/**
|
|
* Takes track data from the last time TakeTrackData ran until mCurrentTime
|
|
* and moves it to aSegment.
|
|
*/
|
|
void TakeTrackData(AudioSegment& aSegment);
|
|
|
|
template<typename T>
|
|
static
|
|
void InterleaveTrackData(nsTArray<const T*>& aInput,
|
|
int32_t aDuration,
|
|
uint32_t aOutputChannels,
|
|
AudioDataValue* aOutput,
|
|
float aVolume)
|
|
{
|
|
if (aInput.Length() < aOutputChannels) {
|
|
// Up-mix. This might make the mChannelData have more than aChannels.
|
|
AudioChannelsUpMix(&aInput, aOutputChannels, SilentChannel::ZeroChannel<T>());
|
|
}
|
|
|
|
if (aInput.Length() > aOutputChannels) {
|
|
DownmixAndInterleave(aInput, aDuration,
|
|
aVolume, aOutputChannels, aOutput);
|
|
} else {
|
|
InterleaveAndConvertBuffer(aInput.Elements(), aDuration, aVolume,
|
|
aOutputChannels, aOutput);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Interleaves the track data and stores the result into aOutput. Might need
|
|
* to up-mix or down-mix the channel data if the channels number of this chunk
|
|
* is different from aOutputChannels. The channel data from aChunk might be
|
|
* modified by up-mixing.
|
|
*/
|
|
static void InterleaveTrackData(AudioChunk& aChunk, int32_t aDuration,
|
|
uint32_t aOutputChannels,
|
|
AudioDataValue* aOutput);
|
|
|
|
/**
|
|
* De-interleaves the aInput data and stores the result into aOutput.
|
|
* No up-mix or down-mix operations inside.
|
|
*/
|
|
static void DeInterleaveTrackData(AudioDataValue* aInput, int32_t aDuration,
|
|
int32_t aChannels, AudioDataValue* aOutput);
|
|
|
|
/**
|
|
* Measure size of internal buffers.
|
|
*/
|
|
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) override;
|
|
|
|
void SetBitrate(const uint32_t aBitrate) override
|
|
{
|
|
mAudioBitrate = aBitrate;
|
|
}
|
|
|
|
/**
|
|
* Tries to initiate the AudioEncoder based on data in aSegment.
|
|
* This can be re-called often, as it will exit early should we already be
|
|
* initiated. mInitiated will only be set if there was enough data in
|
|
* aSegment to infer metadata. If mInitiated gets set, listeners are notified.
|
|
*
|
|
* Not having enough data in aSegment to initiate the encoder for an accumulated aDuration of one second will make us initiate with a default number of channels.
|
|
*
|
|
* If we attempt to initiate the underlying encoder but fail, we Cancel() and
|
|
* notify listeners.
|
|
*/
|
|
void TryInit(const AudioSegment& aSegment, StreamTime aDuration);
|
|
|
|
void Cancel() override;
|
|
|
|
/**
|
|
* Dispatched from MediaStreamGraph when we have finished feeding data to
|
|
* mIncomingBuffer.
|
|
*/
|
|
void NotifyEndOfStream() override;
|
|
|
|
void SetStartOffset(StreamTime aStartOffset) override;
|
|
|
|
/**
|
|
* Dispatched from MediaStreamGraph when it has run an iteration where the
|
|
* input track of the track this TrackEncoder is associated with didn't have
|
|
* any data.
|
|
*
|
|
* Since we sometimes use a direct listener for AudioSegments we miss periods
|
|
* of time for which the source didn't have any data. This ensures that the
|
|
* latest frame gets displayed while we wait for more data to be pushed.
|
|
*/
|
|
void AdvanceBlockedInput(StreamTime aDuration) override;
|
|
|
|
/**
|
|
* Dispatched from MediaStreamGraph when it has run an iteration so we can
|
|
* hand more data to the encoder.
|
|
*/
|
|
void AdvanceCurrentTime(StreamTime aDuration) override;
|
|
protected:
|
|
/**
|
|
* Number of samples per channel in a pcm buffer. This is also the value of
|
|
* frame size required by audio encoder, and listeners will be notified when
|
|
* at least this much data has been added to mOutgoingBuffer.
|
|
*/
|
|
virtual int GetPacketDuration() { return 0; }
|
|
|
|
/**
|
|
* Initializes the audio encoder. The call of this method is delayed until we
|
|
* have received the first valid track from MediaStreamGraph.
|
|
*/
|
|
virtual nsresult Init(int aChannels, int aSamplingRate) = 0;
|
|
|
|
/**
|
|
* The number of channels are used for processing PCM data in the audio encoder.
|
|
* This value comes from the first valid audio chunk. If encoder can't support
|
|
* the channels in the chunk, downmix PCM stream can be performed.
|
|
* This value also be used to initialize the audio encoder.
|
|
*/
|
|
int mChannels;
|
|
|
|
/**
|
|
* The sampling rate of source audio data.
|
|
*/
|
|
int mSamplingRate;
|
|
|
|
/**
|
|
* A segment queue of incoming audio track data, from listeners.
|
|
* The duration of mIncomingBuffer is strictly increasing as it gets fed more
|
|
* data. Consumed data is replaced by null data.
|
|
*/
|
|
AudioSegment mIncomingBuffer;
|
|
|
|
/**
|
|
* A segment queue of outgoing audio track data to the encoder.
|
|
* The contents of mOutgoingBuffer will always be what has been consumed from
|
|
* mIncomingBuffer (up to mCurrentTime) but not yet consumed by the encoder
|
|
* sub class.
|
|
*/
|
|
AudioSegment mOutgoingBuffer;
|
|
|
|
uint32_t mAudioBitrate;
|
|
|
|
// This may only be accessed on the MSG thread.
|
|
// I.e., in the regular NotifyQueuedChanges for audio to avoid adding data
|
|
// from that callback when the direct one is active.
|
|
bool mDirectConnected;
|
|
};
|
|
|
|
enum class FrameDroppingMode {
|
|
ALLOW, // Allowed to drop frames to keep up under load
|
|
DISALLOW, // Must not drop any frames, even if it means we will OOM
|
|
};
|
|
|
|
class VideoTrackEncoder : public TrackEncoder
|
|
{
|
|
public:
|
|
VideoTrackEncoder(TrackRate aTrackRate, FrameDroppingMode aFrameDroppingMode)
|
|
: TrackEncoder(aTrackRate)
|
|
, mFrameWidth(0)
|
|
, mFrameHeight(0)
|
|
, mDisplayWidth(0)
|
|
, mDisplayHeight(0)
|
|
, mEncodedTicks(0)
|
|
, mVideoBitrate(0)
|
|
, mFrameDroppingMode(aFrameDroppingMode)
|
|
{
|
|
mLastChunk.mDuration = 0;
|
|
}
|
|
|
|
/**
|
|
* Suspends encoding from aTime, i.e., all video frame with a timestamp
|
|
* between aTime and the timestamp of the next Resume() will be dropped.
|
|
*/
|
|
void Suspend(TimeStamp aTime) override;
|
|
|
|
/**
|
|
* Resumes encoding starting at aTime.
|
|
*/
|
|
void Resume(TimeStamp aTime) override;
|
|
|
|
/**
|
|
* Appends source video frames to mIncomingBuffer. We only append the source
|
|
* chunk if the image is different from mLastChunk's image. Called on the
|
|
* MediaStreamGraph thread.
|
|
*/
|
|
void AppendVideoSegment(VideoSegment&& aSegment);
|
|
|
|
/**
|
|
* Takes track data from the last time TakeTrackData ran until mCurrentTime
|
|
* and moves it to aSegment.
|
|
*/
|
|
void TakeTrackData(VideoSegment& aSegment);
|
|
|
|
/**
|
|
* Measure size of internal buffers.
|
|
*/
|
|
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) override;
|
|
|
|
void SetBitrate(const uint32_t aBitrate) override
|
|
{
|
|
mVideoBitrate = aBitrate;
|
|
}
|
|
|
|
/**
|
|
* Tries to initiate the VideoEncoder based on data in aSegment.
|
|
* This can be re-called often, as it will exit early should we already be
|
|
* initiated. mInitiated will only be set if there was enough data in
|
|
* aSegment to infer metadata. If mInitiated gets set, listeners are notified.
|
|
*
|
|
* Failing to initiate the encoder for an accumulated aDuration of 30 seconds
|
|
* is seen as an error and will cancel the current encoding.
|
|
*/
|
|
void Init(const VideoSegment& aSegment, StreamTime aDuration);
|
|
|
|
StreamTime SecondsToMediaTime(double aS) const
|
|
{
|
|
NS_ASSERTION(0 <= aS && aS <= TRACK_TICKS_MAX/TRACK_RATE_MAX,
|
|
"Bad seconds");
|
|
return mTrackRate * aS;
|
|
}
|
|
|
|
void Cancel() override;
|
|
|
|
/**
|
|
* Notifies us that we have reached the end of the stream and no more data
|
|
* will be appended to mIncomingBuffer.
|
|
*/
|
|
void NotifyEndOfStream() override;
|
|
|
|
void SetStartOffset(StreamTime aStartOffset) override;
|
|
|
|
/**
|
|
* Dispatched from MediaStreamGraph when it has run an iteration where the
|
|
* input track of the track this TrackEncoder is associated with didn't have
|
|
* any data.
|
|
*
|
|
* Since we use a direct listener for VideoSegments we miss periods of time
|
|
* for which the source didn't have any data. This ensures that the latest
|
|
* frame gets displayed while we wait for more data to be pushed.
|
|
*/
|
|
void AdvanceBlockedInput(StreamTime aDuration) override;
|
|
|
|
/**
|
|
* Dispatched from MediaStreamGraph when it has run an iteration so we can
|
|
* hand more data to the encoder.
|
|
*/
|
|
void AdvanceCurrentTime(StreamTime aDuration) override;
|
|
|
|
protected:
|
|
/**
|
|
* Initialize the video encoder. In order to collect the value of width and
|
|
* height of source frames, this initialization is delayed until we have
|
|
* received the first valid video frame from MediaStreamGraph.
|
|
* Listeners will be notified after it has been successfully initialized.
|
|
*/
|
|
virtual nsresult Init(int aWidth, int aHeight, int aDisplayWidth,
|
|
int aDisplayHeight) = 0;
|
|
|
|
/**
|
|
* The width of source video frame, ceiled if the source width is odd.
|
|
*/
|
|
int mFrameWidth;
|
|
|
|
/**
|
|
* The height of source video frame, ceiled if the source height is odd.
|
|
*/
|
|
int mFrameHeight;
|
|
|
|
/**
|
|
* The display width of source video frame.
|
|
*/
|
|
int mDisplayWidth;
|
|
|
|
/**
|
|
* The display height of source video frame.
|
|
*/
|
|
int mDisplayHeight;
|
|
|
|
/**
|
|
* The last unique frame and duration so far handled by NotifyAdvanceCurrentTime.
|
|
* When a new frame is detected, mLastChunk is added to mOutgoingBuffer.
|
|
*/
|
|
VideoChunk mLastChunk;
|
|
|
|
/**
|
|
* A segment queue of incoming video track data, from listeners.
|
|
* The duration of mIncomingBuffer is strictly increasing as it gets fed more
|
|
* data. Consumed data is replaced by null data.
|
|
*/
|
|
VideoSegment mIncomingBuffer;
|
|
|
|
/**
|
|
* A segment queue of outgoing video track data to the encoder.
|
|
* The contents of mOutgoingBuffer will always be what has been consumed from
|
|
* mIncomingBuffer (up to mCurrentTime) but not yet consumed by the encoder
|
|
* sub class. There won't be any null data at the beginning of mOutgoingBuffer
|
|
* unless explicitly pushed by the producer.
|
|
*/
|
|
VideoSegment mOutgoingBuffer;
|
|
|
|
/**
|
|
* The number of mTrackRate ticks we have passed to mOutgoingBuffer.
|
|
*/
|
|
StreamTime mEncodedTicks;
|
|
|
|
/**
|
|
* The time of the first real video frame passed to mOutgoingBuffer (at t=0).
|
|
*
|
|
* Note that this time will progress during suspension, to make sure the
|
|
* incoming frames stay in sync with the output.
|
|
*/
|
|
TimeStamp mStartTime;
|
|
|
|
/**
|
|
* The time Suspend was called on the MediaRecorder, so we can calculate the
|
|
* duration on the next Resume().
|
|
*/
|
|
TimeStamp mSuspendTime;
|
|
|
|
uint32_t mVideoBitrate;
|
|
|
|
/**
|
|
* ALLOW to drop frames under load.
|
|
* DISALLOW to encode all frames, mainly for testing.
|
|
*/
|
|
FrameDroppingMode mFrameDroppingMode;
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif
|