michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #if !defined(AudioStream_h_) michael@0: #define AudioStream_h_ michael@0: michael@0: #include "AudioSampleFormat.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsAutoRef.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "Latency.h" michael@0: #include "mozilla/dom/AudioChannelBinding.h" michael@0: #include "mozilla/StaticMutex.h" michael@0: #include "mozilla/RefPtr.h" michael@0: michael@0: #include "cubeb/cubeb.h" michael@0: michael@0: template <> michael@0: class nsAutoRefTraits : public nsPointerRefTraits michael@0: { michael@0: public: michael@0: static void Release(cubeb_stream* aStream) { cubeb_stream_destroy(aStream); } michael@0: }; michael@0: michael@0: namespace soundtouch { michael@0: class SoundTouch; michael@0: } michael@0: michael@0: namespace mozilla { michael@0: michael@0: class AudioStream; michael@0: michael@0: class AudioClock michael@0: { michael@0: public: michael@0: AudioClock(AudioStream* aStream); michael@0: // Initialize the clock with the current AudioStream. Need to be called michael@0: // before querying the clock. Called on the audio thread. michael@0: void Init(); michael@0: // Update the number of samples that has been written in the audio backend. michael@0: // Called on the state machine thread. michael@0: void UpdateWritePosition(uint32_t aCount); michael@0: // Get the read position of the stream, in microseconds. michael@0: // Called on the state machine thead. michael@0: // Assumes the AudioStream lock is held and thus calls Unlocked versions michael@0: // of AudioStream funcs. michael@0: uint64_t GetPositionUnlocked(); michael@0: // Get the read position of the stream, in frames. michael@0: // Called on the state machine thead. michael@0: uint64_t GetPositionInFrames(); michael@0: // Set the playback rate. michael@0: // Called on the audio thread. michael@0: // Assumes the AudioStream lock is held and thus calls Unlocked versions michael@0: // of AudioStream funcs. michael@0: void SetPlaybackRateUnlocked(double aPlaybackRate); michael@0: // Get the current playback rate. michael@0: // Called on the audio thread. michael@0: double GetPlaybackRate(); michael@0: // Set if we are preserving the pitch. michael@0: // Called on the audio thread. michael@0: void SetPreservesPitch(bool aPreservesPitch); michael@0: // Get the current pitch preservation state. michael@0: // Called on the audio thread. michael@0: bool GetPreservesPitch(); michael@0: // Get the number of frames written to the backend. michael@0: int64_t GetWritten(); michael@0: private: michael@0: // This AudioStream holds a strong reference to this AudioClock. This michael@0: // pointer is garanteed to always be valid. michael@0: AudioStream* mAudioStream; michael@0: // The old output rate, to compensate audio latency for the period inbetween michael@0: // the moment resampled buffers are pushed to the hardware and the moment the michael@0: // clock should take the new rate into account for A/V sync. michael@0: int mOldOutRate; michael@0: // Position at which the last playback rate change occured michael@0: int64_t mBasePosition; michael@0: // Offset, in frames, at which the last playback rate change occured michael@0: int64_t mBaseOffset; michael@0: // Old base offset (number of samples), used when changing rate to compute the michael@0: // position in the stream. michael@0: int64_t mOldBaseOffset; michael@0: // Old base position (number of microseconds), when changing rate. This is the michael@0: // time in the media, not wall clock position. michael@0: int64_t mOldBasePosition; michael@0: // Write position at which the playbackRate change occured. michael@0: int64_t mPlaybackRateChangeOffset; michael@0: // The previous position reached in the media, used when compensating michael@0: // latency, to have the position at which the playbackRate change occured. michael@0: int64_t mPreviousPosition; michael@0: // Number of samples effectivelly written in backend, i.e. write position. michael@0: int64_t mWritten; michael@0: // Output rate in Hz (characteristic of the playback rate) michael@0: int mOutRate; michael@0: // Input rate in Hz (characteristic of the media being played) michael@0: int mInRate; michael@0: // True if the we are timestretching, false if we are resampling. michael@0: bool mPreservesPitch; michael@0: // True if we are playing at the old playbackRate after it has been changed. michael@0: bool mCompensatingLatency; michael@0: }; michael@0: michael@0: class CircularByteBuffer michael@0: { michael@0: public: michael@0: CircularByteBuffer() michael@0: : mBuffer(nullptr), mCapacity(0), mStart(0), mCount(0) michael@0: {} michael@0: michael@0: // Set the capacity of the buffer in bytes. Must be called before any michael@0: // call to append or pop elements. michael@0: void SetCapacity(uint32_t aCapacity) { michael@0: NS_ABORT_IF_FALSE(!mBuffer, "Buffer allocated."); michael@0: mCapacity = aCapacity; michael@0: mBuffer = new uint8_t[mCapacity]; michael@0: } michael@0: michael@0: uint32_t Length() { michael@0: return mCount; michael@0: } michael@0: michael@0: uint32_t Capacity() { michael@0: return mCapacity; michael@0: } michael@0: michael@0: uint32_t Available() { michael@0: return Capacity() - Length(); michael@0: } michael@0: michael@0: // Append aLength bytes from aSrc to the buffer. Caller must check that michael@0: // sufficient space is available. michael@0: void AppendElements(const uint8_t* aSrc, uint32_t aLength) { michael@0: NS_ABORT_IF_FALSE(mBuffer && mCapacity, "Buffer not initialized."); michael@0: NS_ABORT_IF_FALSE(aLength <= Available(), "Buffer full."); michael@0: michael@0: uint32_t end = (mStart + mCount) % mCapacity; michael@0: michael@0: uint32_t toCopy = std::min(mCapacity - end, aLength); michael@0: memcpy(&mBuffer[end], aSrc, toCopy); michael@0: memcpy(&mBuffer[0], aSrc + toCopy, aLength - toCopy); michael@0: mCount += aLength; michael@0: } michael@0: michael@0: // Remove aSize bytes from the buffer. Caller must check returned size in michael@0: // aSize{1,2} before using the pointer returned in aData{1,2}. Caller michael@0: // must not specify an aSize larger than Length(). michael@0: void PopElements(uint32_t aSize, void** aData1, uint32_t* aSize1, michael@0: void** aData2, uint32_t* aSize2) { michael@0: NS_ABORT_IF_FALSE(mBuffer && mCapacity, "Buffer not initialized."); michael@0: NS_ABORT_IF_FALSE(aSize <= Length(), "Request too large."); michael@0: michael@0: *aData1 = &mBuffer[mStart]; michael@0: *aSize1 = std::min(mCapacity - mStart, aSize); michael@0: *aData2 = &mBuffer[0]; michael@0: *aSize2 = aSize - *aSize1; michael@0: mCount -= *aSize1 + *aSize2; michael@0: mStart += *aSize1 + *aSize2; michael@0: mStart %= mCapacity; michael@0: } michael@0: michael@0: // Throw away all but aSize bytes from the buffer. Returns new size, which michael@0: // may be less than aSize michael@0: uint32_t ContractTo(uint32_t aSize) { michael@0: NS_ABORT_IF_FALSE(mBuffer && mCapacity, "Buffer not initialized."); michael@0: if (aSize >= mCount) { michael@0: return mCount; michael@0: } michael@0: mStart += (mCount - aSize); michael@0: mCount = aSize; michael@0: mStart %= mCapacity; michael@0: return mCount; michael@0: } michael@0: michael@0: size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = 0; michael@0: amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf); michael@0: return amount; michael@0: } michael@0: michael@0: private: michael@0: nsAutoArrayPtr mBuffer; michael@0: uint32_t mCapacity; michael@0: uint32_t mStart; michael@0: uint32_t mCount; michael@0: }; michael@0: michael@0: class AudioInitTask; michael@0: michael@0: // Access to a single instance of this class must be synchronized by michael@0: // callers, or made from a single thread. One exception is that access to michael@0: // GetPosition, GetPositionInFrames, SetVolume, and Get{Rate,Channels} michael@0: // is thread-safe without external synchronization. michael@0: class AudioStream MOZ_FINAL michael@0: { michael@0: public: michael@0: // Initialize Audio Library. Some Audio backends require initializing the michael@0: // library before using it. michael@0: static void InitLibrary(); michael@0: michael@0: // Shutdown Audio Library. Some Audio backends require shutting down the michael@0: // library after using it. michael@0: static void ShutdownLibrary(); michael@0: michael@0: // Returns the maximum number of channels supported by the audio hardware. michael@0: static int MaxNumberOfChannels(); michael@0: michael@0: // Queries the samplerate the hardware/mixer runs at, and stores it. michael@0: // Can be called on any thread. When this returns, it is safe to call michael@0: // PreferredSampleRate without locking. michael@0: static void InitPreferredSampleRate(); michael@0: // Get the aformentionned sample rate. Does not lock. michael@0: static int PreferredSampleRate(); michael@0: michael@0: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioStream) michael@0: AudioStream(); michael@0: virtual ~AudioStream(); michael@0: michael@0: enum LatencyRequest { michael@0: HighLatency, michael@0: LowLatency michael@0: }; michael@0: michael@0: // Initialize the audio stream. aNumChannels is the number of audio michael@0: // channels (1 for mono, 2 for stereo, etc) and aRate is the sample rate michael@0: // (22050Hz, 44100Hz, etc). michael@0: nsresult Init(int32_t aNumChannels, int32_t aRate, michael@0: const dom::AudioChannel aAudioStreamChannel, michael@0: LatencyRequest aLatencyRequest); michael@0: michael@0: // Closes the stream. All future use of the stream is an error. michael@0: void Shutdown(); michael@0: michael@0: // Write audio data to the audio hardware. aBuf is an array of AudioDataValues michael@0: // AudioDataValue of length aFrames*mChannels. If aFrames is larger michael@0: // than the result of Available(), the write will block until sufficient michael@0: // buffer space is available. aTime is the time in ms associated with the first sample michael@0: // for latency calculations michael@0: nsresult Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp* aTime = nullptr); michael@0: michael@0: // Return the number of audio frames that can be written without blocking. michael@0: uint32_t Available(); michael@0: michael@0: // Set the current volume of the audio playback. This is a value from michael@0: // 0 (meaning muted) to 1 (meaning full volume). Thread-safe. michael@0: void SetVolume(double aVolume); michael@0: michael@0: // Block until buffered audio data has been consumed. michael@0: void Drain(); michael@0: michael@0: // Start the stream. michael@0: void Start(); michael@0: michael@0: // Return the number of frames written so far in the stream. This allow the michael@0: // caller to check if it is safe to start the stream, if needed. michael@0: int64_t GetWritten(); michael@0: michael@0: // Pause audio playback. michael@0: void Pause(); michael@0: michael@0: // Resume audio playback. michael@0: void Resume(); michael@0: michael@0: // Return the position in microseconds of the audio frame being played by michael@0: // the audio hardware, compensated for playback rate change. Thread-safe. michael@0: int64_t GetPosition(); michael@0: michael@0: // Return the position, measured in audio frames played since the stream michael@0: // was opened, of the audio hardware. Thread-safe. michael@0: int64_t GetPositionInFrames(); michael@0: michael@0: // Return the position, measured in audio framed played since the stream was michael@0: // opened, of the audio hardware, not adjusted for the changes of playback michael@0: // rate. michael@0: int64_t GetPositionInFramesInternal(); michael@0: michael@0: // Returns true when the audio stream is paused. michael@0: bool IsPaused(); michael@0: michael@0: int GetRate() { return mOutRate; } michael@0: int GetChannels() { return mChannels; } michael@0: int GetOutChannels() { return mOutChannels; } michael@0: michael@0: // Set playback rate as a multiple of the intrinsic playback rate. This is to michael@0: // be called only with aPlaybackRate > 0.0. michael@0: nsresult SetPlaybackRate(double aPlaybackRate); michael@0: // Switch between resampling (if false) and time stretching (if true, default). michael@0: nsresult SetPreservesPitch(bool aPreservesPitch); michael@0: michael@0: size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; michael@0: michael@0: protected: michael@0: friend class AudioClock; michael@0: michael@0: // Shared implementation of underflow adjusted position calculation. michael@0: // Caller must own the monitor. michael@0: int64_t GetPositionInFramesUnlocked(); michael@0: michael@0: private: michael@0: friend class AudioInitTask; michael@0: michael@0: // So we can call it asynchronously from AudioInitTask michael@0: nsresult OpenCubeb(cubeb_stream_params &aParams, michael@0: LatencyRequest aLatencyRequest); michael@0: michael@0: void CheckForStart(); michael@0: michael@0: static void PrefChanged(const char* aPref, void* aClosure); michael@0: static double GetVolumeScale(); michael@0: static cubeb* GetCubebContext(); michael@0: static cubeb* GetCubebContextUnlocked(); michael@0: static uint32_t GetCubebLatency(); michael@0: static bool CubebLatencyPrefSet(); michael@0: michael@0: static long DataCallback_S(cubeb_stream*, void* aThis, void* aBuffer, long aFrames) michael@0: { michael@0: return static_cast(aThis)->DataCallback(aBuffer, aFrames); michael@0: } michael@0: michael@0: static void StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState) michael@0: { michael@0: static_cast(aThis)->StateCallback(aState); michael@0: } michael@0: michael@0: long DataCallback(void* aBuffer, long aFrames); michael@0: void StateCallback(cubeb_state aState); michael@0: michael@0: nsresult EnsureTimeStretcherInitializedUnlocked(); michael@0: michael@0: // aTime is the time in ms the samples were inserted into MediaStreamGraph michael@0: long GetUnprocessed(void* aBuffer, long aFrames, int64_t &aTime); michael@0: long GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTime); michael@0: long GetUnprocessedWithSilencePadding(void* aBuffer, long aFrames, int64_t &aTime); michael@0: michael@0: int64_t GetLatencyInFrames(); michael@0: void GetBufferInsertTime(int64_t &aTimeMs); michael@0: michael@0: void StartUnlocked(); michael@0: michael@0: // The monitor is held to protect all access to member variables. Write() michael@0: // waits while mBuffer is full; DataCallback() notifies as it consumes michael@0: // data from mBuffer. Drain() waits while mState is DRAINING; michael@0: // StateCallback() notifies when mState is DRAINED. michael@0: Monitor mMonitor; michael@0: michael@0: // Input rate in Hz (characteristic of the media being played) michael@0: int mInRate; michael@0: // Output rate in Hz (characteristic of the playback rate) michael@0: int mOutRate; michael@0: int mChannels; michael@0: int mOutChannels; michael@0: // Number of frames written to the buffers. michael@0: int64_t mWritten; michael@0: AudioClock mAudioClock; michael@0: nsAutoPtr mTimeStretcher; michael@0: nsRefPtr mLatencyLog; michael@0: michael@0: // copy of Latency logger's starting time for offset calculations michael@0: TimeStamp mStartTime; michael@0: // Whether we are playing a low latency stream, or a normal stream. michael@0: LatencyRequest mLatencyRequest; michael@0: // Where in the current mInserts[0] block cubeb has read to michael@0: int64_t mReadPoint; michael@0: // Keep track of each inserted block of samples and the time it was inserted michael@0: // so we can estimate the clock time for a specific sample's insertion (for when michael@0: // we send data to cubeb). Blocks are aged out as needed. michael@0: struct Inserts { michael@0: int64_t mTimeMs; michael@0: int64_t mFrames; michael@0: }; michael@0: nsAutoTArray mInserts; michael@0: michael@0: // Sum of silent frames written when DataCallback requests more frames michael@0: // than are available in mBuffer. michael@0: uint64_t mLostFrames; michael@0: michael@0: // Output file for dumping audio michael@0: FILE* mDumpFile; michael@0: michael@0: // Temporary audio buffer. Filled by Write() and consumed by michael@0: // DataCallback(). Once mBuffer is full, Write() blocks until sufficient michael@0: // space becomes available in mBuffer. mBuffer is sized in bytes, not michael@0: // frames. michael@0: CircularByteBuffer mBuffer; michael@0: michael@0: // Software volume level. Applied during the servicing of DataCallback(). michael@0: double mVolume; michael@0: michael@0: // Owning reference to a cubeb_stream. cubeb_stream_destroy is called by michael@0: // nsAutoRef's destructor. michael@0: nsAutoRef mCubebStream; michael@0: michael@0: uint32_t mBytesPerFrame; michael@0: michael@0: uint32_t BytesToFrames(uint32_t aBytes) { michael@0: NS_ASSERTION(aBytes % mBytesPerFrame == 0, michael@0: "Byte count not aligned on frames size."); michael@0: return aBytes / mBytesPerFrame; michael@0: } michael@0: michael@0: uint32_t FramesToBytes(uint32_t aFrames) { michael@0: return aFrames * mBytesPerFrame; michael@0: } michael@0: michael@0: enum StreamState { michael@0: INITIALIZED, // Initialized, playback has not begun. michael@0: STARTED, // cubeb started, but callbacks haven't started michael@0: RUNNING, // DataCallbacks have started after STARTED, or after Resume(). michael@0: STOPPED, // Stopped by a call to Pause(). michael@0: DRAINING, // Drain requested. DataCallback will indicate end of stream michael@0: // once the remaining contents of mBuffer are requested by michael@0: // cubeb, after which StateCallback will indicate drain michael@0: // completion. michael@0: DRAINED, // StateCallback has indicated that the drain is complete. michael@0: ERRORED, // Stream disabled due to an internal error. michael@0: SHUTDOWN // Shutdown has been called michael@0: }; michael@0: michael@0: StreamState mState; michael@0: bool mNeedsStart; // needed in case Start() is called before cubeb is open michael@0: michael@0: // This mutex protects the static members below. michael@0: static StaticMutex sMutex; michael@0: static cubeb* sCubebContext; michael@0: michael@0: // Prefered samplerate, in Hz (characteristic of the michael@0: // hardware/mixer/platform/API used). michael@0: static uint32_t sPreferredSampleRate; michael@0: michael@0: static double sVolumeScale; michael@0: static uint32_t sCubebLatency; michael@0: static bool sCubebLatencyPrefSet; michael@0: }; michael@0: michael@0: class AudioInitTask : public nsRunnable michael@0: { michael@0: public: michael@0: AudioInitTask(AudioStream *aStream, michael@0: AudioStream::LatencyRequest aLatencyRequest, michael@0: const cubeb_stream_params &aParams) michael@0: : mAudioStream(aStream) michael@0: , mLatencyRequest(aLatencyRequest) michael@0: , mParams(aParams) michael@0: {} michael@0: michael@0: nsresult Dispatch() michael@0: { michael@0: // Can't add 'this' as the event to run, since mThread may not be set yet michael@0: nsresult rv = NS_NewNamedThread("CubebInit", getter_AddRefs(mThread)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // Note: event must not null out mThread! michael@0: rv = mThread->Dispatch(this, NS_DISPATCH_NORMAL); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: protected: michael@0: virtual ~AudioInitTask() {}; michael@0: michael@0: private: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE MOZ_FINAL; michael@0: michael@0: RefPtr mAudioStream; michael@0: AudioStream::LatencyRequest mLatencyRequest; michael@0: cubeb_stream_params mParams; michael@0: michael@0: nsCOMPtr mThread; michael@0: }; michael@0: michael@0: } // namespace mozilla michael@0: michael@0: #endif