зеркало из https://github.com/mozilla/gecko-dev.git
Bug 942657 - Devirtualize AudioStream. r=doublec,gps
This commit is contained in:
Родитель
02f058e57a
Коммит
2ca1434c82
|
@ -33,14 +33,12 @@ if CONFIG['MOZ_VP8'] and not CONFIG['MOZ_NATIVE_LIBVPX']:
|
|||
if CONFIG['MOZ_OGG']:
|
||||
external_dirs += ['media/libogg', 'media/libtheora']
|
||||
|
||||
if CONFIG['MOZ_CUBEB']:
|
||||
external_dirs += ['media/libcubeb']
|
||||
|
||||
if not CONFIG['MOZ_NATIVE_PNG']:
|
||||
external_dirs += ['media/libpng']
|
||||
|
||||
external_dirs += [
|
||||
'media/kiss_fft',
|
||||
'media/libcubeb',
|
||||
'media/libspeex_resampler',
|
||||
'media/libsoundtouch',
|
||||
]
|
||||
|
|
29
configure.in
29
configure.in
|
@ -3938,7 +3938,6 @@ MOZ_JSDEBUGGER=1
|
|||
MOZ_AUTH_EXTENSION=1
|
||||
MOZ_OGG=1
|
||||
MOZ_RAW=
|
||||
MOZ_CUBEB=
|
||||
MOZ_VORBIS=
|
||||
MOZ_TREMOR=
|
||||
MOZ_WAVE=1
|
||||
|
@ -5199,7 +5198,6 @@ MOZ_ARG_DISABLE_BOOL(ogg,
|
|||
|
||||
if test -n "$MOZ_OGG"; then
|
||||
AC_DEFINE(MOZ_OGG)
|
||||
MOZ_CUBEB=1
|
||||
|
||||
dnl Checks for __attribute__(aligned()) directive
|
||||
AC_CACHE_CHECK([__attribute__ ((aligned ())) support],
|
||||
|
@ -5259,7 +5257,6 @@ MOZ_ARG_DISABLE_BOOL(directshow,
|
|||
|
||||
if test -n "$MOZ_DIRECTSHOW"; then
|
||||
AC_DEFINE(MOZ_DIRECTSHOW)
|
||||
MOZ_CUBEB=1
|
||||
fi;
|
||||
|
||||
dnl ========================================================
|
||||
|
@ -5279,7 +5276,6 @@ MOZ_ARG_DISABLE_BOOL(wmf,
|
|||
|
||||
if test -n "$MOZ_WMF"; then
|
||||
AC_DEFINE(MOZ_WMF)
|
||||
MOZ_CUBEB=1
|
||||
fi;
|
||||
|
||||
dnl ========================================================
|
||||
|
@ -5410,7 +5406,6 @@ AC_SUBST(MOZ_LIBVPX_CFLAGS)
|
|||
AC_SUBST(MOZ_LIBVPX_LIBS)
|
||||
|
||||
if test "$MOZ_WEBM" -o "$MOZ_OGG"; then
|
||||
MOZ_CUBEB=1
|
||||
if test "$MOZ_SAMPLE_TYPE_FLOAT32"; then
|
||||
MOZ_VORBIS=1
|
||||
else
|
||||
|
@ -5512,17 +5507,12 @@ MOZ_ARG_DISABLE_BOOL(wave,
|
|||
|
||||
if test -n "$MOZ_WAVE"; then
|
||||
AC_DEFINE(MOZ_WAVE)
|
||||
MOZ_CUBEB=1
|
||||
fi
|
||||
|
||||
dnl ========================================================
|
||||
dnl = Handle dependent CUBEB and MEDIA defines
|
||||
dnl = Handle dependent MEDIA defines
|
||||
dnl ========================================================
|
||||
|
||||
if test -n "$MOZ_CUBEB"; then
|
||||
AC_DEFINE(MOZ_CUBEB)
|
||||
fi
|
||||
|
||||
if test -n "$MOZ_OPUS" -a -z "$MOZ_OGG"; then
|
||||
AC_MSG_ERROR([MOZ_OPUS requires MOZ_OGG which is disabled.])
|
||||
fi
|
||||
|
@ -5552,12 +5542,12 @@ if test -n "$MOZ_OPUS"; then
|
|||
AC_DEFINE(MOZ_OPUS)
|
||||
fi
|
||||
|
||||
dnl ====================================================
|
||||
dnl = Check alsa availability on Linux if using libcubeb
|
||||
dnl ====================================================
|
||||
dnl ==================================
|
||||
dnl = Check alsa availability on Linux
|
||||
dnl ==================================
|
||||
|
||||
dnl If using libcubeb with Linux, ensure that the alsa library is available
|
||||
if test -n "$MOZ_CUBEB" -a "$OS_TARGET" = "Linux"; then
|
||||
dnl If using Linux, ensure that the alsa library is available
|
||||
if test "$OS_TARGET" = "Linux"; then
|
||||
MOZ_ALSA=1
|
||||
fi
|
||||
|
||||
|
@ -5567,7 +5557,6 @@ MOZ_ARG_ENABLE_BOOL(alsa,
|
|||
MOZ_ALSA=)
|
||||
|
||||
if test -n "$MOZ_ALSA"; then
|
||||
AC_DEFINE(MOZ_CUBEB)
|
||||
PKG_CHECK_MODULES(MOZ_ALSA, alsa, ,
|
||||
[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.)])])
|
||||
|
@ -5581,8 +5570,8 @@ dnl ========================================================
|
|||
dnl = Disable PulseAudio
|
||||
dnl ========================================================
|
||||
|
||||
dnl If using libcubeb with Linux, ensure that the PA library is available
|
||||
if test -n "$MOZ_CUBEB" -a "$OS_TARGET" = "Linux" -a -z "$MOZ_B2G"; then
|
||||
dnl If using Linux, ensure that the PA library is available
|
||||
if test "$OS_TARGET" = "Linux" -a -z "$MOZ_B2G"; then
|
||||
MOZ_PULSEAUDIO=1
|
||||
fi
|
||||
|
||||
|
@ -5592,7 +5581,6 @@ MOZ_ARG_DISABLE_BOOL(pulseaudio,
|
|||
MOZ_PULSEAUDIO=1)
|
||||
|
||||
if test -n "$MOZ_PULSEAUDIO"; then
|
||||
AC_DEFINE(MOZ_CUBEB)
|
||||
if test -z "$gonkdir"; then
|
||||
PKG_CHECK_MODULES(MOZ_PULSEAUDIO, libpulse, ,
|
||||
[echo "$MOZ_PULSEAUDIO_PKG_ERRORS"
|
||||
|
@ -8655,7 +8643,6 @@ AC_SUBST(MOZ_NSS_PATCH)
|
|||
AC_SUBST(MOZ_APP_COMPONENT_LIBS)
|
||||
AC_SUBST(MOZ_APP_EXTRA_LIBS)
|
||||
|
||||
AC_SUBST(MOZ_CUBEB)
|
||||
AC_SUBST(MOZ_WAVE)
|
||||
AC_SUBST(MOZ_VORBIS)
|
||||
AC_SUBST(MOZ_TREMOR)
|
||||
|
|
|
@ -111,7 +111,7 @@ HTMLAudioElement::MozSetup(uint32_t aChannels, uint32_t aRate, ErrorResult& aRv)
|
|||
}
|
||||
#endif
|
||||
|
||||
mAudioStream = AudioStream::AllocateStream();
|
||||
mAudioStream = new AudioStream();
|
||||
aRv = mAudioStream->Init(aChannels, aRate, mAudioChannelType, AudioStream::HighLatency);
|
||||
if (aRv.Failed()) {
|
||||
mAudioStream->Shutdown();
|
||||
|
|
|
@ -16,104 +16,91 @@
|
|||
#include "soundtouch/SoundTouch.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 {
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
PRLogModuleInfo* gAudioStreamLog = nullptr;
|
||||
#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),
|
||||
* 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.
|
||||
*/
|
||||
#if defined(MOZ_CUBEB)
|
||||
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) {
|
||||
nsAdoptingString value = Preferences::GetString(aPref);
|
||||
MutexAutoLock lock(*gAudioPrefsLock);
|
||||
StaticMutexAutoLock lock(sMutex);
|
||||
if (value.IsEmpty()) {
|
||||
gVolumeScale = 1.0;
|
||||
sVolumeScale = 1.0;
|
||||
} else {
|
||||
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) {
|
||||
// Arbitrary default stream latency of 100ms. The higher this
|
||||
// value, the longer stream volume changes will take to become
|
||||
// audible.
|
||||
gCubebLatencyPrefSet = Preferences::HasUserValue(aPref);
|
||||
sCubebLatencyPrefSet = Preferences::HasUserValue(aPref);
|
||||
uint32_t value = Preferences::GetUint(aPref, CUBEB_NORMAL_LATENCY_MS);
|
||||
MutexAutoLock lock(*gAudioPrefsLock);
|
||||
gCubebLatency = std::min<uint32_t>(std::max<uint32_t>(value, 1), 1000);
|
||||
StaticMutexAutoLock lock(sMutex);
|
||||
sCubebLatency = std::min<uint32_t>(std::max<uint32_t>(value, 1), 1000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(MOZ_CUBEB)
|
||||
static double GetVolumeScale()
|
||||
/*static*/ double AudioStream::GetVolumeScale()
|
||||
{
|
||||
MutexAutoLock lock(*gAudioPrefsLock);
|
||||
return gVolumeScale;
|
||||
StaticMutexAutoLock lock(sMutex);
|
||||
return sVolumeScale;
|
||||
}
|
||||
|
||||
static cubeb* gCubebContext;
|
||||
|
||||
static cubeb* GetCubebContext()
|
||||
/*static*/ cubeb* AudioStream::GetCubebContext()
|
||||
{
|
||||
MutexAutoLock lock(*gAudioPrefsLock);
|
||||
if (gCubebContext ||
|
||||
cubeb_init(&gCubebContext, "AudioStream") == CUBEB_OK) {
|
||||
return gCubebContext;
|
||||
StaticMutexAutoLock lock(sMutex);
|
||||
return GetCubebContextUnlocked();
|
||||
}
|
||||
|
||||
/*static*/ cubeb* AudioStream::GetCubebContextUnlocked()
|
||||
{
|
||||
sMutex.AssertCurrentThreadOwns();
|
||||
if (sCubebContext ||
|
||||
cubeb_init(&sCubebContext, "AudioStream") == CUBEB_OK) {
|
||||
return sCubebContext;
|
||||
}
|
||||
NS_WARNING("cubeb_init failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static uint32_t GetCubebLatency()
|
||||
/*static*/ uint32_t AudioStream::GetCubebLatency()
|
||||
{
|
||||
MutexAutoLock lock(*gAudioPrefsLock);
|
||||
return gCubebLatency;
|
||||
StaticMutexAutoLock lock(sMutex);
|
||||
return sCubebLatency;
|
||||
}
|
||||
|
||||
static bool CubebLatencyPrefSet()
|
||||
/*static*/ bool AudioStream::CubebLatencyPrefSet()
|
||||
{
|
||||
MutexAutoLock lock(*gAudioPrefsLock);
|
||||
return gCubebLatencyPrefSet;
|
||||
StaticMutexAutoLock lock(sMutex);
|
||||
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)
|
||||
{
|
||||
switch(aType) {
|
||||
|
@ -139,52 +126,64 @@ static cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannelType aType)
|
|||
#endif
|
||||
|
||||
AudioStream::AudioStream()
|
||||
: mInRate(0),
|
||||
mOutRate(0),
|
||||
mChannels(0),
|
||||
mWritten(0),
|
||||
mAudioClock(MOZ_THIS_IN_INITIALIZER_LIST()),
|
||||
mLatencyRequest(HighLatency),
|
||||
mReadPoint(0)
|
||||
{}
|
||||
|
||||
void AudioStream::InitLibrary()
|
||||
: mMonitor("AudioStream")
|
||||
, mInRate(0)
|
||||
, mOutRate(0)
|
||||
, mChannels(0)
|
||||
, mWritten(0)
|
||||
, mAudioClock(MOZ_THIS_IN_INITIALIZER_LIST())
|
||||
, mLatencyRequest(HighLatency)
|
||||
, mReadPoint(0)
|
||||
, mLostFrames(0)
|
||||
, mDumpFile(nullptr)
|
||||
, mVolume(1.0)
|
||||
, mBytesPerFrame(0)
|
||||
, mState(INITIALIZED)
|
||||
{
|
||||
#ifdef PR_LOGGING
|
||||
gAudioStreamLog = PR_NewLogModule("AudioStream");
|
||||
#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
|
||||
// keep a ref in case we shut down later than nsLayoutStatics
|
||||
mLatencyLog = AsyncLatencyLogger::Get(true);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
return EnsureTimeStretcherInitializedUnlocked();
|
||||
}
|
||||
|
||||
nsresult AudioStream::EnsureTimeStretcherInitializedUnlocked()
|
||||
{
|
||||
mMonitor.AssertCurrentThreadOwns();
|
||||
if (!mTimeStretcher) {
|
||||
// SoundTouch does not support a number of channels > 2
|
||||
if (mChannels > 2) {
|
||||
|
@ -214,7 +213,6 @@ nsresult AudioStream::SetPlaybackRate(double aPlaybackRate)
|
|||
mAudioClock.SetPlaybackRate(aPlaybackRate);
|
||||
mOutRate = mInRate / aPlaybackRate;
|
||||
|
||||
|
||||
if (mAudioClock.GetPreservesPitch()) {
|
||||
mTimeStretcher->setTempo(aPlaybackRate);
|
||||
mTimeStretcher->setRate(1.0f);
|
||||
|
@ -254,224 +252,43 @@ int64_t AudioStream::GetWritten()
|
|||
return mWritten;
|
||||
}
|
||||
|
||||
#if defined(MOZ_CUBEB)
|
||||
class nsCircularByteBuffer
|
||||
/*static*/ int AudioStream::MaxNumberOfChannels()
|
||||
{
|
||||
public:
|
||||
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)
|
||||
cubeb* cubebContext = GetCubebContext();
|
||||
uint32_t maxNumberOfChannels;
|
||||
|
||||
if (cubeb_get_max_channel_count(GetCubebContext(),
|
||||
if (cubebContext &&
|
||||
cubeb_get_max_channel_count(cubebContext,
|
||||
&maxNumberOfChannels) == CUBEB_OK) {
|
||||
return static_cast<int>(maxNumberOfChannels);
|
||||
}
|
||||
#endif
|
||||
|
||||
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
|
||||
// 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
|
||||
// backend used.
|
||||
const int fallbackSampleRate = 44100;
|
||||
if (mPreferredSampleRate == 0) {
|
||||
#if defined(MOZ_CUBEB)
|
||||
if (cubeb_get_preferred_sample_rate(GetCubebContext(),
|
||||
&mPreferredSampleRate) == CUBEB_OK) {
|
||||
return mPreferredSampleRate;
|
||||
}
|
||||
#endif
|
||||
mPreferredSampleRate = fallbackSampleRate;
|
||||
if (cubeb_get_preferred_sample_rate(cubebContext,
|
||||
&sPreferredSampleRate) != CUBEB_OK) {
|
||||
sPreferredSampleRate = fallbackSampleRate;
|
||||
}
|
||||
|
||||
return mPreferredSampleRate;
|
||||
return sPreferredSampleRate;
|
||||
}
|
||||
|
||||
#if defined(MOZ_CUBEB)
|
||||
static void SetUint16LE(uint8_t* aDest, uint16_t aValue)
|
||||
{
|
||||
aDest[0] = aValue & 0xFF;
|
||||
|
@ -541,33 +358,10 @@ WriteDumpFile(FILE* aDumpFile, AudioStream* aStream, uint32_t aFrames,
|
|||
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
|
||||
BufferedAudioStream::EnsureTimeStretcherInitialized()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
return AudioStream::EnsureTimeStretcherInitialized();
|
||||
}
|
||||
|
||||
nsresult
|
||||
BufferedAudioStream::Init(int32_t aNumChannels, int32_t aRate,
|
||||
const dom::AudioChannelType aAudioChannelType,
|
||||
AudioStream::LatencyRequest aLatencyRequest)
|
||||
AudioStream::Init(int32_t aNumChannels, int32_t aRate,
|
||||
const dom::AudioChannelType aAudioChannelType,
|
||||
LatencyRequest aLatencyRequest)
|
||||
{
|
||||
cubeb* cubebContext = GetCubebContext();
|
||||
|
||||
|
@ -610,7 +404,7 @@ BufferedAudioStream::Init(int32_t aNumChannels, int32_t aRate,
|
|||
// for low latency playback, try to get the lowest latency possible.
|
||||
// Otherwise, for normal streams, use 100ms.
|
||||
uint32_t latency;
|
||||
if (aLatencyRequest == AudioStream::LowLatency && !CubebLatencyPrefSet()) {
|
||||
if (aLatencyRequest == LowLatency && !CubebLatencyPrefSet()) {
|
||||
if (cubeb_get_min_latency(cubebContext, params, &latency) != CUBEB_OK) {
|
||||
latency = GetCubebLatency();
|
||||
}
|
||||
|
@ -620,7 +414,7 @@ BufferedAudioStream::Init(int32_t aNumChannels, int32_t aRate,
|
|||
|
||||
{
|
||||
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) {
|
||||
mCubebStream.own(stream);
|
||||
}
|
||||
|
@ -639,8 +433,8 @@ BufferedAudioStream::Init(int32_t aNumChannels, int32_t aRate,
|
|||
|
||||
// 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
|
||||
// are writtent to this BufferedAudioStream.
|
||||
if (mLatencyRequest == AudioStream::LowLatency) {
|
||||
// are writtent to this AudioStream.
|
||||
if (mLatencyRequest == LowLatency) {
|
||||
Start();
|
||||
}
|
||||
|
||||
|
@ -648,7 +442,7 @@ BufferedAudioStream::Init(int32_t aNumChannels, int32_t aRate,
|
|||
}
|
||||
|
||||
void
|
||||
BufferedAudioStream::Shutdown()
|
||||
AudioStream::Shutdown()
|
||||
{
|
||||
if (mState == STARTED) {
|
||||
Pause();
|
||||
|
@ -660,7 +454,7 @@ BufferedAudioStream::Shutdown()
|
|||
|
||||
// aTime is the time in ms the samples were inserted into MediaStreamGraph
|
||||
nsresult
|
||||
BufferedAudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime)
|
||||
AudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime)
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (!mCubebStream || mState == ERRORED) {
|
||||
|
@ -715,7 +509,7 @@ BufferedAudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeSta
|
|||
}
|
||||
|
||||
uint32_t
|
||||
BufferedAudioStream::Available()
|
||||
AudioStream::Available()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Buffer invariant violated.");
|
||||
|
@ -723,7 +517,7 @@ BufferedAudioStream::Available()
|
|||
}
|
||||
|
||||
void
|
||||
BufferedAudioStream::SetVolume(double aVolume)
|
||||
AudioStream::SetVolume(double aVolume)
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
NS_ABORT_IF_FALSE(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
|
||||
|
@ -731,7 +525,7 @@ BufferedAudioStream::SetVolume(double aVolume)
|
|||
}
|
||||
|
||||
void
|
||||
BufferedAudioStream::Drain()
|
||||
AudioStream::Drain()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (mState != STARTED) {
|
||||
|
@ -745,14 +539,14 @@ BufferedAudioStream::Drain()
|
|||
}
|
||||
|
||||
void
|
||||
BufferedAudioStream::Start()
|
||||
AudioStream::Start()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
StartUnlocked();
|
||||
}
|
||||
|
||||
void
|
||||
BufferedAudioStream::StartUnlocked()
|
||||
AudioStream::StartUnlocked()
|
||||
{
|
||||
mMonitor.AssertCurrentThreadOwns();
|
||||
if (!mCubebStream || mState != INITIALIZED) {
|
||||
|
@ -771,7 +565,7 @@ BufferedAudioStream::StartUnlocked()
|
|||
}
|
||||
|
||||
void
|
||||
BufferedAudioStream::Pause()
|
||||
AudioStream::Pause()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (!mCubebStream || mState != STARTED) {
|
||||
|
@ -789,7 +583,7 @@ BufferedAudioStream::Pause()
|
|||
}
|
||||
|
||||
void
|
||||
BufferedAudioStream::Resume()
|
||||
AudioStream::Resume()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (!mCubebStream || mState != STOPPED) {
|
||||
|
@ -807,7 +601,7 @@ BufferedAudioStream::Resume()
|
|||
}
|
||||
|
||||
int64_t
|
||||
BufferedAudioStream::GetPosition()
|
||||
AudioStream::GetPosition()
|
||||
{
|
||||
return mAudioClock.GetPosition();
|
||||
}
|
||||
|
@ -817,7 +611,7 @@ BufferedAudioStream::GetPosition()
|
|||
#pragma optimize("", off)
|
||||
#endif
|
||||
int64_t
|
||||
BufferedAudioStream::GetPositionInFrames()
|
||||
AudioStream::GetPositionInFrames()
|
||||
{
|
||||
return mAudioClock.GetPositionInFrames();
|
||||
}
|
||||
|
@ -826,14 +620,14 @@ BufferedAudioStream::GetPositionInFrames()
|
|||
#endif
|
||||
|
||||
int64_t
|
||||
BufferedAudioStream::GetPositionInFramesInternal()
|
||||
AudioStream::GetPositionInFramesInternal()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
return GetPositionInFramesUnlocked();
|
||||
}
|
||||
|
||||
int64_t
|
||||
BufferedAudioStream::GetPositionInFramesUnlocked()
|
||||
AudioStream::GetPositionInFramesUnlocked()
|
||||
{
|
||||
mMonitor.AssertCurrentThreadOwns();
|
||||
|
||||
|
@ -859,10 +653,10 @@ BufferedAudioStream::GetPositionInFramesUnlocked()
|
|||
}
|
||||
|
||||
int64_t
|
||||
BufferedAudioStream::GetLatencyInFrames()
|
||||
AudioStream::GetLatencyInFrames()
|
||||
{
|
||||
uint32_t latency;
|
||||
if(cubeb_stream_get_latency(mCubebStream, &latency)) {
|
||||
if (cubeb_stream_get_latency(mCubebStream, &latency)) {
|
||||
NS_WARNING("Could not get cubeb latency.");
|
||||
return 0;
|
||||
}
|
||||
|
@ -870,14 +664,14 @@ BufferedAudioStream::GetLatencyInFrames()
|
|||
}
|
||||
|
||||
bool
|
||||
BufferedAudioStream::IsPaused()
|
||||
AudioStream::IsPaused()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
return mState == STOPPED;
|
||||
}
|
||||
|
||||
void
|
||||
BufferedAudioStream::GetBufferInsertTime(int64_t &aTimeMs)
|
||||
AudioStream::GetBufferInsertTime(int64_t &aTimeMs)
|
||||
{
|
||||
if (mInserts.Length() > 0) {
|
||||
// Find the right block, but don't leave the array empty
|
||||
|
@ -894,7 +688,7 @@ BufferedAudioStream::GetBufferInsertTime(int64_t &aTimeMs)
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -925,7 +719,7 @@ BufferedAudioStream::GetUnprocessed(void* aBuffer, long aFrames, int64_t &aTimeM
|
|||
// Get unprocessed samples, and pad the beginning of the buffer with silence if
|
||||
// there is not enough data.
|
||||
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 available = std::min(toPopBytes, mBuffer.Length());
|
||||
|
@ -949,12 +743,12 @@ BufferedAudioStream::GetUnprocessedWithSilencePadding(void* aBuffer, long aFrame
|
|||
}
|
||||
|
||||
long
|
||||
BufferedAudioStream::GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTimeMs)
|
||||
AudioStream::GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTimeMs)
|
||||
{
|
||||
long processedFrames = 0;
|
||||
|
||||
// We need to call the non-locking version, because we already have the lock.
|
||||
if (AudioStream::EnsureTimeStretcherInitialized() != NS_OK) {
|
||||
if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -990,7 +784,7 @@ BufferedAudioStream::GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTim
|
|||
}
|
||||
|
||||
long
|
||||
BufferedAudioStream::DataCallback(void* aBuffer, long aFrames)
|
||||
AudioStream::DataCallback(void* aBuffer, long aFrames)
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
uint32_t available = std::min(static_cast<uint32_t>(FramesToBytes(aFrames)), mBuffer.Length());
|
||||
|
@ -1006,7 +800,7 @@ BufferedAudioStream::DataCallback(void* aBuffer, long aFrames)
|
|||
// 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.
|
||||
if (mInRate == mOutRate) {
|
||||
if (mLatencyRequest == AudioStream::LowLatency && !mWritten) {
|
||||
if (mLatencyRequest == LowLatency && !mWritten) {
|
||||
servicedFrames = GetUnprocessedWithSilencePadding(output, aFrames, insertTime);
|
||||
} else {
|
||||
servicedFrames = GetUnprocessed(output, aFrames, insertTime);
|
||||
|
@ -1060,7 +854,7 @@ BufferedAudioStream::DataCallback(void* aBuffer, long aFrames)
|
|||
}
|
||||
|
||||
void
|
||||
BufferedAudioStream::StateCallback(cubeb_state aState)
|
||||
AudioStream::StateCallback(cubeb_state aState)
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
if (aState == CUBEB_STATE_DRAINED) {
|
||||
|
@ -1071,8 +865,6 @@ BufferedAudioStream::StateCallback(cubeb_state aState)
|
|||
mon.NotifyAll();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
AudioClock::AudioClock(AudioStream* aStream)
|
||||
:mAudioStream(aStream),
|
||||
mOldOutRate(0),
|
||||
|
|
|
@ -9,10 +9,20 @@
|
|||
#include "AudioSampleFormat.h"
|
||||
#include "AudioChannelCommon.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsAutoRef.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "Latency.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 {
|
||||
class SoundTouch;
|
||||
}
|
||||
|
@ -23,84 +33,141 @@ class AudioStream;
|
|||
|
||||
class AudioClock
|
||||
{
|
||||
public:
|
||||
AudioClock(mozilla::AudioStream* aStream);
|
||||
// Initialize the clock with the current AudioStream. Need to be called
|
||||
// before querying the clock. Called on the audio thread.
|
||||
void Init();
|
||||
// Update the number of samples that has been written in the audio backend.
|
||||
// Called on the state machine thread.
|
||||
void UpdateWritePosition(uint32_t aCount);
|
||||
// Get the read position of the stream, in microseconds.
|
||||
// Called on the state machine thead.
|
||||
uint64_t GetPosition();
|
||||
// Get the read position of the stream, in frames.
|
||||
// Called on the state machine thead.
|
||||
uint64_t GetPositionInFrames();
|
||||
// Set the playback rate.
|
||||
// Called on the audio thread.
|
||||
void SetPlaybackRate(double aPlaybackRate);
|
||||
// Get the current playback rate.
|
||||
// Called on the audio thread.
|
||||
double GetPlaybackRate();
|
||||
// Set if we are preserving the pitch.
|
||||
// Called on the audio thread.
|
||||
void SetPreservesPitch(bool aPreservesPitch);
|
||||
// Get the current pitch preservation state.
|
||||
// Called on the audio thread.
|
||||
bool GetPreservesPitch();
|
||||
// Get the number of frames written to the backend.
|
||||
int64_t GetWritten();
|
||||
private:
|
||||
// This AudioStream holds a strong reference to this AudioClock. This
|
||||
// pointer is garanteed to always be valid.
|
||||
AudioStream* mAudioStream;
|
||||
// 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
|
||||
// clock should take the new rate into account for A/V sync.
|
||||
int mOldOutRate;
|
||||
// Position at which the last playback rate change occured
|
||||
int64_t mBasePosition;
|
||||
// Offset, in frames, at which the last playback rate change occured
|
||||
int64_t mBaseOffset;
|
||||
// Old base offset (number of samples), used when changing rate to compute the
|
||||
// position in the stream.
|
||||
int64_t mOldBaseOffset;
|
||||
// Old base position (number of microseconds), when changing rate. This is the
|
||||
// time in the media, not wall clock position.
|
||||
int64_t mOldBasePosition;
|
||||
// Write position at which the playbackRate change occured.
|
||||
int64_t mPlaybackRateChangeOffset;
|
||||
// The previous position reached in the media, used when compensating
|
||||
// latency, to have the position at which the playbackRate change occured.
|
||||
int64_t mPreviousPosition;
|
||||
// Number of samples effectivelly written in backend, i.e. write position.
|
||||
int64_t mWritten;
|
||||
// Output rate in Hz (characteristic of the playback rate)
|
||||
int mOutRate;
|
||||
// Input rate in Hz (characteristic of the media being played)
|
||||
int mInRate;
|
||||
// True if the we are timestretching, false if we are resampling.
|
||||
bool mPreservesPitch;
|
||||
// True if we are playing at the old playbackRate after it has been changed.
|
||||
bool mCompensatingLatency;
|
||||
public:
|
||||
AudioClock(AudioStream* aStream);
|
||||
// Initialize the clock with the current AudioStream. Need to be called
|
||||
// before querying the clock. Called on the audio thread.
|
||||
void Init();
|
||||
// Update the number of samples that has been written in the audio backend.
|
||||
// Called on the state machine thread.
|
||||
void UpdateWritePosition(uint32_t aCount);
|
||||
// Get the read position of the stream, in microseconds.
|
||||
// Called on the state machine thead.
|
||||
uint64_t GetPosition();
|
||||
// Get the read position of the stream, in frames.
|
||||
// Called on the state machine thead.
|
||||
uint64_t GetPositionInFrames();
|
||||
// Set the playback rate.
|
||||
// Called on the audio thread.
|
||||
void SetPlaybackRate(double aPlaybackRate);
|
||||
// Get the current playback rate.
|
||||
// Called on the audio thread.
|
||||
double GetPlaybackRate();
|
||||
// Set if we are preserving the pitch.
|
||||
// Called on the audio thread.
|
||||
void SetPreservesPitch(bool aPreservesPitch);
|
||||
// Get the current pitch preservation state.
|
||||
// Called on the audio thread.
|
||||
bool GetPreservesPitch();
|
||||
// Get the number of frames written to the backend.
|
||||
int64_t GetWritten();
|
||||
private:
|
||||
// This AudioStream holds a strong reference to this AudioClock. This
|
||||
// pointer is garanteed to always be valid.
|
||||
AudioStream* mAudioStream;
|
||||
// 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
|
||||
// clock should take the new rate into account for A/V sync.
|
||||
int mOldOutRate;
|
||||
// Position at which the last playback rate change occured
|
||||
int64_t mBasePosition;
|
||||
// Offset, in frames, at which the last playback rate change occured
|
||||
int64_t mBaseOffset;
|
||||
// Old base offset (number of samples), used when changing rate to compute the
|
||||
// position in the stream.
|
||||
int64_t mOldBaseOffset;
|
||||
// Old base position (number of microseconds), when changing rate. This is the
|
||||
// time in the media, not wall clock position.
|
||||
int64_t mOldBasePosition;
|
||||
// Write position at which the playbackRate change occured.
|
||||
int64_t mPlaybackRateChangeOffset;
|
||||
// The previous position reached in the media, used when compensating
|
||||
// latency, to have the position at which the playbackRate change occured.
|
||||
int64_t mPreviousPosition;
|
||||
// Number of samples effectivelly written in backend, i.e. write position.
|
||||
int64_t mWritten;
|
||||
// Output rate in Hz (characteristic of the playback rate)
|
||||
int mOutRate;
|
||||
// Input rate in Hz (characteristic of the media being played)
|
||||
int mInRate;
|
||||
// True if the we are timestretching, false if we are resampling.
|
||||
bool mPreservesPitch;
|
||||
// True if we are playing at the old playbackRate after it has been changed.
|
||||
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
|
||||
// callers, or made from a single thread. One exception is that access to
|
||||
// GetPosition, GetPositionInFrames, SetVolume, and Get{Rate,Channels}
|
||||
// is thread-safe without external synchronization.
|
||||
class AudioStream
|
||||
class AudioStream MOZ_FINAL
|
||||
{
|
||||
public:
|
||||
enum LatencyRequest {
|
||||
HighLatency,
|
||||
LowLatency
|
||||
};
|
||||
AudioStream();
|
||||
|
||||
virtual ~AudioStream();
|
||||
|
||||
// Initialize Audio Library. Some Audio backends require initializing the
|
||||
// library before using it.
|
||||
static void InitLibrary();
|
||||
|
@ -109,11 +176,6 @@ public:
|
|||
// library after using it.
|
||||
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.
|
||||
static int MaxNumberOfChannels();
|
||||
|
||||
|
@ -121,79 +183,124 @@ public:
|
|||
// samplerate the hardware/mixer supports.
|
||||
static int PreferredSampleRate();
|
||||
|
||||
AudioStream();
|
||||
~AudioStream();
|
||||
|
||||
enum LatencyRequest {
|
||||
HighLatency,
|
||||
LowLatency
|
||||
};
|
||||
|
||||
// Initialize the audio stream. aNumChannels is the number of audio
|
||||
// channels (1 for mono, 2 for stereo, etc) and aRate is the sample rate
|
||||
// (22050Hz, 44100Hz, etc).
|
||||
virtual nsresult Init(int32_t aNumChannels, int32_t aRate,
|
||||
const dom::AudioChannelType aAudioStreamType,
|
||||
LatencyRequest aLatencyRequest) = 0;
|
||||
nsresult Init(int32_t aNumChannels, int32_t aRate,
|
||||
const dom::AudioChannelType aAudioStreamType,
|
||||
LatencyRequest aLatencyRequest);
|
||||
|
||||
// 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
|
||||
// AudioDataValue of length aFrames*mChannels. If aFrames is larger
|
||||
// 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
|
||||
// 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.
|
||||
virtual uint32_t Available() = 0;
|
||||
uint32_t Available();
|
||||
|
||||
// Set the current volume of the audio playback. This is a value from
|
||||
// 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.
|
||||
virtual void Drain() = 0;
|
||||
void Drain();
|
||||
|
||||
// Start the stream.
|
||||
virtual void Start() = 0;
|
||||
void Start();
|
||||
|
||||
// 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.
|
||||
virtual int64_t GetWritten();
|
||||
int64_t GetWritten();
|
||||
|
||||
// Pause audio playback.
|
||||
virtual void Pause() = 0;
|
||||
void Pause();
|
||||
|
||||
// Resume audio playback.
|
||||
virtual void Resume() = 0;
|
||||
void Resume();
|
||||
|
||||
// Return the position in microseconds of the audio frame being played by
|
||||
// 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
|
||||
// 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
|
||||
// opened, of the audio hardware, not adjusted for the changes of playback
|
||||
// rate.
|
||||
virtual int64_t GetPositionInFramesInternal() = 0;
|
||||
int64_t GetPositionInFramesInternal();
|
||||
|
||||
// Returns true when the audio stream is paused.
|
||||
virtual bool IsPaused() = 0;
|
||||
bool IsPaused();
|
||||
|
||||
int GetRate() { return mOutRate; }
|
||||
int GetChannels() { return mChannels; }
|
||||
|
||||
// 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
|
||||
// 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).
|
||||
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);
|
||||
|
||||
nsresult EnsureTimeStretcherInitializedUnlocked();
|
||||
|
||||
// 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)
|
||||
int mInRate;
|
||||
// Output rate in Hz (characteristic of the playback rate)
|
||||
|
@ -218,7 +325,65 @@ protected:
|
|||
int64_t mTimeMs;
|
||||
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
|
||||
|
|
|
@ -1056,7 +1056,7 @@ void MediaDecoderStateMachine::AudioLoop()
|
|||
// AudioStream initialization can block for extended periods in unusual
|
||||
// circumstances, so we take care to drop the decoder monitor while
|
||||
// initializing.
|
||||
nsAutoPtr<AudioStream> audioStream(AudioStream::AllocateStream());
|
||||
nsAutoPtr<AudioStream> audioStream(new AudioStream());
|
||||
audioStream->Init(channels, rate, audioChannelType, AudioStream::HighLatency);
|
||||
audioStream->SetVolume(volume);
|
||||
if (audioStream->SetPreservesPitch(preservesPitch) != NS_OK) {
|
||||
|
|
|
@ -778,7 +778,7 @@ MediaStreamGraphImpl::CreateOrDestroyAudioStreams(GraphTime aAudioOutputStartTim
|
|||
aStream->mAudioOutputStreams.AppendElement();
|
||||
audioOutputStream->mAudioPlaybackStartTime = aAudioOutputStartTime;
|
||||
audioOutputStream->mBlockedAudioTime = 0;
|
||||
audioOutputStream->mStream = AudioStream::AllocateStream();
|
||||
audioOutputStream->mStream = new AudioStream();
|
||||
// XXX for now, allocate stereo output. But we need to fix this to
|
||||
// match the system's ideal channel configuration.
|
||||
audioOutputStream->mStream->Init(2, tracks->GetRate(), AUDIO_CHANNEL_NORMAL, AudioStream::LowLatency);
|
||||
|
|
|
@ -17,10 +17,5 @@ ifeq (WINNT,$(OS_TARGET))
|
|||
symbols.def: symbols.def.in $(GLOBAL_DEPS)
|
||||
$(call py_action,preprocessor,$(ACDEFINES) $< -o $@)
|
||||
|
||||
OS_LIBS += $(call EXPAND_LIBNAME, msimg32)
|
||||
|
||||
ifdef MOZ_CUBEB
|
||||
OS_LIBS += $(call EXPAND_LIBNAME, winmm)
|
||||
endif
|
||||
|
||||
OS_LIBS += $(call EXPAND_LIBNAME, msimg32 winmm)
|
||||
endif
|
||||
|
|
|
@ -117,7 +117,6 @@ speex_resampler_get_output_latency
|
|||
speex_resampler_skip_zeros
|
||||
speex_resampler_reset_mem
|
||||
speex_resampler_strerror
|
||||
#ifdef MOZ_CUBEB
|
||||
cubeb_destroy
|
||||
cubeb_init
|
||||
cubeb_get_max_channel_count
|
||||
|
@ -129,7 +128,6 @@ cubeb_stream_init
|
|||
cubeb_stream_start
|
||||
cubeb_stream_stop
|
||||
cubeb_stream_get_latency
|
||||
#endif
|
||||
#ifdef MOZ_OGG
|
||||
th_comment_clear
|
||||
th_comment_init
|
||||
|
|
|
@ -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 git commit ID used was 8c78a282aa0320e997436d6832024efe1527ca1c.
|
||||
The git commit ID used was e92a27c96c0efd33acf983e4c873376ff4cae3d8.
|
||||
|
|
|
@ -159,7 +159,7 @@ cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels)
|
|||
int
|
||||
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 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
|
||||
cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
|
||||
{
|
||||
if (!rate) {
|
||||
if (!context || !rate) {
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
return context->ops->get_preferred_sample_rate(context, rate);
|
||||
|
|
|
@ -17,13 +17,11 @@ LIBS = \
|
|||
$(DEPTH)/netwerk/srtp/src/$(LIB_PREFIX)nksrtp_s.$(LIB_SUFFIX) \
|
||||
$(NULL)
|
||||
|
||||
ifdef MOZ_CUBEB
|
||||
ifdef MOZ_ALSA
|
||||
LIBS += \
|
||||
$(MOZ_ALSA_LIBS) \
|
||||
$(NULL)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(OS_TARGET),Android)
|
||||
LIBS += \
|
||||
|
|
|
@ -180,11 +180,9 @@ endif
|
|||
endif
|
||||
|
||||
|
||||
ifdef MOZ_CUBEB
|
||||
ifdef MOZ_ALSA
|
||||
EXTRA_DSO_LDOPTS += $(MOZ_ALSA_LIBS)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef HAVE_CLOCK_MONOTONIC
|
||||
EXTRA_DSO_LDOPTS += $(REALTIME_LIBS)
|
||||
|
@ -247,10 +245,8 @@ OS_LIBS += \
|
|||
endif
|
||||
|
||||
ifeq (OpenBSD,$(OS_ARCH))
|
||||
ifdef MOZ_CUBEB
|
||||
EXTRA_DSO_LDOPTS += -lsndio
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef MOZ_ENABLE_DBUS
|
||||
EXTRA_DSO_LDOPTS += $(MOZ_DBUS_GLIB_LIBS)
|
||||
|
|
Загрузка…
Ссылка в новой задаче