Bug 942657 - Devirtualize AudioStream. r=doublec,gps

This commit is contained in:
Matthew Gregan 2013-11-28 18:09:08 +13:00
parent ded7b4c4c5
commit 93237d768e
13 changed files with 414 additions and 492 deletions

View File

@ -33,14 +33,12 @@ if CONFIG['MOZ_VP8'] and not CONFIG['MOZ_NATIVE_LIBVPX']:
if CONFIG['MOZ_OGG']: if CONFIG['MOZ_OGG']:
external_dirs += ['media/libogg', 'media/libtheora'] external_dirs += ['media/libogg', 'media/libtheora']
if CONFIG['MOZ_CUBEB']:
external_dirs += ['media/libcubeb']
if not CONFIG['MOZ_NATIVE_PNG']: if not CONFIG['MOZ_NATIVE_PNG']:
external_dirs += ['media/libpng'] external_dirs += ['media/libpng']
external_dirs += [ external_dirs += [
'media/kiss_fft', 'media/kiss_fft',
'media/libcubeb',
'media/libspeex_resampler', 'media/libspeex_resampler',
'media/libsoundtouch', 'media/libsoundtouch',
] ]

View File

@ -3938,7 +3938,6 @@ MOZ_JSDEBUGGER=1
MOZ_AUTH_EXTENSION=1 MOZ_AUTH_EXTENSION=1
MOZ_OGG=1 MOZ_OGG=1
MOZ_RAW= MOZ_RAW=
MOZ_CUBEB=
MOZ_VORBIS= MOZ_VORBIS=
MOZ_TREMOR= MOZ_TREMOR=
MOZ_WAVE=1 MOZ_WAVE=1
@ -5199,7 +5198,6 @@ MOZ_ARG_DISABLE_BOOL(ogg,
if test -n "$MOZ_OGG"; then if test -n "$MOZ_OGG"; then
AC_DEFINE(MOZ_OGG) AC_DEFINE(MOZ_OGG)
MOZ_CUBEB=1
dnl Checks for __attribute__(aligned()) directive dnl Checks for __attribute__(aligned()) directive
AC_CACHE_CHECK([__attribute__ ((aligned ())) support], AC_CACHE_CHECK([__attribute__ ((aligned ())) support],
@ -5259,7 +5257,6 @@ MOZ_ARG_DISABLE_BOOL(directshow,
if test -n "$MOZ_DIRECTSHOW"; then if test -n "$MOZ_DIRECTSHOW"; then
AC_DEFINE(MOZ_DIRECTSHOW) AC_DEFINE(MOZ_DIRECTSHOW)
MOZ_CUBEB=1
fi; fi;
dnl ======================================================== dnl ========================================================
@ -5279,7 +5276,6 @@ MOZ_ARG_DISABLE_BOOL(wmf,
if test -n "$MOZ_WMF"; then if test -n "$MOZ_WMF"; then
AC_DEFINE(MOZ_WMF) AC_DEFINE(MOZ_WMF)
MOZ_CUBEB=1
fi; fi;
dnl ======================================================== dnl ========================================================
@ -5410,7 +5406,6 @@ AC_SUBST(MOZ_LIBVPX_CFLAGS)
AC_SUBST(MOZ_LIBVPX_LIBS) AC_SUBST(MOZ_LIBVPX_LIBS)
if test "$MOZ_WEBM" -o "$MOZ_OGG"; then if test "$MOZ_WEBM" -o "$MOZ_OGG"; then
MOZ_CUBEB=1
if test "$MOZ_SAMPLE_TYPE_FLOAT32"; then if test "$MOZ_SAMPLE_TYPE_FLOAT32"; then
MOZ_VORBIS=1 MOZ_VORBIS=1
else else
@ -5512,17 +5507,12 @@ MOZ_ARG_DISABLE_BOOL(wave,
if test -n "$MOZ_WAVE"; then if test -n "$MOZ_WAVE"; then
AC_DEFINE(MOZ_WAVE) AC_DEFINE(MOZ_WAVE)
MOZ_CUBEB=1
fi fi
dnl ======================================================== dnl ========================================================
dnl = Handle dependent CUBEB and MEDIA defines dnl = Handle dependent MEDIA defines
dnl ======================================================== dnl ========================================================
if test -n "$MOZ_CUBEB"; then
AC_DEFINE(MOZ_CUBEB)
fi
if test -n "$MOZ_OPUS" -a -z "$MOZ_OGG"; then if test -n "$MOZ_OPUS" -a -z "$MOZ_OGG"; then
AC_MSG_ERROR([MOZ_OPUS requires MOZ_OGG which is disabled.]) AC_MSG_ERROR([MOZ_OPUS requires MOZ_OGG which is disabled.])
fi fi
@ -5552,12 +5542,12 @@ if test -n "$MOZ_OPUS"; then
AC_DEFINE(MOZ_OPUS) AC_DEFINE(MOZ_OPUS)
fi fi
dnl ==================================================== dnl ==================================
dnl = Check alsa availability on Linux if using libcubeb dnl = Check alsa availability on Linux
dnl ==================================================== dnl ==================================
dnl If using libcubeb with Linux, ensure that the alsa library is available dnl If using Linux, ensure that the alsa library is available
if test -n "$MOZ_CUBEB" -a "$OS_TARGET" = "Linux"; then if test "$OS_TARGET" = "Linux"; then
MOZ_ALSA=1 MOZ_ALSA=1
fi fi
@ -5567,7 +5557,6 @@ MOZ_ARG_ENABLE_BOOL(alsa,
MOZ_ALSA=) MOZ_ALSA=)
if test -n "$MOZ_ALSA"; then if test -n "$MOZ_ALSA"; then
AC_DEFINE(MOZ_CUBEB)
PKG_CHECK_MODULES(MOZ_ALSA, alsa, , PKG_CHECK_MODULES(MOZ_ALSA, alsa, ,
[echo "$MOZ_ALSA_PKG_ERRORS" [echo "$MOZ_ALSA_PKG_ERRORS"
AC_MSG_ERROR([Need alsa for Ogg, Wave or WebM decoding on Linux. Disable with --disable-ogg --disable-wave --disable-webm. (On Ubuntu, you might try installing the package libasound2-dev.)])]) AC_MSG_ERROR([Need alsa for Ogg, Wave or WebM decoding on Linux. Disable with --disable-ogg --disable-wave --disable-webm. (On Ubuntu, you might try installing the package libasound2-dev.)])])
@ -5581,8 +5570,8 @@ dnl ========================================================
dnl = Disable PulseAudio dnl = Disable PulseAudio
dnl ======================================================== dnl ========================================================
dnl If using libcubeb with Linux, ensure that the PA library is available dnl If using Linux, ensure that the PA library is available
if test -n "$MOZ_CUBEB" -a "$OS_TARGET" = "Linux" -a -z "$MOZ_B2G"; then if test "$OS_TARGET" = "Linux" -a -z "$MOZ_B2G"; then
MOZ_PULSEAUDIO=1 MOZ_PULSEAUDIO=1
fi fi
@ -5592,7 +5581,6 @@ MOZ_ARG_DISABLE_BOOL(pulseaudio,
MOZ_PULSEAUDIO=1) MOZ_PULSEAUDIO=1)
if test -n "$MOZ_PULSEAUDIO"; then if test -n "$MOZ_PULSEAUDIO"; then
AC_DEFINE(MOZ_CUBEB)
if test -z "$gonkdir"; then if test -z "$gonkdir"; then
PKG_CHECK_MODULES(MOZ_PULSEAUDIO, libpulse, , PKG_CHECK_MODULES(MOZ_PULSEAUDIO, libpulse, ,
[echo "$MOZ_PULSEAUDIO_PKG_ERRORS" [echo "$MOZ_PULSEAUDIO_PKG_ERRORS"
@ -8655,7 +8643,6 @@ AC_SUBST(MOZ_NSS_PATCH)
AC_SUBST(MOZ_APP_COMPONENT_LIBS) AC_SUBST(MOZ_APP_COMPONENT_LIBS)
AC_SUBST(MOZ_APP_EXTRA_LIBS) AC_SUBST(MOZ_APP_EXTRA_LIBS)
AC_SUBST(MOZ_CUBEB)
AC_SUBST(MOZ_WAVE) AC_SUBST(MOZ_WAVE)
AC_SUBST(MOZ_VORBIS) AC_SUBST(MOZ_VORBIS)
AC_SUBST(MOZ_TREMOR) AC_SUBST(MOZ_TREMOR)

View File

@ -111,7 +111,7 @@ HTMLAudioElement::MozSetup(uint32_t aChannels, uint32_t aRate, ErrorResult& aRv)
} }
#endif #endif
mAudioStream = AudioStream::AllocateStream(); mAudioStream = new AudioStream();
aRv = mAudioStream->Init(aChannels, aRate, mAudioChannelType, AudioStream::HighLatency); aRv = mAudioStream->Init(aChannels, aRate, mAudioChannelType, AudioStream::HighLatency);
if (aRv.Failed()) { if (aRv.Failed()) {
mAudioStream->Shutdown(); mAudioStream->Shutdown();

View File

@ -16,104 +16,91 @@
#include "soundtouch/SoundTouch.h" #include "soundtouch/SoundTouch.h"
#include "Latency.h" #include "Latency.h"
#if defined(MOZ_CUBEB)
#include "nsAutoRef.h"
#include "cubeb/cubeb.h"
template <>
class nsAutoRefTraits<cubeb_stream> : public nsPointerRefTraits<cubeb_stream>
{
public:
static void Release(cubeb_stream* aStream) { cubeb_stream_destroy(aStream); }
};
#endif
namespace mozilla { namespace mozilla {
#ifdef PR_LOGGING #ifdef PR_LOGGING
PRLogModuleInfo* gAudioStreamLog = nullptr; PRLogModuleInfo* gAudioStreamLog = nullptr;
#endif #endif
#define PREF_VOLUME_SCALE "media.volume_scale"
#define PREF_CUBEB_LATENCY "media.cubeb_latency_ms"
static Mutex* gAudioPrefsLock = nullptr;
static double gVolumeScale;
static uint32_t gCubebLatency;
static bool gCubebLatencyPrefSet;
static const uint32_t CUBEB_NORMAL_LATENCY_MS = 100;
StaticMutex AudioStream::mMutex;
uint32_t AudioStream::mPreferredSampleRate = 0;
/** /**
* When MOZ_DUMP_AUDIO is set in the environment (to anything), * When MOZ_DUMP_AUDIO is set in the environment (to anything),
* we'll drop a series of files in the current working directory named * we'll drop a series of files in the current working directory named
* dumped-audio-<nnn>.wav, one per nsBufferedAudioStream created, containing * dumped-audio-<nnn>.wav, one per AudioStream created, containing
* the audio for the stream including any skips due to underruns. * the audio for the stream including any skips due to underruns.
*/ */
#if defined(MOZ_CUBEB)
static int gDumpedAudioCount = 0; static int gDumpedAudioCount = 0;
#endif
static int PrefChanged(const char* aPref, void* aClosure) #define PREF_VOLUME_SCALE "media.volume_scale"
#define PREF_CUBEB_LATENCY "media.cubeb_latency_ms"
static const uint32_t CUBEB_NORMAL_LATENCY_MS = 100;
StaticMutex AudioStream::sMutex;
cubeb* AudioStream::sCubebContext;
uint32_t AudioStream::sPreferredSampleRate;
double AudioStream::sVolumeScale;
uint32_t AudioStream::sCubebLatency;
bool AudioStream::sCubebLatencyPrefSet;
/*static*/ int AudioStream::PrefChanged(const char* aPref, void* aClosure)
{ {
if (strcmp(aPref, PREF_VOLUME_SCALE) == 0) { if (strcmp(aPref, PREF_VOLUME_SCALE) == 0) {
nsAdoptingString value = Preferences::GetString(aPref); nsAdoptingString value = Preferences::GetString(aPref);
MutexAutoLock lock(*gAudioPrefsLock); StaticMutexAutoLock lock(sMutex);
if (value.IsEmpty()) { if (value.IsEmpty()) {
gVolumeScale = 1.0; sVolumeScale = 1.0;
} else { } else {
NS_ConvertUTF16toUTF8 utf8(value); NS_ConvertUTF16toUTF8 utf8(value);
gVolumeScale = std::max<double>(0, PR_strtod(utf8.get(), nullptr)); sVolumeScale = std::max<double>(0, PR_strtod(utf8.get(), nullptr));
} }
} else if (strcmp(aPref, PREF_CUBEB_LATENCY) == 0) { } else if (strcmp(aPref, PREF_CUBEB_LATENCY) == 0) {
// Arbitrary default stream latency of 100ms. The higher this // Arbitrary default stream latency of 100ms. The higher this
// value, the longer stream volume changes will take to become // value, the longer stream volume changes will take to become
// audible. // audible.
gCubebLatencyPrefSet = Preferences::HasUserValue(aPref); sCubebLatencyPrefSet = Preferences::HasUserValue(aPref);
uint32_t value = Preferences::GetUint(aPref, CUBEB_NORMAL_LATENCY_MS); uint32_t value = Preferences::GetUint(aPref, CUBEB_NORMAL_LATENCY_MS);
MutexAutoLock lock(*gAudioPrefsLock); StaticMutexAutoLock lock(sMutex);
gCubebLatency = std::min<uint32_t>(std::max<uint32_t>(value, 1), 1000); sCubebLatency = std::min<uint32_t>(std::max<uint32_t>(value, 1), 1000);
} }
return 0; return 0;
} }
#if defined(MOZ_CUBEB) /*static*/ double AudioStream::GetVolumeScale()
static double GetVolumeScale()
{ {
MutexAutoLock lock(*gAudioPrefsLock); StaticMutexAutoLock lock(sMutex);
return gVolumeScale; return sVolumeScale;
} }
static cubeb* gCubebContext; /*static*/ cubeb* AudioStream::GetCubebContext()
static cubeb* GetCubebContext()
{ {
MutexAutoLock lock(*gAudioPrefsLock); StaticMutexAutoLock lock(sMutex);
if (gCubebContext || return GetCubebContextUnlocked();
cubeb_init(&gCubebContext, "AudioStream") == CUBEB_OK) { }
return gCubebContext;
/*static*/ cubeb* AudioStream::GetCubebContextUnlocked()
{
sMutex.AssertCurrentThreadOwns();
if (sCubebContext ||
cubeb_init(&sCubebContext, "AudioStream") == CUBEB_OK) {
return sCubebContext;
} }
NS_WARNING("cubeb_init failed"); NS_WARNING("cubeb_init failed");
return nullptr; return nullptr;
} }
static uint32_t GetCubebLatency() /*static*/ uint32_t AudioStream::GetCubebLatency()
{ {
MutexAutoLock lock(*gAudioPrefsLock); StaticMutexAutoLock lock(sMutex);
return gCubebLatency; return sCubebLatency;
} }
static bool CubebLatencyPrefSet() /*static*/ bool AudioStream::CubebLatencyPrefSet()
{ {
MutexAutoLock lock(*gAudioPrefsLock); StaticMutexAutoLock lock(sMutex);
return gCubebLatencyPrefSet; return sCubebLatencyPrefSet;
} }
#endif
#if defined(MOZ_CUBEB) && defined(__ANDROID__) && defined(MOZ_B2G) #if defined(__ANDROID__) && defined(MOZ_B2G)
static cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannelType aType) static cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannelType aType)
{ {
switch(aType) { switch(aType) {
@ -139,52 +126,58 @@ static cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannelType aType)
#endif #endif
AudioStream::AudioStream() AudioStream::AudioStream()
: mInRate(0), : mMonitor("AudioStream")
mOutRate(0), , mInRate(0)
mChannels(0), , mOutRate(0)
mWritten(0), , mChannels(0)
mAudioClock(MOZ_THIS_IN_INITIALIZER_LIST()), , mWritten(0)
mLatencyRequest(HighLatency), , mAudioClock(MOZ_THIS_IN_INITIALIZER_LIST())
mReadPoint(0) , mLatencyRequest(HighLatency)
{} , mReadPoint(0)
, mLostFrames(0)
void AudioStream::InitLibrary() , mDumpFile(nullptr)
, mVolume(1.0)
, mBytesPerFrame(0)
, mState(INITIALIZED)
{ {
#ifdef PR_LOGGING // keep a ref in case we shut down later than nsLayoutStatics
gAudioStreamLog = PR_NewLogModule("AudioStream"); mLatencyLog = AsyncLatencyLogger::Get(true);
#endif
gAudioPrefsLock = new Mutex("AudioStream::gAudioPrefsLock");
PrefChanged(PREF_VOLUME_SCALE, nullptr);
Preferences::RegisterCallback(PrefChanged, PREF_VOLUME_SCALE);
#if defined(MOZ_CUBEB)
PrefChanged(PREF_CUBEB_LATENCY, nullptr);
Preferences::RegisterCallback(PrefChanged, PREF_CUBEB_LATENCY);
#endif
}
void AudioStream::ShutdownLibrary()
{
Preferences::UnregisterCallback(PrefChanged, PREF_VOLUME_SCALE);
#if defined(MOZ_CUBEB)
Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY);
#endif
delete gAudioPrefsLock;
gAudioPrefsLock = nullptr;
#if defined(MOZ_CUBEB)
if (gCubebContext) {
cubeb_destroy(gCubebContext);
gCubebContext = nullptr;
}
#endif
} }
AudioStream::~AudioStream() AudioStream::~AudioStream()
{ {
Shutdown();
if (mDumpFile) {
fclose(mDumpFile);
}
}
/*static*/ void AudioStream::InitLibrary()
{
#ifdef PR_LOGGING
gAudioStreamLog = PR_NewLogModule("AudioStream");
#endif
PrefChanged(PREF_VOLUME_SCALE, nullptr);
Preferences::RegisterCallback(PrefChanged, PREF_VOLUME_SCALE);
PrefChanged(PREF_CUBEB_LATENCY, nullptr);
Preferences::RegisterCallback(PrefChanged, PREF_CUBEB_LATENCY);
}
/*static*/ void AudioStream::ShutdownLibrary()
{
Preferences::UnregisterCallback(PrefChanged, PREF_VOLUME_SCALE);
Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY);
StaticMutexAutoLock lock(sMutex);
if (sCubebContext) {
cubeb_destroy(sCubebContext);
sCubebContext = nullptr;
}
} }
nsresult AudioStream::EnsureTimeStretcherInitialized() nsresult AudioStream::EnsureTimeStretcherInitialized()
{ {
MonitorAutoLock mon(mMonitor);
if (!mTimeStretcher) { if (!mTimeStretcher) {
// SoundTouch does not support a number of channels > 2 // SoundTouch does not support a number of channels > 2
if (mChannels > 2) { if (mChannels > 2) {
@ -254,224 +247,43 @@ int64_t AudioStream::GetWritten()
return mWritten; return mWritten;
} }
#if defined(MOZ_CUBEB) /*static*/ int AudioStream::MaxNumberOfChannels()
class nsCircularByteBuffer
{ {
public: cubeb* cubebContext = GetCubebContext();
nsCircularByteBuffer()
: mBuffer(nullptr), mCapacity(0), mStart(0), mCount(0)
{}
// Set the capacity of the buffer in bytes. Must be called before any
// call to append or pop elements.
void SetCapacity(uint32_t aCapacity) {
NS_ABORT_IF_FALSE(!mBuffer, "Buffer allocated.");
mCapacity = aCapacity;
mBuffer = new uint8_t[mCapacity];
}
uint32_t Length() {
return mCount;
}
uint32_t Capacity() {
return mCapacity;
}
uint32_t Available() {
return Capacity() - Length();
}
// Append aLength bytes from aSrc to the buffer. Caller must check that
// sufficient space is available.
void AppendElements(const uint8_t* aSrc, uint32_t aLength) {
NS_ABORT_IF_FALSE(mBuffer && mCapacity, "Buffer not initialized.");
NS_ABORT_IF_FALSE(aLength <= Available(), "Buffer full.");
uint32_t end = (mStart + mCount) % mCapacity;
uint32_t toCopy = std::min(mCapacity - end, aLength);
memcpy(&mBuffer[end], aSrc, toCopy);
memcpy(&mBuffer[0], aSrc + toCopy, aLength - toCopy);
mCount += aLength;
}
// Remove aSize bytes from the buffer. Caller must check returned size in
// aSize{1,2} before using the pointer returned in aData{1,2}. Caller
// must not specify an aSize larger than Length().
void PopElements(uint32_t aSize, void** aData1, uint32_t* aSize1,
void** aData2, uint32_t* aSize2) {
NS_ABORT_IF_FALSE(mBuffer && mCapacity, "Buffer not initialized.");
NS_ABORT_IF_FALSE(aSize <= Length(), "Request too large.");
*aData1 = &mBuffer[mStart];
*aSize1 = std::min(mCapacity - mStart, aSize);
*aData2 = &mBuffer[0];
*aSize2 = aSize - *aSize1;
mCount -= *aSize1 + *aSize2;
mStart += *aSize1 + *aSize2;
mStart %= mCapacity;
}
private:
nsAutoArrayPtr<uint8_t> mBuffer;
uint32_t mCapacity;
uint32_t mStart;
uint32_t mCount;
};
class BufferedAudioStream : public AudioStream
{
public:
BufferedAudioStream();
~BufferedAudioStream();
nsresult Init(int32_t aNumChannels, int32_t aRate,
const dom::AudioChannelType aAudioChannelType,
AudioStream::LatencyRequest aLatencyRequest);
void Shutdown();
nsresult Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime = nullptr);
uint32_t Available();
void SetVolume(double aVolume);
void Drain();
void Start();
void Pause();
void Resume();
int64_t GetPosition();
int64_t GetPositionInFrames();
int64_t GetPositionInFramesInternal();
int64_t GetLatencyInFrames();
bool IsPaused();
void GetBufferInsertTime(int64_t &aTimeMs);
// This method acquires the monitor and forward the call to the base
// class, to prevent a race on |mTimeStretcher|, in
// |AudioStream::EnsureTimeStretcherInitialized|.
nsresult EnsureTimeStretcherInitialized();
private:
static long DataCallback_S(cubeb_stream*, void* aThis, void* aBuffer, long aFrames)
{
return static_cast<BufferedAudioStream*>(aThis)->DataCallback(aBuffer, aFrames);
}
static void StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState)
{
static_cast<BufferedAudioStream*>(aThis)->StateCallback(aState);
}
long DataCallback(void* aBuffer, long aFrames);
void StateCallback(cubeb_state aState);
// aTime is the time in ms the samples were inserted into MediaStreamGraph
long GetUnprocessed(void* aBuffer, long aFrames, int64_t &aTime);
long GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTime);
long GetUnprocessedWithSilencePadding(void* aBuffer, long aFrames, int64_t &aTime);
// Shared implementation of underflow adjusted position calculation.
// Caller must own the monitor.
int64_t GetPositionInFramesUnlocked();
void StartUnlocked();
// The monitor is held to protect all access to member variables. Write()
// waits while mBuffer is full; DataCallback() notifies as it consumes
// data from mBuffer. Drain() waits while mState is DRAINING;
// StateCallback() notifies when mState is DRAINED.
Monitor mMonitor;
// Sum of silent frames written when DataCallback requests more frames
// than are available in mBuffer.
uint64_t mLostFrames;
// Output file for dumping audio
FILE* mDumpFile;
// Temporary audio buffer. Filled by Write() and consumed by
// DataCallback(). Once mBuffer is full, Write() blocks until sufficient
// space becomes available in mBuffer. mBuffer is sized in bytes, not
// frames.
nsCircularByteBuffer mBuffer;
// Software volume level. Applied during the servicing of DataCallback().
double mVolume;
// Owning reference to a cubeb_stream. cubeb_stream_destroy is called by
// nsAutoRef's destructor.
nsAutoRef<cubeb_stream> mCubebStream;
uint32_t mBytesPerFrame;
uint32_t BytesToFrames(uint32_t aBytes) {
NS_ASSERTION(aBytes % mBytesPerFrame == 0,
"Byte count not aligned on frames size.");
return aBytes / mBytesPerFrame;
}
uint32_t FramesToBytes(uint32_t aFrames) {
return aFrames * mBytesPerFrame;
}
enum StreamState {
INITIALIZED, // Initialized, playback has not begun.
STARTED, // Started by a call to Write() (iff INITIALIZED) or Resume().
STOPPED, // Stopped by a call to Pause().
DRAINING, // Drain requested. DataCallback will indicate end of stream
// once the remaining contents of mBuffer are requested by
// cubeb, after which StateCallback will indicate drain
// completion.
DRAINED, // StateCallback has indicated that the drain is complete.
ERRORED // Stream disabled due to an internal error.
};
StreamState mState;
};
#endif
AudioStream* AudioStream::AllocateStream()
{
#if defined(MOZ_CUBEB)
return new BufferedAudioStream();
#endif
return nullptr;
}
int AudioStream::MaxNumberOfChannels()
{
#if defined(MOZ_CUBEB)
uint32_t maxNumberOfChannels; uint32_t maxNumberOfChannels;
if (cubebContext &&
if (cubeb_get_max_channel_count(GetCubebContext(), cubeb_get_max_channel_count(cubebContext,
&maxNumberOfChannels) == CUBEB_OK) { &maxNumberOfChannels) == CUBEB_OK) {
return static_cast<int>(maxNumberOfChannels); return static_cast<int>(maxNumberOfChannels);
} }
#endif
return 0; return 0;
} }
int AudioStream::PreferredSampleRate() /*static*/ int AudioStream::PreferredSampleRate()
{ {
StaticMutexAutoLock lock(AudioStream::mMutex); const int fallbackSampleRate = 44100;
StaticMutexAutoLock lock(sMutex);
if (sPreferredSampleRate != 0) {
return sPreferredSampleRate;
}
cubeb* cubebContext = GetCubebContextUnlocked();
if (!cubebContext) {
sPreferredSampleRate = fallbackSampleRate;
}
// Get the preferred samplerate for this platform, or fallback to something // Get the preferred samplerate for this platform, or fallback to something
// sensible if we fail. We cache the value, because this might be accessed // sensible if we fail. We cache the value, because this might be accessed
// often, and the complexity of the function call below depends on the // often, and the complexity of the function call below depends on the
// backend used. // backend used.
const int fallbackSampleRate = 44100; if (cubeb_get_preferred_sample_rate(cubebContext,
if (mPreferredSampleRate == 0) { &sPreferredSampleRate) != CUBEB_OK) {
#if defined(MOZ_CUBEB) sPreferredSampleRate = fallbackSampleRate;
if (cubeb_get_preferred_sample_rate(GetCubebContext(),
&mPreferredSampleRate) == CUBEB_OK) {
return mPreferredSampleRate;
}
#endif
mPreferredSampleRate = fallbackSampleRate;
} }
return mPreferredSampleRate; return sPreferredSampleRate;
} }
#if defined(MOZ_CUBEB)
static void SetUint16LE(uint8_t* aDest, uint16_t aValue) static void SetUint16LE(uint8_t* aDest, uint16_t aValue)
{ {
aDest[0] = aValue & 0xFF; aDest[0] = aValue & 0xFF;
@ -541,33 +353,10 @@ WriteDumpFile(FILE* aDumpFile, AudioStream* aStream, uint32_t aFrames,
fflush(aDumpFile); fflush(aDumpFile);
} }
BufferedAudioStream::BufferedAudioStream()
: mMonitor("BufferedAudioStream"), mLostFrames(0), mDumpFile(nullptr),
mVolume(1.0), mBytesPerFrame(0), mState(INITIALIZED)
{
// keep a ref in case we shut down later than nsLayoutStatics
mLatencyLog = AsyncLatencyLogger::Get(true);
}
BufferedAudioStream::~BufferedAudioStream()
{
Shutdown();
if (mDumpFile) {
fclose(mDumpFile);
}
}
nsresult nsresult
BufferedAudioStream::EnsureTimeStretcherInitialized() AudioStream::Init(int32_t aNumChannels, int32_t aRate,
{ const dom::AudioChannelType aAudioChannelType,
MonitorAutoLock mon(mMonitor); LatencyRequest aLatencyRequest)
return AudioStream::EnsureTimeStretcherInitialized();
}
nsresult
BufferedAudioStream::Init(int32_t aNumChannels, int32_t aRate,
const dom::AudioChannelType aAudioChannelType,
AudioStream::LatencyRequest aLatencyRequest)
{ {
cubeb* cubebContext = GetCubebContext(); cubeb* cubebContext = GetCubebContext();
@ -610,7 +399,7 @@ BufferedAudioStream::Init(int32_t aNumChannels, int32_t aRate,
// for low latency playback, try to get the lowest latency possible. // for low latency playback, try to get the lowest latency possible.
// Otherwise, for normal streams, use 100ms. // Otherwise, for normal streams, use 100ms.
uint32_t latency; uint32_t latency;
if (aLatencyRequest == AudioStream::LowLatency && !CubebLatencyPrefSet()) { if (aLatencyRequest == LowLatency && !CubebLatencyPrefSet()) {
if (cubeb_get_min_latency(cubebContext, params, &latency) != CUBEB_OK) { if (cubeb_get_min_latency(cubebContext, params, &latency) != CUBEB_OK) {
latency = GetCubebLatency(); latency = GetCubebLatency();
} }
@ -620,7 +409,7 @@ BufferedAudioStream::Init(int32_t aNumChannels, int32_t aRate,
{ {
cubeb_stream* stream; cubeb_stream* stream;
if (cubeb_stream_init(cubebContext, &stream, "BufferedAudioStream", params, if (cubeb_stream_init(cubebContext, &stream, "AudioStream", params,
latency, DataCallback_S, StateCallback_S, this) == CUBEB_OK) { latency, DataCallback_S, StateCallback_S, this) == CUBEB_OK) {
mCubebStream.own(stream); mCubebStream.own(stream);
} }
@ -639,8 +428,8 @@ BufferedAudioStream::Init(int32_t aNumChannels, int32_t aRate,
// Start the stream right away when low latency has been requested. This means // Start the stream right away when low latency has been requested. This means
// that the DataCallback will feed silence to cubeb, until the first frames // that the DataCallback will feed silence to cubeb, until the first frames
// are writtent to this BufferedAudioStream. // are writtent to this AudioStream.
if (mLatencyRequest == AudioStream::LowLatency) { if (mLatencyRequest == LowLatency) {
Start(); Start();
} }
@ -648,7 +437,7 @@ BufferedAudioStream::Init(int32_t aNumChannels, int32_t aRate,
} }
void void
BufferedAudioStream::Shutdown() AudioStream::Shutdown()
{ {
if (mState == STARTED) { if (mState == STARTED) {
Pause(); Pause();
@ -660,7 +449,7 @@ BufferedAudioStream::Shutdown()
// aTime is the time in ms the samples were inserted into MediaStreamGraph // aTime is the time in ms the samples were inserted into MediaStreamGraph
nsresult nsresult
BufferedAudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime) AudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime)
{ {
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
if (!mCubebStream || mState == ERRORED) { if (!mCubebStream || mState == ERRORED) {
@ -715,7 +504,7 @@ BufferedAudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeSta
} }
uint32_t uint32_t
BufferedAudioStream::Available() AudioStream::Available()
{ {
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Buffer invariant violated."); NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Buffer invariant violated.");
@ -723,7 +512,7 @@ BufferedAudioStream::Available()
} }
void void
BufferedAudioStream::SetVolume(double aVolume) AudioStream::SetVolume(double aVolume)
{ {
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
NS_ABORT_IF_FALSE(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume"); NS_ABORT_IF_FALSE(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
@ -731,7 +520,7 @@ BufferedAudioStream::SetVolume(double aVolume)
} }
void void
BufferedAudioStream::Drain() AudioStream::Drain()
{ {
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
if (mState != STARTED) { if (mState != STARTED) {
@ -745,14 +534,14 @@ BufferedAudioStream::Drain()
} }
void void
BufferedAudioStream::Start() AudioStream::Start()
{ {
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
StartUnlocked(); StartUnlocked();
} }
void void
BufferedAudioStream::StartUnlocked() AudioStream::StartUnlocked()
{ {
mMonitor.AssertCurrentThreadOwns(); mMonitor.AssertCurrentThreadOwns();
if (!mCubebStream || mState != INITIALIZED) { if (!mCubebStream || mState != INITIALIZED) {
@ -771,7 +560,7 @@ BufferedAudioStream::StartUnlocked()
} }
void void
BufferedAudioStream::Pause() AudioStream::Pause()
{ {
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
if (!mCubebStream || mState != STARTED) { if (!mCubebStream || mState != STARTED) {
@ -789,7 +578,7 @@ BufferedAudioStream::Pause()
} }
void void
BufferedAudioStream::Resume() AudioStream::Resume()
{ {
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
if (!mCubebStream || mState != STOPPED) { if (!mCubebStream || mState != STOPPED) {
@ -807,7 +596,7 @@ BufferedAudioStream::Resume()
} }
int64_t int64_t
BufferedAudioStream::GetPosition() AudioStream::GetPosition()
{ {
return mAudioClock.GetPosition(); return mAudioClock.GetPosition();
} }
@ -817,7 +606,7 @@ BufferedAudioStream::GetPosition()
#pragma optimize("", off) #pragma optimize("", off)
#endif #endif
int64_t int64_t
BufferedAudioStream::GetPositionInFrames() AudioStream::GetPositionInFrames()
{ {
return mAudioClock.GetPositionInFrames(); return mAudioClock.GetPositionInFrames();
} }
@ -826,14 +615,14 @@ BufferedAudioStream::GetPositionInFrames()
#endif #endif
int64_t int64_t
BufferedAudioStream::GetPositionInFramesInternal() AudioStream::GetPositionInFramesInternal()
{ {
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
return GetPositionInFramesUnlocked(); return GetPositionInFramesUnlocked();
} }
int64_t int64_t
BufferedAudioStream::GetPositionInFramesUnlocked() AudioStream::GetPositionInFramesUnlocked()
{ {
mMonitor.AssertCurrentThreadOwns(); mMonitor.AssertCurrentThreadOwns();
@ -859,10 +648,10 @@ BufferedAudioStream::GetPositionInFramesUnlocked()
} }
int64_t int64_t
BufferedAudioStream::GetLatencyInFrames() AudioStream::GetLatencyInFrames()
{ {
uint32_t latency; uint32_t latency;
if(cubeb_stream_get_latency(mCubebStream, &latency)) { if (cubeb_stream_get_latency(mCubebStream, &latency)) {
NS_WARNING("Could not get cubeb latency."); NS_WARNING("Could not get cubeb latency.");
return 0; return 0;
} }
@ -870,14 +659,14 @@ BufferedAudioStream::GetLatencyInFrames()
} }
bool bool
BufferedAudioStream::IsPaused() AudioStream::IsPaused()
{ {
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
return mState == STOPPED; return mState == STOPPED;
} }
void void
BufferedAudioStream::GetBufferInsertTime(int64_t &aTimeMs) AudioStream::GetBufferInsertTime(int64_t &aTimeMs)
{ {
if (mInserts.Length() > 0) { if (mInserts.Length() > 0) {
// Find the right block, but don't leave the array empty // Find the right block, but don't leave the array empty
@ -894,7 +683,7 @@ BufferedAudioStream::GetBufferInsertTime(int64_t &aTimeMs)
} }
long long
BufferedAudioStream::GetUnprocessed(void* aBuffer, long aFrames, int64_t &aTimeMs) AudioStream::GetUnprocessed(void* aBuffer, long aFrames, int64_t &aTimeMs)
{ {
uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer); uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer);
@ -925,7 +714,7 @@ BufferedAudioStream::GetUnprocessed(void* aBuffer, long aFrames, int64_t &aTimeM
// Get unprocessed samples, and pad the beginning of the buffer with silence if // Get unprocessed samples, and pad the beginning of the buffer with silence if
// there is not enough data. // there is not enough data.
long long
BufferedAudioStream::GetUnprocessedWithSilencePadding(void* aBuffer, long aFrames, int64_t& aTimeMs) AudioStream::GetUnprocessedWithSilencePadding(void* aBuffer, long aFrames, int64_t& aTimeMs)
{ {
uint32_t toPopBytes = FramesToBytes(aFrames); uint32_t toPopBytes = FramesToBytes(aFrames);
uint32_t available = std::min(toPopBytes, mBuffer.Length()); uint32_t available = std::min(toPopBytes, mBuffer.Length());
@ -949,12 +738,12 @@ BufferedAudioStream::GetUnprocessedWithSilencePadding(void* aBuffer, long aFrame
} }
long long
BufferedAudioStream::GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTimeMs) AudioStream::GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTimeMs)
{ {
long processedFrames = 0; long processedFrames = 0;
// We need to call the non-locking version, because we already have the lock. // We need to call the non-locking version, because we already have the lock.
if (AudioStream::EnsureTimeStretcherInitialized() != NS_OK) { if (EnsureTimeStretcherInitialized() != NS_OK) {
return 0; return 0;
} }
@ -990,7 +779,7 @@ BufferedAudioStream::GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTim
} }
long long
BufferedAudioStream::DataCallback(void* aBuffer, long aFrames) AudioStream::DataCallback(void* aBuffer, long aFrames)
{ {
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
uint32_t available = std::min(static_cast<uint32_t>(FramesToBytes(aFrames)), mBuffer.Length()); uint32_t available = std::min(static_cast<uint32_t>(FramesToBytes(aFrames)), mBuffer.Length());
@ -1006,7 +795,7 @@ BufferedAudioStream::DataCallback(void* aBuffer, long aFrames)
// underrun at the beginning of the buffer, so the first buffer is not cut // underrun at the beginning of the buffer, so the first buffer is not cut
// in half by the silence inserted to compensate for the underrun. // in half by the silence inserted to compensate for the underrun.
if (mInRate == mOutRate) { if (mInRate == mOutRate) {
if (mLatencyRequest == AudioStream::LowLatency && !mWritten) { if (mLatencyRequest == LowLatency && !mWritten) {
servicedFrames = GetUnprocessedWithSilencePadding(output, aFrames, insertTime); servicedFrames = GetUnprocessedWithSilencePadding(output, aFrames, insertTime);
} else { } else {
servicedFrames = GetUnprocessed(output, aFrames, insertTime); servicedFrames = GetUnprocessed(output, aFrames, insertTime);
@ -1060,7 +849,7 @@ BufferedAudioStream::DataCallback(void* aBuffer, long aFrames)
} }
void void
BufferedAudioStream::StateCallback(cubeb_state aState) AudioStream::StateCallback(cubeb_state aState)
{ {
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
if (aState == CUBEB_STATE_DRAINED) { if (aState == CUBEB_STATE_DRAINED) {
@ -1071,8 +860,6 @@ BufferedAudioStream::StateCallback(cubeb_state aState)
mon.NotifyAll(); mon.NotifyAll();
} }
#endif
AudioClock::AudioClock(AudioStream* aStream) AudioClock::AudioClock(AudioStream* aStream)
:mAudioStream(aStream), :mAudioStream(aStream),
mOldOutRate(0), mOldOutRate(0),

View File

@ -9,10 +9,20 @@
#include "AudioSampleFormat.h" #include "AudioSampleFormat.h"
#include "AudioChannelCommon.h" #include "AudioChannelCommon.h"
#include "nsAutoPtr.h" #include "nsAutoPtr.h"
#include "nsAutoRef.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "Latency.h" #include "Latency.h"
#include "mozilla/StaticMutex.h" #include "mozilla/StaticMutex.h"
#include "cubeb/cubeb.h"
template <>
class nsAutoRefTraits<cubeb_stream> : public nsPointerRefTraits<cubeb_stream>
{
public:
static void Release(cubeb_stream* aStream) { cubeb_stream_destroy(aStream); }
};
namespace soundtouch { namespace soundtouch {
class SoundTouch; class SoundTouch;
} }
@ -23,84 +33,141 @@ class AudioStream;
class AudioClock class AudioClock
{ {
public: public:
AudioClock(mozilla::AudioStream* aStream); AudioClock(AudioStream* aStream);
// Initialize the clock with the current AudioStream. Need to be called // Initialize the clock with the current AudioStream. Need to be called
// before querying the clock. Called on the audio thread. // before querying the clock. Called on the audio thread.
void Init(); void Init();
// Update the number of samples that has been written in the audio backend. // Update the number of samples that has been written in the audio backend.
// Called on the state machine thread. // Called on the state machine thread.
void UpdateWritePosition(uint32_t aCount); void UpdateWritePosition(uint32_t aCount);
// Get the read position of the stream, in microseconds. // Get the read position of the stream, in microseconds.
// Called on the state machine thead. // Called on the state machine thead.
uint64_t GetPosition(); uint64_t GetPosition();
// Get the read position of the stream, in frames. // Get the read position of the stream, in frames.
// Called on the state machine thead. // Called on the state machine thead.
uint64_t GetPositionInFrames(); uint64_t GetPositionInFrames();
// Set the playback rate. // Set the playback rate.
// Called on the audio thread. // Called on the audio thread.
void SetPlaybackRate(double aPlaybackRate); void SetPlaybackRate(double aPlaybackRate);
// Get the current playback rate. // Get the current playback rate.
// Called on the audio thread. // Called on the audio thread.
double GetPlaybackRate(); double GetPlaybackRate();
// Set if we are preserving the pitch. // Set if we are preserving the pitch.
// Called on the audio thread. // Called on the audio thread.
void SetPreservesPitch(bool aPreservesPitch); void SetPreservesPitch(bool aPreservesPitch);
// Get the current pitch preservation state. // Get the current pitch preservation state.
// Called on the audio thread. // Called on the audio thread.
bool GetPreservesPitch(); bool GetPreservesPitch();
// Get the number of frames written to the backend. // Get the number of frames written to the backend.
int64_t GetWritten(); int64_t GetWritten();
private: private:
// This AudioStream holds a strong reference to this AudioClock. This // This AudioStream holds a strong reference to this AudioClock. This
// pointer is garanteed to always be valid. // pointer is garanteed to always be valid.
AudioStream* mAudioStream; AudioStream* mAudioStream;
// The old output rate, to compensate audio latency for the period inbetween // The old output rate, to compensate audio latency for the period inbetween
// the moment resampled buffers are pushed to the hardware and the moment the // the moment resampled buffers are pushed to the hardware and the moment the
// clock should take the new rate into account for A/V sync. // clock should take the new rate into account for A/V sync.
int mOldOutRate; int mOldOutRate;
// Position at which the last playback rate change occured // Position at which the last playback rate change occured
int64_t mBasePosition; int64_t mBasePosition;
// Offset, in frames, at which the last playback rate change occured // Offset, in frames, at which the last playback rate change occured
int64_t mBaseOffset; int64_t mBaseOffset;
// Old base offset (number of samples), used when changing rate to compute the // Old base offset (number of samples), used when changing rate to compute the
// position in the stream. // position in the stream.
int64_t mOldBaseOffset; int64_t mOldBaseOffset;
// Old base position (number of microseconds), when changing rate. This is the // Old base position (number of microseconds), when changing rate. This is the
// time in the media, not wall clock position. // time in the media, not wall clock position.
int64_t mOldBasePosition; int64_t mOldBasePosition;
// Write position at which the playbackRate change occured. // Write position at which the playbackRate change occured.
int64_t mPlaybackRateChangeOffset; int64_t mPlaybackRateChangeOffset;
// The previous position reached in the media, used when compensating // The previous position reached in the media, used when compensating
// latency, to have the position at which the playbackRate change occured. // latency, to have the position at which the playbackRate change occured.
int64_t mPreviousPosition; int64_t mPreviousPosition;
// Number of samples effectivelly written in backend, i.e. write position. // Number of samples effectivelly written in backend, i.e. write position.
int64_t mWritten; int64_t mWritten;
// Output rate in Hz (characteristic of the playback rate) // Output rate in Hz (characteristic of the playback rate)
int mOutRate; int mOutRate;
// Input rate in Hz (characteristic of the media being played) // Input rate in Hz (characteristic of the media being played)
int mInRate; int mInRate;
// True if the we are timestretching, false if we are resampling. // True if the we are timestretching, false if we are resampling.
bool mPreservesPitch; bool mPreservesPitch;
// True if we are playing at the old playbackRate after it has been changed. // True if we are playing at the old playbackRate after it has been changed.
bool mCompensatingLatency; bool mCompensatingLatency;
};
class CircularByteBuffer
{
public:
CircularByteBuffer()
: mBuffer(nullptr), mCapacity(0), mStart(0), mCount(0)
{}
// Set the capacity of the buffer in bytes. Must be called before any
// call to append or pop elements.
void SetCapacity(uint32_t aCapacity) {
NS_ABORT_IF_FALSE(!mBuffer, "Buffer allocated.");
mCapacity = aCapacity;
mBuffer = new uint8_t[mCapacity];
}
uint32_t Length() {
return mCount;
}
uint32_t Capacity() {
return mCapacity;
}
uint32_t Available() {
return Capacity() - Length();
}
// Append aLength bytes from aSrc to the buffer. Caller must check that
// sufficient space is available.
void AppendElements(const uint8_t* aSrc, uint32_t aLength) {
NS_ABORT_IF_FALSE(mBuffer && mCapacity, "Buffer not initialized.");
NS_ABORT_IF_FALSE(aLength <= Available(), "Buffer full.");
uint32_t end = (mStart + mCount) % mCapacity;
uint32_t toCopy = std::min(mCapacity - end, aLength);
memcpy(&mBuffer[end], aSrc, toCopy);
memcpy(&mBuffer[0], aSrc + toCopy, aLength - toCopy);
mCount += aLength;
}
// Remove aSize bytes from the buffer. Caller must check returned size in
// aSize{1,2} before using the pointer returned in aData{1,2}. Caller
// must not specify an aSize larger than Length().
void PopElements(uint32_t aSize, void** aData1, uint32_t* aSize1,
void** aData2, uint32_t* aSize2) {
NS_ABORT_IF_FALSE(mBuffer && mCapacity, "Buffer not initialized.");
NS_ABORT_IF_FALSE(aSize <= Length(), "Request too large.");
*aData1 = &mBuffer[mStart];
*aSize1 = std::min(mCapacity - mStart, aSize);
*aData2 = &mBuffer[0];
*aSize2 = aSize - *aSize1;
mCount -= *aSize1 + *aSize2;
mStart += *aSize1 + *aSize2;
mStart %= mCapacity;
}
private:
nsAutoArrayPtr<uint8_t> mBuffer;
uint32_t mCapacity;
uint32_t mStart;
uint32_t mCount;
}; };
// Access to a single instance of this class must be synchronized by // Access to a single instance of this class must be synchronized by
// callers, or made from a single thread. One exception is that access to // callers, or made from a single thread. One exception is that access to
// GetPosition, GetPositionInFrames, SetVolume, and Get{Rate,Channels} // GetPosition, GetPositionInFrames, SetVolume, and Get{Rate,Channels}
// is thread-safe without external synchronization. // is thread-safe without external synchronization.
class AudioStream class AudioStream MOZ_FINAL
{ {
public: public:
enum LatencyRequest {
HighLatency,
LowLatency
};
AudioStream();
virtual ~AudioStream();
// Initialize Audio Library. Some Audio backends require initializing the // Initialize Audio Library. Some Audio backends require initializing the
// library before using it. // library before using it.
static void InitLibrary(); static void InitLibrary();
@ -109,11 +176,6 @@ public:
// library after using it. // library after using it.
static void ShutdownLibrary(); static void ShutdownLibrary();
// AllocateStream will return either a local stream or a remoted stream
// depending on where you call it from. If you call this from a child process,
// you may receive an implementation which forwards to a compositing process.
static AudioStream* AllocateStream();
// Returns the maximum number of channels supported by the audio hardware. // Returns the maximum number of channels supported by the audio hardware.
static int MaxNumberOfChannels(); static int MaxNumberOfChannels();
@ -121,79 +183,122 @@ public:
// samplerate the hardware/mixer supports. // samplerate the hardware/mixer supports.
static int PreferredSampleRate(); static int PreferredSampleRate();
AudioStream();
~AudioStream();
enum LatencyRequest {
HighLatency,
LowLatency
};
// Initialize the audio stream. aNumChannels is the number of audio // Initialize the audio stream. aNumChannels is the number of audio
// channels (1 for mono, 2 for stereo, etc) and aRate is the sample rate // channels (1 for mono, 2 for stereo, etc) and aRate is the sample rate
// (22050Hz, 44100Hz, etc). // (22050Hz, 44100Hz, etc).
virtual nsresult Init(int32_t aNumChannels, int32_t aRate, nsresult Init(int32_t aNumChannels, int32_t aRate,
const dom::AudioChannelType aAudioStreamType, const dom::AudioChannelType aAudioStreamType,
LatencyRequest aLatencyRequest) = 0; LatencyRequest aLatencyRequest);
// Closes the stream. All future use of the stream is an error. // Closes the stream. All future use of the stream is an error.
virtual void Shutdown() = 0; void Shutdown();
// Write audio data to the audio hardware. aBuf is an array of AudioDataValues // Write audio data to the audio hardware. aBuf is an array of AudioDataValues
// AudioDataValue of length aFrames*mChannels. If aFrames is larger // AudioDataValue of length aFrames*mChannels. If aFrames is larger
// than the result of Available(), the write will block until sufficient // than the result of Available(), the write will block until sufficient
// buffer space is available. aTime is the time in ms associated with the first sample // buffer space is available. aTime is the time in ms associated with the first sample
// for latency calculations // for latency calculations
virtual nsresult Write(const mozilla::AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime = nullptr) = 0; nsresult Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp* aTime = nullptr);
// Return the number of audio frames that can be written without blocking. // Return the number of audio frames that can be written without blocking.
virtual uint32_t Available() = 0; uint32_t Available();
// Set the current volume of the audio playback. This is a value from // Set the current volume of the audio playback. This is a value from
// 0 (meaning muted) to 1 (meaning full volume). Thread-safe. // 0 (meaning muted) to 1 (meaning full volume). Thread-safe.
virtual void SetVolume(double aVolume) = 0; void SetVolume(double aVolume);
// Block until buffered audio data has been consumed. // Block until buffered audio data has been consumed.
virtual void Drain() = 0; void Drain();
// Start the stream. // Start the stream.
virtual void Start() = 0; void Start();
// Return the number of frames written so far in the stream. This allow the // Return the number of frames written so far in the stream. This allow the
// caller to check if it is safe to start the stream, if needed. // caller to check if it is safe to start the stream, if needed.
virtual int64_t GetWritten(); int64_t GetWritten();
// Pause audio playback. // Pause audio playback.
virtual void Pause() = 0; void Pause();
// Resume audio playback. // Resume audio playback.
virtual void Resume() = 0; void Resume();
// Return the position in microseconds of the audio frame being played by // Return the position in microseconds of the audio frame being played by
// the audio hardware, compensated for playback rate change. Thread-safe. // the audio hardware, compensated for playback rate change. Thread-safe.
virtual int64_t GetPosition() = 0; int64_t GetPosition();
// Return the position, measured in audio frames played since the stream // Return the position, measured in audio frames played since the stream
// was opened, of the audio hardware. Thread-safe. // was opened, of the audio hardware. Thread-safe.
virtual int64_t GetPositionInFrames() = 0; int64_t GetPositionInFrames();
// Return the position, measured in audio framed played since the stream was // Return the position, measured in audio framed played since the stream was
// opened, of the audio hardware, not adjusted for the changes of playback // opened, of the audio hardware, not adjusted for the changes of playback
// rate. // rate.
virtual int64_t GetPositionInFramesInternal() = 0; int64_t GetPositionInFramesInternal();
// Returns true when the audio stream is paused. // Returns true when the audio stream is paused.
virtual bool IsPaused() = 0; bool IsPaused();
int GetRate() { return mOutRate; } int GetRate() { return mOutRate; }
int GetChannels() { return mChannels; } int GetChannels() { return mChannels; }
// This should be called before attempting to use the time stretcher. // This should be called before attempting to use the time stretcher.
virtual nsresult EnsureTimeStretcherInitialized(); nsresult EnsureTimeStretcherInitialized();
// Set playback rate as a multiple of the intrinsic playback rate. This is to // Set playback rate as a multiple of the intrinsic playback rate. This is to
// be called only with aPlaybackRate > 0.0. // be called only with aPlaybackRate > 0.0.
virtual nsresult SetPlaybackRate(double aPlaybackRate); nsresult SetPlaybackRate(double aPlaybackRate);
// Switch between resampling (if false) and time stretching (if true, default). // Switch between resampling (if false) and time stretching (if true, default).
virtual nsresult SetPreservesPitch(bool aPreservesPitch); nsresult SetPreservesPitch(bool aPreservesPitch);
private:
static int PrefChanged(const char* aPref, void* aClosure);
static double GetVolumeScale();
static cubeb* GetCubebContext();
static cubeb* GetCubebContextUnlocked();
static uint32_t GetCubebLatency();
static bool CubebLatencyPrefSet();
static long DataCallback_S(cubeb_stream*, void* aThis, void* aBuffer, long aFrames)
{
return static_cast<AudioStream*>(aThis)->DataCallback(aBuffer, aFrames);
}
static void StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState)
{
static_cast<AudioStream*>(aThis)->StateCallback(aState);
}
long DataCallback(void* aBuffer, long aFrames);
void StateCallback(cubeb_state aState);
// aTime is the time in ms the samples were inserted into MediaStreamGraph
long GetUnprocessed(void* aBuffer, long aFrames, int64_t &aTime);
long GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTime);
long GetUnprocessedWithSilencePadding(void* aBuffer, long aFrames, int64_t &aTime);
// Shared implementation of underflow adjusted position calculation.
// Caller must own the monitor.
int64_t GetPositionInFramesUnlocked();
int64_t GetLatencyInFrames();
void GetBufferInsertTime(int64_t &aTimeMs);
void StartUnlocked();
// The monitor is held to protect all access to member variables. Write()
// waits while mBuffer is full; DataCallback() notifies as it consumes
// data from mBuffer. Drain() waits while mState is DRAINING;
// StateCallback() notifies when mState is DRAINED.
Monitor mMonitor;
protected:
// This mutex protects the mPreferedSamplerate member below.
static StaticMutex mMutex;
// Prefered samplerate, in Hz (characteristic of the
// hardware/mixer/platform/API used).
static uint32_t mPreferredSampleRate;
// Input rate in Hz (characteristic of the media being played) // Input rate in Hz (characteristic of the media being played)
int mInRate; int mInRate;
// Output rate in Hz (characteristic of the playback rate) // Output rate in Hz (characteristic of the playback rate)
@ -218,7 +323,65 @@ protected:
int64_t mTimeMs; int64_t mTimeMs;
int64_t mFrames; int64_t mFrames;
}; };
nsAutoTArray<Inserts,8> mInserts; nsAutoTArray<Inserts, 8> mInserts;
// Sum of silent frames written when DataCallback requests more frames
// than are available in mBuffer.
uint64_t mLostFrames;
// Output file for dumping audio
FILE* mDumpFile;
// Temporary audio buffer. Filled by Write() and consumed by
// DataCallback(). Once mBuffer is full, Write() blocks until sufficient
// space becomes available in mBuffer. mBuffer is sized in bytes, not
// frames.
CircularByteBuffer mBuffer;
// Software volume level. Applied during the servicing of DataCallback().
double mVolume;
// Owning reference to a cubeb_stream. cubeb_stream_destroy is called by
// nsAutoRef's destructor.
nsAutoRef<cubeb_stream> mCubebStream;
uint32_t mBytesPerFrame;
uint32_t BytesToFrames(uint32_t aBytes) {
NS_ASSERTION(aBytes % mBytesPerFrame == 0,
"Byte count not aligned on frames size.");
return aBytes / mBytesPerFrame;
}
uint32_t FramesToBytes(uint32_t aFrames) {
return aFrames * mBytesPerFrame;
}
enum StreamState {
INITIALIZED, // Initialized, playback has not begun.
STARTED, // Started by a call to Write() (iff INITIALIZED) or Resume().
STOPPED, // Stopped by a call to Pause().
DRAINING, // Drain requested. DataCallback will indicate end of stream
// once the remaining contents of mBuffer are requested by
// cubeb, after which StateCallback will indicate drain
// completion.
DRAINED, // StateCallback has indicated that the drain is complete.
ERRORED // Stream disabled due to an internal error.
};
StreamState mState;
// This mutex protects the static members below.
static StaticMutex sMutex;
static cubeb* sCubebContext;
// Prefered samplerate, in Hz (characteristic of the
// hardware/mixer/platform/API used).
static uint32_t sPreferredSampleRate;
static double sVolumeScale;
static uint32_t sCubebLatency;
static bool sCubebLatencyPrefSet;
}; };
} // namespace mozilla } // namespace mozilla

View File

@ -1056,7 +1056,7 @@ void MediaDecoderStateMachine::AudioLoop()
// AudioStream initialization can block for extended periods in unusual // AudioStream initialization can block for extended periods in unusual
// circumstances, so we take care to drop the decoder monitor while // circumstances, so we take care to drop the decoder monitor while
// initializing. // initializing.
nsAutoPtr<AudioStream> audioStream(AudioStream::AllocateStream()); nsAutoPtr<AudioStream> audioStream(new AudioStream());
audioStream->Init(channels, rate, audioChannelType, AudioStream::HighLatency); audioStream->Init(channels, rate, audioChannelType, AudioStream::HighLatency);
audioStream->SetVolume(volume); audioStream->SetVolume(volume);
if (audioStream->SetPreservesPitch(preservesPitch) != NS_OK) { if (audioStream->SetPreservesPitch(preservesPitch) != NS_OK) {

View File

@ -778,7 +778,7 @@ MediaStreamGraphImpl::CreateOrDestroyAudioStreams(GraphTime aAudioOutputStartTim
aStream->mAudioOutputStreams.AppendElement(); aStream->mAudioOutputStreams.AppendElement();
audioOutputStream->mAudioPlaybackStartTime = aAudioOutputStartTime; audioOutputStream->mAudioPlaybackStartTime = aAudioOutputStartTime;
audioOutputStream->mBlockedAudioTime = 0; audioOutputStream->mBlockedAudioTime = 0;
audioOutputStream->mStream = AudioStream::AllocateStream(); audioOutputStream->mStream = new AudioStream();
// XXX for now, allocate stereo output. But we need to fix this to // XXX for now, allocate stereo output. But we need to fix this to
// match the system's ideal channel configuration. // match the system's ideal channel configuration.
audioOutputStream->mStream->Init(2, tracks->GetRate(), AUDIO_CHANNEL_NORMAL, AudioStream::LowLatency); audioOutputStream->mStream->Init(2, tracks->GetRate(), AUDIO_CHANNEL_NORMAL, AudioStream::LowLatency);

View File

@ -17,10 +17,5 @@ ifeq (WINNT,$(OS_TARGET))
symbols.def: symbols.def.in $(GLOBAL_DEPS) symbols.def: symbols.def.in $(GLOBAL_DEPS)
$(call py_action,preprocessor,$(ACDEFINES) $< -o $@) $(call py_action,preprocessor,$(ACDEFINES) $< -o $@)
OS_LIBS += $(call EXPAND_LIBNAME, msimg32) OS_LIBS += $(call EXPAND_LIBNAME, msimg32 winmm)
ifdef MOZ_CUBEB
OS_LIBS += $(call EXPAND_LIBNAME, winmm)
endif
endif endif

View File

@ -117,7 +117,6 @@ speex_resampler_get_output_latency
speex_resampler_skip_zeros speex_resampler_skip_zeros
speex_resampler_reset_mem speex_resampler_reset_mem
speex_resampler_strerror speex_resampler_strerror
#ifdef MOZ_CUBEB
cubeb_destroy cubeb_destroy
cubeb_init cubeb_init
cubeb_get_max_channel_count cubeb_get_max_channel_count
@ -129,7 +128,6 @@ cubeb_stream_init
cubeb_stream_start cubeb_stream_start
cubeb_stream_stop cubeb_stream_stop
cubeb_stream_get_latency cubeb_stream_get_latency
#endif
#ifdef MOZ_OGG #ifdef MOZ_OGG
th_comment_clear th_comment_clear
th_comment_init th_comment_init

View File

@ -5,4 +5,4 @@ Makefile.in build files for the Mozilla build system.
The cubeb git repository is: git://github.com/kinetiknz/cubeb.git The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
The git commit ID used was 8c78a282aa0320e997436d6832024efe1527ca1c. The git commit ID used was e92a27c96c0efd33acf983e4c873376ff4cae3d8.

View File

@ -159,7 +159,7 @@ cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels)
int int
cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms) cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms)
{ {
if (!latency_ms) { if (!context || !latency_ms) {
return CUBEB_ERROR_INVALID_PARAMETER; return CUBEB_ERROR_INVALID_PARAMETER;
} }
return context->ops->get_min_latency(context, params, latency_ms); return context->ops->get_min_latency(context, params, latency_ms);
@ -168,7 +168,7 @@ cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * la
int int
cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate) cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
{ {
if (!rate) { if (!context || !rate) {
return CUBEB_ERROR_INVALID_PARAMETER; return CUBEB_ERROR_INVALID_PARAMETER;
} }
return context->ops->get_preferred_sample_rate(context, rate); return context->ops->get_preferred_sample_rate(context, rate);

View File

@ -17,13 +17,11 @@ LIBS = \
$(DEPTH)/netwerk/srtp/src/$(LIB_PREFIX)nksrtp_s.$(LIB_SUFFIX) \ $(DEPTH)/netwerk/srtp/src/$(LIB_PREFIX)nksrtp_s.$(LIB_SUFFIX) \
$(NULL) $(NULL)
ifdef MOZ_CUBEB
ifdef MOZ_ALSA ifdef MOZ_ALSA
LIBS += \ LIBS += \
$(MOZ_ALSA_LIBS) \ $(MOZ_ALSA_LIBS) \
$(NULL) $(NULL)
endif endif
endif
ifeq ($(OS_TARGET),Android) ifeq ($(OS_TARGET),Android)
LIBS += \ LIBS += \

View File

@ -180,11 +180,9 @@ endif
endif endif
ifdef MOZ_CUBEB
ifdef MOZ_ALSA ifdef MOZ_ALSA
EXTRA_DSO_LDOPTS += $(MOZ_ALSA_LIBS) EXTRA_DSO_LDOPTS += $(MOZ_ALSA_LIBS)
endif endif
endif
ifdef HAVE_CLOCK_MONOTONIC ifdef HAVE_CLOCK_MONOTONIC
EXTRA_DSO_LDOPTS += $(REALTIME_LIBS) EXTRA_DSO_LDOPTS += $(REALTIME_LIBS)
@ -247,10 +245,8 @@ OS_LIBS += \
endif endif
ifeq (OpenBSD,$(OS_ARCH)) ifeq (OpenBSD,$(OS_ARCH))
ifdef MOZ_CUBEB
EXTRA_DSO_LDOPTS += -lsndio EXTRA_DSO_LDOPTS += -lsndio
endif endif
endif
ifdef MOZ_ENABLE_DBUS ifdef MOZ_ENABLE_DBUS
EXTRA_DSO_LDOPTS += $(MOZ_DBUS_GLIB_LIBS) EXTRA_DSO_LDOPTS += $(MOZ_DBUS_GLIB_LIBS)