gecko-dev/dom/media/encoder/TrackEncoder.h

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