2008-07-29 23:50:14 -07:00
|
|
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
2012-05-21 04:12:37 -07:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
2008-07-29 23:50:14 -07:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include "prlog.h"
|
2011-05-18 14:12:25 -07:00
|
|
|
#include "prdtoa.h"
|
2012-11-14 11:46:40 -08:00
|
|
|
#include "AudioStream.h"
|
2008-11-10 14:45:09 -08:00
|
|
|
#include "nsAlgorithm.h"
|
2011-04-13 15:12:23 -07:00
|
|
|
#include "VideoUtils.h"
|
2012-11-18 16:54:29 -08:00
|
|
|
#include "mozilla/Monitor.h"
|
2011-05-18 14:12:25 -07:00
|
|
|
#include "mozilla/Mutex.h"
|
2013-01-15 04:22:03 -08:00
|
|
|
#include <algorithm>
|
2008-07-29 23:50:14 -07:00
|
|
|
extern "C" {
|
2008-10-15 20:16:29 -07:00
|
|
|
#include "sydneyaudio/sydney_audio.h"
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2011-05-28 00:03:00 -07:00
|
|
|
#include "mozilla/Preferences.h"
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
#if defined(MOZ_CUBEB)
|
|
|
|
#include "nsAutoRef.h"
|
|
|
|
#include "cubeb/cubeb.h"
|
2012-11-14 11:45:33 -08:00
|
|
|
|
|
|
|
template <>
|
|
|
|
class nsAutoRefTraits<cubeb_stream> : public nsPointerRefTraits<cubeb_stream>
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
static void Release(cubeb_stream* aStream) { cubeb_stream_destroy(aStream); }
|
|
|
|
};
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
#endif
|
|
|
|
|
2012-11-14 11:45:33 -08:00
|
|
|
namespace mozilla {
|
2010-04-01 20:03:07 -07:00
|
|
|
|
2010-08-11 18:52:36 -07:00
|
|
|
#if defined(XP_MACOSX)
|
|
|
|
#define SA_PER_STREAM_VOLUME 1
|
|
|
|
#endif
|
|
|
|
|
2008-07-29 23:50:14 -07:00
|
|
|
#ifdef PR_LOGGING
|
2012-07-30 07:20:58 -07:00
|
|
|
PRLogModuleInfo* gAudioStreamLog = nullptr;
|
2008-07-29 23:50:14 -07:00
|
|
|
#endif
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
static const uint32_t FAKE_BUFFER_SIZE = 176400;
|
2008-11-06 12:53:20 -08:00
|
|
|
|
2011-10-18 22:29:08 -07:00
|
|
|
// Number of milliseconds per second.
|
2012-08-22 08:56:38 -07:00
|
|
|
static const int64_t MS_PER_S = 1000;
|
2011-10-18 22:29:08 -07:00
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
class NativeAudioStream : public AudioStream
|
2010-11-16 20:14:19 -08:00
|
|
|
{
|
|
|
|
public:
|
2012-11-28 11:40:07 -08:00
|
|
|
~NativeAudioStream();
|
|
|
|
NativeAudioStream();
|
2010-12-19 11:05:40 -08:00
|
|
|
|
2012-11-15 19:25:26 -08:00
|
|
|
nsresult Init(int32_t aNumChannels, int32_t aRate,
|
2012-11-18 16:54:29 -08:00
|
|
|
const dom::AudioChannelType aAudioChannelType);
|
2010-11-16 20:14:19 -08:00
|
|
|
void Shutdown();
|
2012-10-25 03:10:51 -07:00
|
|
|
nsresult Write(const AudioDataValue* aBuf, uint32_t aFrames);
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t Available();
|
2011-01-16 19:03:00 -08:00
|
|
|
void SetVolume(double aVolume);
|
2010-11-16 20:14:19 -08:00
|
|
|
void Drain();
|
|
|
|
void Pause();
|
|
|
|
void Resume();
|
2012-08-22 08:56:38 -07:00
|
|
|
int64_t GetPosition();
|
|
|
|
int64_t GetPositionInFrames();
|
2012-11-22 02:38:28 -08:00
|
|
|
int64_t GetPositionInFramesInternal();
|
2011-09-28 23:19:26 -07:00
|
|
|
bool IsPaused();
|
2012-08-22 08:56:38 -07:00
|
|
|
int32_t GetMinWriteSize();
|
2010-11-16 20:14:19 -08:00
|
|
|
|
|
|
|
private:
|
2012-11-22 02:38:28 -08:00
|
|
|
int32_t WriteToBackend(const float* aBuffer, uint32_t aFrames);
|
|
|
|
int32_t WriteToBackend(const short* aBuffer, uint32_t aFrames);
|
2010-11-16 20:14:19 -08:00
|
|
|
|
|
|
|
double mVolume;
|
|
|
|
void* mAudioHandle;
|
|
|
|
|
2011-09-29 16:34:37 -07:00
|
|
|
// True if this audio stream is paused.
|
2011-09-28 23:19:26 -07:00
|
|
|
bool mPaused;
|
2010-11-16 20:14:19 -08:00
|
|
|
|
2011-09-29 16:34:37 -07:00
|
|
|
// True if this stream has encountered an error.
|
2011-09-28 23:19:26 -07:00
|
|
|
bool mInError;
|
2010-11-16 20:14:19 -08:00
|
|
|
|
|
|
|
};
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
#define PREF_VOLUME_SCALE "media.volume_scale"
|
|
|
|
#define PREF_USE_CUBEB "media.use_cubeb"
|
2012-05-02 21:48:54 -07:00
|
|
|
#define PREF_CUBEB_LATENCY "media.cubeb_latency_ms"
|
2011-05-18 14:12:25 -07:00
|
|
|
|
2012-11-14 11:46:40 -08:00
|
|
|
static Mutex* gAudioPrefsLock = nullptr;
|
2012-06-16 19:13:22 -07:00
|
|
|
static double gVolumeScale;
|
|
|
|
static bool gUseCubeb;
|
2012-08-22 08:56:38 -07:00
|
|
|
static uint32_t gCubebLatency;
|
2012-05-02 21:48:54 -07:00
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
static int PrefChanged(const char* aPref, void* aClosure)
|
|
|
|
{
|
|
|
|
if (strcmp(aPref, PREF_VOLUME_SCALE) == 0) {
|
|
|
|
nsAdoptingString value = Preferences::GetString(aPref);
|
2012-11-14 11:46:40 -08:00
|
|
|
MutexAutoLock lock(*gAudioPrefsLock);
|
2012-01-12 13:20:36 -08:00
|
|
|
if (value.IsEmpty()) {
|
|
|
|
gVolumeScale = 1.0;
|
|
|
|
} else {
|
|
|
|
NS_ConvertUTF16toUTF8 utf8(value);
|
2013-01-15 04:22:03 -08:00
|
|
|
gVolumeScale = std::max<double>(0, PR_strtod(utf8.get(), nullptr));
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
} else if (strcmp(aPref, PREF_USE_CUBEB) == 0) {
|
2012-09-04 13:45:08 -07:00
|
|
|
#ifdef MOZ_WIDGET_GONK
|
|
|
|
bool value = Preferences::GetBool(aPref, false);
|
|
|
|
#else
|
2012-04-15 20:00:40 -07:00
|
|
|
bool value = Preferences::GetBool(aPref, true);
|
2012-09-04 13:45:08 -07:00
|
|
|
#endif
|
2012-11-14 11:46:40 -08:00
|
|
|
MutexAutoLock lock(*gAudioPrefsLock);
|
2012-01-12 13:20:36 -08:00
|
|
|
gUseCubeb = value;
|
2012-05-02 21:48:54 -07:00
|
|
|
} else if (strcmp(aPref, PREF_CUBEB_LATENCY) == 0) {
|
2012-06-16 19:13:22 -07:00
|
|
|
// Arbitrary default stream latency of 100ms. The higher this
|
|
|
|
// value, the longer stream volume changes will take to become
|
|
|
|
// audible.
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t value = Preferences::GetUint(aPref, 100);
|
2012-11-14 11:46:40 -08:00
|
|
|
MutexAutoLock lock(*gAudioPrefsLock);
|
2013-01-15 04:22:03 -08:00
|
|
|
gCubebLatency = std::min<uint32_t>(std::max<uint32_t>(value, 20), 1000);
|
2011-05-18 14:12:25 -07:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
static double GetVolumeScale()
|
|
|
|
{
|
2012-11-14 11:46:40 -08:00
|
|
|
MutexAutoLock lock(*gAudioPrefsLock);
|
2011-05-18 14:12:25 -07:00
|
|
|
return gVolumeScale;
|
|
|
|
}
|
2010-11-16 20:14:19 -08:00
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
#if defined(MOZ_CUBEB)
|
|
|
|
static bool GetUseCubeb()
|
|
|
|
{
|
2012-11-14 11:46:40 -08:00
|
|
|
MutexAutoLock lock(*gAudioPrefsLock);
|
2012-01-12 13:20:36 -08:00
|
|
|
return gUseCubeb;
|
|
|
|
}
|
2012-04-15 20:00:12 -07:00
|
|
|
|
|
|
|
static cubeb* gCubebContext;
|
|
|
|
|
|
|
|
static cubeb* GetCubebContext()
|
|
|
|
{
|
2012-11-14 11:46:40 -08:00
|
|
|
MutexAutoLock lock(*gAudioPrefsLock);
|
2012-04-15 20:00:12 -07:00
|
|
|
if (gCubebContext ||
|
2012-11-14 11:46:40 -08:00
|
|
|
cubeb_init(&gCubebContext, "AudioStream") == CUBEB_OK) {
|
2012-04-15 20:00:12 -07:00
|
|
|
return gCubebContext;
|
|
|
|
}
|
|
|
|
NS_WARNING("cubeb_init failed");
|
2012-07-30 07:20:58 -07:00
|
|
|
return nullptr;
|
2012-04-15 20:00:12 -07:00
|
|
|
}
|
2012-05-02 21:48:54 -07:00
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
static uint32_t GetCubebLatency()
|
2012-05-02 21:48:54 -07:00
|
|
|
{
|
2012-11-14 11:46:40 -08:00
|
|
|
MutexAutoLock lock(*gAudioPrefsLock);
|
2012-05-02 21:48:54 -07:00
|
|
|
return gCubebLatency;
|
|
|
|
}
|
2012-01-12 13:20:36 -08:00
|
|
|
#endif
|
|
|
|
|
2012-11-18 16:54:29 -08:00
|
|
|
static sa_stream_type_t ConvertChannelToSAType(dom::AudioChannelType aType)
|
2012-11-15 19:25:26 -08:00
|
|
|
{
|
|
|
|
switch(aType) {
|
2012-11-18 16:54:29 -08:00
|
|
|
case dom::AUDIO_CHANNEL_NORMAL:
|
2012-11-15 19:25:26 -08:00
|
|
|
return SA_STREAM_TYPE_SYSTEM;
|
2012-11-18 16:54:29 -08:00
|
|
|
case dom::AUDIO_CHANNEL_CONTENT:
|
2012-11-15 19:25:26 -08:00
|
|
|
return SA_STREAM_TYPE_MUSIC;
|
2012-11-18 16:54:29 -08:00
|
|
|
case dom::AUDIO_CHANNEL_NOTIFICATION:
|
2012-11-15 19:25:26 -08:00
|
|
|
return SA_STREAM_TYPE_NOTIFICATION;
|
2012-11-18 16:54:29 -08:00
|
|
|
case dom::AUDIO_CHANNEL_ALARM:
|
2012-11-15 19:25:26 -08:00
|
|
|
return SA_STREAM_TYPE_ALARM;
|
2012-11-18 16:54:29 -08:00
|
|
|
case dom::AUDIO_CHANNEL_TELEPHONY:
|
2012-11-15 19:25:26 -08:00
|
|
|
return SA_STREAM_TYPE_VOICE_CALL;
|
2012-12-06 01:11:19 -08:00
|
|
|
case dom::AUDIO_CHANNEL_RINGER:
|
|
|
|
return SA_STREAM_TYPE_RING;
|
2012-11-18 16:54:29 -08:00
|
|
|
case dom::AUDIO_CHANNEL_PUBLICNOTIFICATION:
|
2012-11-15 19:25:26 -08:00
|
|
|
return SA_STREAM_TYPE_ENFORCED_AUDIBLE;
|
|
|
|
default:
|
|
|
|
NS_ERROR("The value of AudioChannelType is invalid");
|
|
|
|
return SA_STREAM_TYPE_MAX;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
AudioStream::AudioStream()
|
|
|
|
: mInRate(0),
|
|
|
|
mOutRate(0),
|
|
|
|
mChannels(0),
|
|
|
|
mAudioClock(this)
|
|
|
|
{}
|
|
|
|
|
2012-11-14 11:46:40 -08:00
|
|
|
void AudioStream::InitLibrary()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
|
|
|
#ifdef PR_LOGGING
|
2012-11-14 11:46:40 -08:00
|
|
|
gAudioStreamLog = PR_NewLogModule("AudioStream");
|
2008-07-29 23:50:14 -07:00
|
|
|
#endif
|
2012-11-14 11:46:40 -08:00
|
|
|
gAudioPrefsLock = new Mutex("AudioStream::gAudioPrefsLock");
|
2012-07-30 07:20:58 -07:00
|
|
|
PrefChanged(PREF_VOLUME_SCALE, nullptr);
|
2012-01-12 13:20:36 -08:00
|
|
|
Preferences::RegisterCallback(PrefChanged, PREF_VOLUME_SCALE);
|
|
|
|
#if defined(MOZ_CUBEB)
|
2012-07-30 07:20:58 -07:00
|
|
|
PrefChanged(PREF_USE_CUBEB, nullptr);
|
2012-01-12 13:20:36 -08:00
|
|
|
Preferences::RegisterCallback(PrefChanged, PREF_USE_CUBEB);
|
2012-07-30 07:20:58 -07:00
|
|
|
PrefChanged(PREF_CUBEB_LATENCY, nullptr);
|
2012-06-16 19:13:22 -07:00
|
|
|
Preferences::RegisterCallback(PrefChanged, PREF_CUBEB_LATENCY);
|
2012-01-12 13:20:36 -08:00
|
|
|
#endif
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2012-11-14 11:46:40 -08:00
|
|
|
void AudioStream::ShutdownLibrary()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2012-01-12 13:20:36 -08:00
|
|
|
Preferences::UnregisterCallback(PrefChanged, PREF_VOLUME_SCALE);
|
|
|
|
#if defined(MOZ_CUBEB)
|
|
|
|
Preferences::UnregisterCallback(PrefChanged, PREF_USE_CUBEB);
|
|
|
|
#endif
|
|
|
|
delete gAudioPrefsLock;
|
2012-07-30 07:20:58 -07:00
|
|
|
gAudioPrefsLock = nullptr;
|
2012-01-12 13:20:36 -08:00
|
|
|
|
|
|
|
#if defined(MOZ_CUBEB)
|
|
|
|
if (gCubebContext) {
|
|
|
|
cubeb_destroy(gCubebContext);
|
2012-07-30 07:20:58 -07:00
|
|
|
gCubebContext = nullptr;
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
#endif
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2012-11-14 11:46:40 -08:00
|
|
|
AudioStream::~AudioStream()
|
2011-01-03 19:55:32 -08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2012-11-29 06:40:57 -08:00
|
|
|
void AudioStream::EnsureTimeStretcherInitialized()
|
2012-11-22 02:38:28 -08:00
|
|
|
{
|
2012-11-28 08:25:57 -08:00
|
|
|
if (!mTimeStretcher) {
|
2012-11-29 06:40:57 -08:00
|
|
|
mTimeStretcher = new soundtouch::SoundTouch();
|
|
|
|
mTimeStretcher->setSampleRate(mInRate);
|
|
|
|
mTimeStretcher->setChannels(mChannels);
|
|
|
|
mTimeStretcher->setPitch(1.0);
|
2012-11-22 02:38:28 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult AudioStream::SetPlaybackRate(double aPlaybackRate)
|
|
|
|
{
|
|
|
|
NS_ASSERTION(aPlaybackRate > 0.0,
|
|
|
|
"Can't handle negative or null playbackrate in the AudioStream.");
|
|
|
|
// Avoid instantiating the resampler if we are not changing the playback rate.
|
|
|
|
if (aPlaybackRate == mAudioClock.GetPlaybackRate()) {
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
mAudioClock.SetPlaybackRate(aPlaybackRate);
|
|
|
|
mOutRate = mInRate / aPlaybackRate;
|
2012-11-29 06:40:57 -08:00
|
|
|
|
|
|
|
EnsureTimeStretcherInitialized();
|
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
if (mAudioClock.GetPreservesPitch()) {
|
|
|
|
mTimeStretcher->setTempo(aPlaybackRate);
|
|
|
|
mTimeStretcher->setRate(1.0f);
|
|
|
|
} else {
|
|
|
|
mTimeStretcher->setTempo(1.0f);
|
|
|
|
mTimeStretcher->setRate(aPlaybackRate);
|
|
|
|
}
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult AudioStream::SetPreservesPitch(bool aPreservesPitch)
|
|
|
|
{
|
|
|
|
// Avoid instantiating the timestretcher instance if not needed.
|
|
|
|
if (aPreservesPitch == mAudioClock.GetPreservesPitch()) {
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2012-11-29 06:40:57 -08:00
|
|
|
|
|
|
|
EnsureTimeStretcherInitialized();
|
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
if (aPreservesPitch == true) {
|
|
|
|
mTimeStretcher->setTempo(mAudioClock.GetPlaybackRate());
|
|
|
|
mTimeStretcher->setRate(1.0f);
|
|
|
|
} else {
|
|
|
|
mTimeStretcher->setTempo(1.0f);
|
|
|
|
mTimeStretcher->setRate(mAudioClock.GetPlaybackRate());
|
|
|
|
}
|
|
|
|
|
|
|
|
mAudioClock.SetPreservesPitch(aPreservesPitch);
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
NativeAudioStream::NativeAudioStream() :
|
2008-07-29 23:50:14 -07:00
|
|
|
mVolume(1.0),
|
|
|
|
mAudioHandle(0),
|
2011-09-29 16:34:37 -07:00
|
|
|
mPaused(false),
|
|
|
|
mInError(false)
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
NativeAudioStream::~NativeAudioStream()
|
2010-11-22 14:08:49 -08:00
|
|
|
{
|
|
|
|
Shutdown();
|
|
|
|
}
|
2009-03-24 19:19:58 -07:00
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
nsresult NativeAudioStream::Init(int32_t aNumChannels, int32_t aRate,
|
2012-11-18 16:54:29 -08:00
|
|
|
const dom::AudioChannelType aAudioChannelType)
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2012-11-22 02:38:28 -08:00
|
|
|
mInRate = mOutRate = aRate;
|
2008-07-29 23:50:14 -07:00
|
|
|
mChannels = aNumChannels;
|
2010-11-16 20:14:19 -08:00
|
|
|
|
2008-07-29 23:50:14 -07:00
|
|
|
if (sa_stream_create_pcm(reinterpret_cast<sa_stream_t**>(&mAudioHandle),
|
2011-09-26 20:31:18 -07:00
|
|
|
NULL,
|
|
|
|
SA_MODE_WRONLY,
|
2009-01-29 23:27:43 -08:00
|
|
|
SA_PCM_FORMAT_S16_NE,
|
2008-07-29 23:50:14 -07:00
|
|
|
aRate,
|
|
|
|
aNumChannels) != SA_SUCCESS) {
|
2012-07-30 07:20:58 -07:00
|
|
|
mAudioHandle = nullptr;
|
2011-09-29 16:34:37 -07:00
|
|
|
mInError = true;
|
2012-11-28 11:40:07 -08:00
|
|
|
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_create_pcm error"));
|
2010-08-25 06:10:00 -07:00
|
|
|
return NS_ERROR_FAILURE;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2011-09-26 20:31:18 -07:00
|
|
|
|
2012-11-15 19:25:26 -08:00
|
|
|
int saError = sa_stream_set_stream_type(static_cast<sa_stream_t*>(mAudioHandle),
|
|
|
|
ConvertChannelToSAType(aAudioChannelType));
|
|
|
|
if (saError != SA_SUCCESS && saError != SA_ERROR_NOT_SUPPORTED) {
|
|
|
|
mAudioHandle = nullptr;
|
|
|
|
mInError = true;
|
2012-11-28 11:40:07 -08:00
|
|
|
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_set_stream_type error"));
|
2012-11-15 19:25:26 -08:00
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
|
|
|
|
2008-11-10 00:12:13 -08:00
|
|
|
if (sa_stream_open(static_cast<sa_stream_t*>(mAudioHandle)) != SA_SUCCESS) {
|
|
|
|
sa_stream_destroy(static_cast<sa_stream_t*>(mAudioHandle));
|
2012-07-30 07:20:58 -07:00
|
|
|
mAudioHandle = nullptr;
|
2011-09-29 16:34:37 -07:00
|
|
|
mInError = true;
|
2012-11-28 11:40:07 -08:00
|
|
|
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_open error"));
|
2010-08-25 06:10:00 -07:00
|
|
|
return NS_ERROR_FAILURE;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2011-09-29 16:34:37 -07:00
|
|
|
mInError = false;
|
2010-11-16 20:14:19 -08:00
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
mAudioClock.Init();
|
|
|
|
|
2010-08-25 06:10:00 -07:00
|
|
|
return NS_OK;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
void NativeAudioStream::Shutdown()
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2010-11-16 20:14:19 -08:00
|
|
|
if (!mAudioHandle)
|
2008-10-28 09:17:59 -07:00
|
|
|
return;
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2008-11-10 00:12:13 -08:00
|
|
|
sa_stream_destroy(static_cast<sa_stream_t*>(mAudioHandle));
|
2012-07-30 07:20:58 -07:00
|
|
|
mAudioHandle = nullptr;
|
2011-09-29 16:34:37 -07:00
|
|
|
mInError = true;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
int32_t NativeAudioStream::WriteToBackend(const AudioDataValue* aBuffer, uint32_t aSamples)
|
2012-11-22 02:38:28 -08:00
|
|
|
{
|
|
|
|
double scaledVolume = GetVolumeScale() * mVolume;
|
|
|
|
|
|
|
|
nsAutoArrayPtr<short> outputBuffer(new short[aSamples]);
|
|
|
|
ConvertAudioSamplesWithScale(aBuffer, outputBuffer.get(), aSamples, scaledVolume);
|
|
|
|
|
|
|
|
if (sa_stream_write(static_cast<sa_stream_t*>(mAudioHandle),
|
|
|
|
outputBuffer,
|
|
|
|
aSamples * sizeof(short)) != SA_SUCCESS) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
mAudioClock.UpdateWritePosition(aSamples / mChannels);
|
|
|
|
return aSamples;
|
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
nsresult NativeAudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames)
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2010-04-01 20:03:07 -07:00
|
|
|
NS_ASSERTION(!mPaused, "Don't write audio when paused, you'll block");
|
2008-11-10 00:12:13 -08:00
|
|
|
|
2010-08-23 19:06:14 -07:00
|
|
|
if (mInError)
|
2010-08-25 06:10:00 -07:00
|
|
|
return NS_ERROR_FAILURE;
|
2010-08-23 19:06:14 -07:00
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t samples = aFrames * mChannels;
|
2012-11-22 02:38:28 -08:00
|
|
|
int32_t written = -1;
|
2008-07-29 23:50:14 -07:00
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
if (mInRate != mOutRate) {
|
2012-11-29 06:40:57 -08:00
|
|
|
EnsureTimeStretcherInitialized();
|
2012-11-22 02:38:28 -08:00
|
|
|
mTimeStretcher->putSamples(aBuf, aFrames);
|
|
|
|
uint32_t numFrames = mTimeStretcher->numSamples();
|
|
|
|
uint32_t arraySize = numFrames * mChannels * sizeof(AudioDataValue);
|
|
|
|
nsAutoArrayPtr<AudioDataValue> data(new AudioDataValue[arraySize]);
|
|
|
|
uint32_t framesAvailable = mTimeStretcher->receiveSamples(data, numFrames);
|
|
|
|
NS_ASSERTION(mTimeStretcher->numSamples() == 0,
|
|
|
|
"We did not get all the data from the SoundTouch pipeline.");
|
|
|
|
// It is possible to have nothing to write: the data are in the processing
|
|
|
|
// pipeline, and will be written to the backend next time.
|
|
|
|
if (framesAvailable) {
|
|
|
|
written = WriteToBackend(data, framesAvailable * mChannels);
|
|
|
|
} else {
|
|
|
|
written = 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
written = WriteToBackend(aBuf, samples);
|
|
|
|
}
|
2008-12-15 15:06:22 -08:00
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
if (written == -1) {
|
2012-11-28 11:40:07 -08:00
|
|
|
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_write error"));
|
2012-08-16 18:10:36 -07:00
|
|
|
mInError = true;
|
|
|
|
return NS_ERROR_FAILURE;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2010-08-25 06:10:00 -07:00
|
|
|
return NS_OK;
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
uint32_t NativeAudioStream::Available()
|
2008-10-19 00:39:21 -07:00
|
|
|
{
|
2008-11-06 12:53:20 -08:00
|
|
|
// If the audio backend failed to open, lie and say we'll accept some
|
|
|
|
// data.
|
2010-08-23 19:06:14 -07:00
|
|
|
if (mInError)
|
2008-11-06 12:53:20 -08:00
|
|
|
return FAKE_BUFFER_SIZE;
|
2008-10-19 00:39:21 -07:00
|
|
|
|
2011-09-26 20:31:18 -07:00
|
|
|
size_t s = 0;
|
2009-05-14 18:29:05 -07:00
|
|
|
if (sa_stream_get_write_size(static_cast<sa_stream_t*>(mAudioHandle), &s) != SA_SUCCESS)
|
|
|
|
return 0;
|
|
|
|
|
2011-09-26 20:31:18 -07:00
|
|
|
return s / mChannels / sizeof(short);
|
2008-10-19 00:39:21 -07:00
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
void NativeAudioStream::SetVolume(double aVolume)
|
2008-07-29 23:50:14 -07:00
|
|
|
{
|
2008-11-10 00:12:13 -08:00
|
|
|
NS_ASSERTION(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
|
2010-08-11 18:52:36 -07:00
|
|
|
#if defined(SA_PER_STREAM_VOLUME)
|
|
|
|
if (sa_stream_set_volume_abs(static_cast<sa_stream_t*>(mAudioHandle), aVolume) != SA_SUCCESS) {
|
2012-11-28 11:40:07 -08:00
|
|
|
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_set_volume_abs error"));
|
2011-09-29 16:34:37 -07:00
|
|
|
mInError = true;
|
2010-08-11 18:52:36 -07:00
|
|
|
}
|
|
|
|
#else
|
2008-07-29 23:50:14 -07:00
|
|
|
mVolume = aVolume;
|
2010-08-11 18:52:36 -07:00
|
|
|
#endif
|
2008-07-29 23:50:14 -07:00
|
|
|
}
|
2008-11-06 12:53:20 -08:00
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
void NativeAudioStream::Drain()
|
2008-11-06 12:53:20 -08:00
|
|
|
{
|
2011-02-02 15:44:40 -08:00
|
|
|
NS_ASSERTION(!mPaused, "Don't drain audio when paused, it won't finish!");
|
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
// Write all the frames still in the time stretcher pipeline.
|
|
|
|
if (mTimeStretcher) {
|
|
|
|
uint32_t numFrames = mTimeStretcher->numSamples();
|
|
|
|
uint32_t arraySize = numFrames * mChannels * sizeof(AudioDataValue);
|
|
|
|
nsAutoArrayPtr<AudioDataValue> data(new AudioDataValue[arraySize]);
|
|
|
|
uint32_t framesAvailable = mTimeStretcher->receiveSamples(data, numFrames);
|
|
|
|
int32_t written = 0;
|
|
|
|
if (framesAvailable) {
|
|
|
|
written = WriteToBackend(data, framesAvailable * mChannels);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (written == -1) {
|
2012-11-28 11:40:07 -08:00
|
|
|
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_write error"));
|
2012-11-22 02:38:28 -08:00
|
|
|
mInError = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_ASSERTION(mTimeStretcher->numSamples() == 0,
|
|
|
|
"We did not get all the data from the SoundTouch pipeline.");
|
|
|
|
}
|
|
|
|
|
2010-08-23 19:06:14 -07:00
|
|
|
if (mInError)
|
2008-11-06 12:53:20 -08:00
|
|
|
return;
|
|
|
|
|
2010-04-11 15:26:34 -07:00
|
|
|
int r = sa_stream_drain(static_cast<sa_stream_t*>(mAudioHandle));
|
|
|
|
if (r != SA_SUCCESS && r != SA_ERROR_INVALID) {
|
2012-11-28 11:40:07 -08:00
|
|
|
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_drain error"));
|
2011-09-29 16:34:37 -07:00
|
|
|
mInError = true;
|
2008-11-06 12:53:20 -08:00
|
|
|
}
|
|
|
|
}
|
2009-05-14 18:29:05 -07:00
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
void NativeAudioStream::Pause()
|
2009-05-14 18:29:05 -07:00
|
|
|
{
|
2010-08-23 19:06:14 -07:00
|
|
|
if (mInError)
|
2009-05-14 18:29:05 -07:00
|
|
|
return;
|
2011-09-29 16:34:37 -07:00
|
|
|
mPaused = true;
|
2009-05-14 18:29:05 -07:00
|
|
|
sa_stream_pause(static_cast<sa_stream_t*>(mAudioHandle));
|
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
void NativeAudioStream::Resume()
|
2009-05-14 18:29:05 -07:00
|
|
|
{
|
2010-08-23 19:06:14 -07:00
|
|
|
if (mInError)
|
2009-05-14 18:29:05 -07:00
|
|
|
return;
|
2011-09-29 16:34:37 -07:00
|
|
|
mPaused = false;
|
2009-05-14 18:29:05 -07:00
|
|
|
sa_stream_resume(static_cast<sa_stream_t*>(mAudioHandle));
|
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
int64_t NativeAudioStream::GetPosition()
|
2009-05-14 18:29:05 -07:00
|
|
|
{
|
2012-11-22 02:38:28 -08:00
|
|
|
return mAudioClock.GetPosition();
|
2010-08-25 06:10:00 -07:00
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
int64_t NativeAudioStream::GetPositionInFrames()
|
2012-11-22 02:38:28 -08:00
|
|
|
{
|
|
|
|
return mAudioClock.GetPositionInFrames();
|
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
int64_t NativeAudioStream::GetPositionInFramesInternal()
|
2010-08-25 06:10:00 -07:00
|
|
|
{
|
|
|
|
if (mInError) {
|
|
|
|
return -1;
|
|
|
|
}
|
2011-09-26 20:31:18 -07:00
|
|
|
|
2009-06-02 15:32:11 -07:00
|
|
|
sa_position_t positionType = SA_POSITION_WRITE_SOFTWARE;
|
|
|
|
#if defined(XP_WIN)
|
|
|
|
positionType = SA_POSITION_WRITE_HARDWARE;
|
|
|
|
#endif
|
2011-04-21 16:16:48 -07:00
|
|
|
int64_t position = 0;
|
2009-05-14 18:29:05 -07:00
|
|
|
if (sa_stream_get_position(static_cast<sa_stream_t*>(mAudioHandle),
|
2009-06-02 15:32:11 -07:00
|
|
|
positionType, &position) == SA_SUCCESS) {
|
2011-09-26 20:31:18 -07:00
|
|
|
return position / mChannels / sizeof(short);
|
2009-05-14 18:29:05 -07:00
|
|
|
}
|
|
|
|
|
2010-04-01 20:03:07 -07:00
|
|
|
return -1;
|
2009-05-14 18:29:05 -07:00
|
|
|
}
|
2010-11-16 20:14:19 -08:00
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
bool NativeAudioStream::IsPaused()
|
2010-11-16 20:14:19 -08:00
|
|
|
{
|
|
|
|
return mPaused;
|
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
int32_t NativeAudioStream::GetMinWriteSize()
|
2010-12-19 11:05:40 -08:00
|
|
|
{
|
2011-08-15 20:47:45 -07:00
|
|
|
size_t size;
|
2010-12-19 11:05:40 -08:00
|
|
|
int r = sa_stream_get_min_write(static_cast<sa_stream_t*>(mAudioHandle),
|
2011-08-15 20:47:45 -07:00
|
|
|
&size);
|
2011-09-26 20:31:18 -07:00
|
|
|
if (r == SA_ERROR_NOT_SUPPORTED)
|
2010-12-19 11:05:40 -08:00
|
|
|
return 1;
|
2012-09-27 23:57:33 -07:00
|
|
|
else if (r != SA_SUCCESS || size > INT32_MAX)
|
2010-12-19 11:05:40 -08:00
|
|
|
return -1;
|
2011-09-26 20:31:18 -07:00
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
return static_cast<int32_t>(size / mChannels / sizeof(short));
|
2010-12-19 11:05:40 -08:00
|
|
|
}
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
#if defined(MOZ_CUBEB)
|
2012-05-02 21:48:54 -07:00
|
|
|
class nsCircularByteBuffer
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
nsCircularByteBuffer()
|
2012-07-30 07:20:58 -07:00
|
|
|
: mBuffer(nullptr), mCapacity(0), mStart(0), mCount(0)
|
2012-05-02 21:48:54 -07:00
|
|
|
{}
|
|
|
|
|
|
|
|
// Set the capacity of the buffer in bytes. Must be called before any
|
|
|
|
// call to append or pop elements.
|
2012-08-22 08:56:38 -07:00
|
|
|
void SetCapacity(uint32_t aCapacity) {
|
2012-05-02 21:48:54 -07:00
|
|
|
NS_ABORT_IF_FALSE(!mBuffer, "Buffer allocated.");
|
|
|
|
mCapacity = aCapacity;
|
2012-08-22 08:56:38 -07:00
|
|
|
mBuffer = new uint8_t[mCapacity];
|
2012-05-02 21:48:54 -07:00
|
|
|
}
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t Length() {
|
2012-05-02 21:48:54 -07:00
|
|
|
return mCount;
|
|
|
|
}
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t Capacity() {
|
2012-05-02 21:48:54 -07:00
|
|
|
return mCapacity;
|
|
|
|
}
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t Available() {
|
2012-05-02 21:48:54 -07:00
|
|
|
return Capacity() - Length();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Append aLength bytes from aSrc to the buffer. Caller must check that
|
|
|
|
// sufficient space is available.
|
2012-08-22 08:56:38 -07:00
|
|
|
void AppendElements(const uint8_t* aSrc, uint32_t aLength) {
|
2012-05-02 21:48:54 -07:00
|
|
|
NS_ABORT_IF_FALSE(mBuffer && mCapacity, "Buffer not initialized.");
|
|
|
|
NS_ABORT_IF_FALSE(aLength <= Available(), "Buffer full.");
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t end = (mStart + mCount) % mCapacity;
|
2012-05-02 21:48:54 -07:00
|
|
|
|
2013-01-15 04:22:03 -08:00
|
|
|
uint32_t toCopy = std::min(mCapacity - end, aLength);
|
2012-05-02 21:48:54 -07:00
|
|
|
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().
|
2012-08-22 08:56:38 -07:00
|
|
|
void PopElements(uint32_t aSize, void** aData1, uint32_t* aSize1,
|
|
|
|
void** aData2, uint32_t* aSize2) {
|
2012-05-02 21:48:54 -07:00
|
|
|
NS_ABORT_IF_FALSE(mBuffer && mCapacity, "Buffer not initialized.");
|
|
|
|
NS_ABORT_IF_FALSE(aSize <= Length(), "Request too large.");
|
|
|
|
|
|
|
|
*aData1 = &mBuffer[mStart];
|
2013-01-15 04:22:03 -08:00
|
|
|
*aSize1 = std::min(mCapacity - mStart, aSize);
|
2012-05-02 21:48:54 -07:00
|
|
|
*aData2 = &mBuffer[0];
|
|
|
|
*aSize2 = aSize - *aSize1;
|
|
|
|
mCount -= *aSize1 + *aSize2;
|
|
|
|
mStart += *aSize1 + *aSize2;
|
|
|
|
mStart %= mCapacity;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2012-08-22 08:56:38 -07:00
|
|
|
nsAutoArrayPtr<uint8_t> mBuffer;
|
|
|
|
uint32_t mCapacity;
|
|
|
|
uint32_t mStart;
|
|
|
|
uint32_t mCount;
|
2012-05-02 21:48:54 -07:00
|
|
|
};
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
class BufferedAudioStream : public AudioStream
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
public:
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream();
|
|
|
|
~BufferedAudioStream();
|
2012-01-12 13:20:36 -08:00
|
|
|
|
2012-11-15 19:25:26 -08:00
|
|
|
nsresult Init(int32_t aNumChannels, int32_t aRate,
|
2012-11-18 16:54:29 -08:00
|
|
|
const dom::AudioChannelType aAudioChannelType);
|
2012-01-12 13:20:36 -08:00
|
|
|
void Shutdown();
|
2012-10-25 03:10:51 -07:00
|
|
|
nsresult Write(const AudioDataValue* aBuf, uint32_t aFrames);
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t Available();
|
2012-01-12 13:20:36 -08:00
|
|
|
void SetVolume(double aVolume);
|
|
|
|
void Drain();
|
|
|
|
void Pause();
|
|
|
|
void Resume();
|
2012-08-22 08:56:38 -07:00
|
|
|
int64_t GetPosition();
|
|
|
|
int64_t GetPositionInFrames();
|
2012-11-22 02:38:28 -08:00
|
|
|
int64_t GetPositionInFramesInternal();
|
2012-01-12 13:20:36 -08:00
|
|
|
bool IsPaused();
|
2012-08-22 08:56:38 -07:00
|
|
|
int32_t GetMinWriteSize();
|
2012-01-12 13:20:36 -08:00
|
|
|
|
|
|
|
private:
|
|
|
|
static long DataCallback_S(cubeb_stream*, void* aThis, void* aBuffer, long aFrames)
|
|
|
|
{
|
2012-11-28 11:40:07 -08:00
|
|
|
return static_cast<BufferedAudioStream*>(aThis)->DataCallback(aBuffer, aFrames);
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
2012-07-16 14:15:24 -07:00
|
|
|
static void StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState)
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
2012-11-28 11:40:07 -08:00
|
|
|
static_cast<BufferedAudioStream*>(aThis)->StateCallback(aState);
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
long DataCallback(void* aBuffer, long aFrames);
|
2012-07-16 14:15:24 -07:00
|
|
|
void StateCallback(cubeb_state aState);
|
2012-01-12 13:20:36 -08:00
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
long GetUnprocessed(void* aBuffer, long aFrames);
|
|
|
|
|
|
|
|
long GetTimeStretched(void* aBuffer, long aFrames);
|
|
|
|
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
// Shared implementation of underflow adjusted position calculation.
|
|
|
|
// Caller must own the monitor.
|
2012-08-22 08:56:38 -07:00
|
|
|
int64_t GetPositionInFramesUnlocked();
|
2012-01-12 13:20:36 -08:00
|
|
|
|
|
|
|
// 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.
|
2012-08-22 08:56:38 -07:00
|
|
|
uint64_t mLostFrames;
|
2012-01-12 13:20:36 -08:00
|
|
|
|
|
|
|
// Temporary audio buffer. Filled by Write() and consumed by
|
2012-05-02 21:48:54 -07:00
|
|
|
// DataCallback(). Once mBuffer is full, Write() blocks until sufficient
|
|
|
|
// space becomes available in mBuffer. mBuffer is sized in bytes, not
|
|
|
|
// frames.
|
|
|
|
nsCircularByteBuffer mBuffer;
|
2012-01-12 13:20:36 -08:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t mBytesPerFrame;
|
2012-01-12 13:20:36 -08:00
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
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.
|
2012-04-15 20:00:40 -07:00
|
|
|
DRAINED, // StateCallback has indicated that the drain is complete.
|
|
|
|
ERRORED // Stream disabled due to an internal error.
|
2012-01-12 13:20:36 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
StreamState mState;
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
2012-11-14 11:46:40 -08:00
|
|
|
AudioStream* AudioStream::AllocateStream()
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
#if defined(MOZ_CUBEB)
|
|
|
|
if (GetUseCubeb()) {
|
2012-11-28 11:40:07 -08:00
|
|
|
return new BufferedAudioStream();
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
#endif
|
2012-11-28 11:40:07 -08:00
|
|
|
return new NativeAudioStream();
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(MOZ_CUBEB)
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::BufferedAudioStream()
|
|
|
|
: mMonitor("BufferedAudioStream"), mLostFrames(0), mVolume(1.0),
|
2012-01-12 13:20:36 -08:00
|
|
|
mBytesPerFrame(0), mState(INITIALIZED)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::~BufferedAudioStream()
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
Shutdown();
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::Init(int32_t aNumChannels, int32_t aRate,
|
2012-11-18 16:54:29 -08:00
|
|
|
const dom::AudioChannelType aAudioChannelType)
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
2012-04-15 20:00:12 -07:00
|
|
|
cubeb* cubebContext = GetCubebContext();
|
|
|
|
|
|
|
|
if (!cubebContext || aNumChannels < 0 || aRate < 0) {
|
2012-01-12 13:20:36 -08:00
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
mInRate = mOutRate = aRate;
|
2012-01-12 13:20:36 -08:00
|
|
|
mChannels = aNumChannels;
|
|
|
|
|
|
|
|
cubeb_stream_params params;
|
|
|
|
params.rate = aRate;
|
|
|
|
params.channels = aNumChannels;
|
2012-10-25 03:09:40 -07:00
|
|
|
if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) {
|
|
|
|
params.format = CUBEB_SAMPLE_S16NE;
|
2012-10-25 03:09:39 -07:00
|
|
|
} else {
|
|
|
|
params.format = CUBEB_SAMPLE_FLOAT32NE;
|
|
|
|
}
|
2012-10-25 03:09:40 -07:00
|
|
|
mBytesPerFrame = sizeof(AudioDataValue) * aNumChannels;
|
2012-01-12 13:20:36 -08:00
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
mAudioClock.Init();
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
cubeb_stream* stream;
|
2012-11-28 11:40:07 -08:00
|
|
|
if (cubeb_stream_init(cubebContext, &stream, "BufferedAudioStream", params,
|
2012-05-02 21:48:54 -07:00
|
|
|
GetCubebLatency(), DataCallback_S, StateCallback_S, this) == CUBEB_OK) {
|
2012-01-12 13:20:36 -08:00
|
|
|
mCubebStream.own(stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mCubebStream) {
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
|
|
|
|
2012-05-02 21:48:54 -07:00
|
|
|
// Size mBuffer for one second of audio. This value is arbitrary, and was
|
2012-11-14 11:46:40 -08:00
|
|
|
// selected based on the observed behaviour of the existing AudioStream
|
2012-01-12 13:20:36 -08:00
|
|
|
// implementations.
|
2012-11-22 02:38:28 -08:00
|
|
|
uint32_t bufferLimit = FramesToBytes(aRate);
|
2012-05-02 21:48:54 -07:00
|
|
|
NS_ABORT_IF_FALSE(bufferLimit % mBytesPerFrame == 0, "Must buffer complete frames");
|
|
|
|
mBuffer.SetCapacity(bufferLimit);
|
2012-01-12 13:20:36 -08:00
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::Shutdown()
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
2012-05-31 21:45:01 -07:00
|
|
|
if (mState == STARTED) {
|
|
|
|
Pause();
|
|
|
|
}
|
2012-01-12 13:20:36 -08:00
|
|
|
if (mCubebStream) {
|
|
|
|
mCubebStream.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames)
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
MonitorAutoLock mon(mMonitor);
|
2012-04-15 20:00:40 -07:00
|
|
|
if (!mCubebStream || mState == ERRORED) {
|
2012-01-12 13:20:36 -08:00
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
2012-11-22 02:38:28 -08:00
|
|
|
NS_ASSERTION(mState == INITIALIZED || mState == STARTED,
|
|
|
|
"Stream write in unexpected state.");
|
2012-01-12 13:20:36 -08:00
|
|
|
|
2012-10-25 03:10:51 -07:00
|
|
|
const uint8_t* src = reinterpret_cast<const uint8_t*>(aBuf);
|
2012-11-22 02:38:28 -08:00
|
|
|
uint32_t bytesToCopy = FramesToBytes(aFrames);
|
2012-01-12 13:20:36 -08:00
|
|
|
|
|
|
|
while (bytesToCopy > 0) {
|
2013-01-15 04:22:03 -08:00
|
|
|
uint32_t available = std::min(bytesToCopy, mBuffer.Available());
|
2012-11-22 02:38:28 -08:00
|
|
|
NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0,
|
|
|
|
"Must copy complete frames.");
|
2012-01-12 13:20:36 -08:00
|
|
|
|
|
|
|
mBuffer.AppendElements(src, available);
|
|
|
|
src += available;
|
|
|
|
bytesToCopy -= available;
|
|
|
|
|
2013-01-16 02:44:06 -08:00
|
|
|
if (mState != STARTED) {
|
|
|
|
int r;
|
|
|
|
{
|
2012-05-31 21:45:01 -07:00
|
|
|
MonitorAutoUnlock mon(mMonitor);
|
2013-01-16 02:44:06 -08:00
|
|
|
r = cubeb_stream_start(mCubebStream);
|
2012-05-31 21:45:01 -07:00
|
|
|
}
|
2013-01-16 02:44:06 -08:00
|
|
|
mState = r == CUBEB_OK ? STARTED : ERRORED;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mState != STARTED) {
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bytesToCopy > 0) {
|
2012-05-31 21:45:01 -07:00
|
|
|
mon.Wait();
|
|
|
|
}
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
uint32_t
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::Available()
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
MonitorAutoLock mon(mMonitor);
|
|
|
|
NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Buffer invariant violated.");
|
2012-11-22 02:38:28 -08:00
|
|
|
return BytesToFrames(mBuffer.Available());
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
int32_t
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::GetMinWriteSize()
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::SetVolume(double aVolume)
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
MonitorAutoLock mon(mMonitor);
|
|
|
|
NS_ABORT_IF_FALSE(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
|
|
|
|
mVolume = aVolume;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::Drain()
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
MonitorAutoLock mon(mMonitor);
|
|
|
|
if (mState != STARTED) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mState = DRAINING;
|
2012-04-15 20:00:40 -07:00
|
|
|
while (mState == DRAINING) {
|
2012-01-12 13:20:36 -08:00
|
|
|
mon.Wait();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::Pause()
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
MonitorAutoLock mon(mMonitor);
|
|
|
|
if (!mCubebStream || mState != STARTED) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-05-31 21:45:01 -07:00
|
|
|
int r;
|
|
|
|
{
|
|
|
|
MonitorAutoUnlock mon(mMonitor);
|
|
|
|
r = cubeb_stream_stop(mCubebStream);
|
|
|
|
}
|
|
|
|
if (mState != ERRORED && r == CUBEB_OK) {
|
2012-01-12 13:20:36 -08:00
|
|
|
mState = STOPPED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::Resume()
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
MonitorAutoLock mon(mMonitor);
|
|
|
|
if (!mCubebStream || mState != STOPPED) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-05-31 21:45:01 -07:00
|
|
|
int r;
|
|
|
|
{
|
|
|
|
MonitorAutoUnlock mon(mMonitor);
|
|
|
|
r = cubeb_stream_start(mCubebStream);
|
|
|
|
}
|
|
|
|
if (mState != ERRORED && r == CUBEB_OK) {
|
2012-01-12 13:20:36 -08:00
|
|
|
mState = STARTED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
int64_t
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::GetPosition()
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
2012-11-22 02:38:28 -08:00
|
|
|
return mAudioClock.GetPosition();
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
2012-07-16 14:21:04 -07:00
|
|
|
// This function is miscompiled by PGO with MSVC 2010. See bug 768333.
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
#pragma optimize("", off)
|
|
|
|
#endif
|
2012-08-22 08:56:38 -07:00
|
|
|
int64_t
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::GetPositionInFrames()
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
2012-11-22 02:38:28 -08:00
|
|
|
return mAudioClock.GetPositionInFrames();
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
2012-07-16 14:21:04 -07:00
|
|
|
#ifdef _MSC_VER
|
|
|
|
#pragma optimize("", on)
|
|
|
|
#endif
|
2012-01-12 13:20:36 -08:00
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
int64_t
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::GetPositionInFramesInternal()
|
2012-11-22 02:38:28 -08:00
|
|
|
{
|
|
|
|
MonitorAutoLock mon(mMonitor);
|
|
|
|
return GetPositionInFramesUnlocked();
|
|
|
|
}
|
|
|
|
|
2012-08-22 08:56:38 -07:00
|
|
|
int64_t
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::GetPositionInFramesUnlocked()
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
mMonitor.AssertCurrentThreadOwns();
|
|
|
|
|
2012-04-15 20:00:40 -07:00
|
|
|
if (!mCubebStream || mState == ERRORED) {
|
2012-01-12 13:20:36 -08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t position = 0;
|
2012-05-31 21:45:01 -07:00
|
|
|
{
|
|
|
|
MonitorAutoUnlock mon(mMonitor);
|
|
|
|
if (cubeb_stream_get_position(mCubebStream, &position) != CUBEB_OK) {
|
|
|
|
return -1;
|
|
|
|
}
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Adjust the reported position by the number of silent frames written
|
|
|
|
// during stream underruns.
|
2012-08-22 08:56:38 -07:00
|
|
|
uint64_t adjustedPosition = 0;
|
2012-01-12 13:20:36 -08:00
|
|
|
if (position >= mLostFrames) {
|
|
|
|
adjustedPosition = position - mLostFrames;
|
|
|
|
}
|
2013-01-15 04:22:03 -08:00
|
|
|
return std::min<uint64_t>(adjustedPosition, INT64_MAX);
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::IsPaused()
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
MonitorAutoLock mon(mMonitor);
|
|
|
|
return mState == STOPPED;
|
|
|
|
}
|
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
long
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::GetUnprocessed(void* aBuffer, long aFrames)
|
2012-11-22 02:38:28 -08:00
|
|
|
{
|
|
|
|
uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer);
|
|
|
|
|
|
|
|
// Flush the timestretcher pipeline, if we were playing using a playback rate
|
|
|
|
// other than 1.0.
|
|
|
|
uint32_t flushedFrames = 0;
|
|
|
|
if (mTimeStretcher && mTimeStretcher->numSamples()) {
|
|
|
|
flushedFrames = mTimeStretcher->receiveSamples(reinterpret_cast<AudioDataValue*>(wpos), aFrames);
|
|
|
|
wpos += FramesToBytes(flushedFrames);
|
|
|
|
}
|
|
|
|
uint32_t toPopBytes = FramesToBytes(aFrames - flushedFrames);
|
2013-01-15 04:22:03 -08:00
|
|
|
uint32_t available = std::min(toPopBytes, mBuffer.Length());
|
2012-11-22 02:38:28 -08:00
|
|
|
|
|
|
|
void* input[2];
|
|
|
|
uint32_t input_size[2];
|
|
|
|
mBuffer.PopElements(available, &input[0], &input_size[0], &input[1], &input_size[1]);
|
|
|
|
memcpy(wpos, input[0], input_size[0]);
|
|
|
|
wpos += input_size[0];
|
|
|
|
memcpy(wpos, input[1], input_size[1]);
|
|
|
|
return BytesToFrames(available) + flushedFrames;
|
|
|
|
}
|
|
|
|
|
|
|
|
long
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::GetTimeStretched(void* aBuffer, long aFrames)
|
2012-11-22 02:38:28 -08:00
|
|
|
{
|
|
|
|
long processedFrames = 0;
|
2012-11-29 06:40:57 -08:00
|
|
|
|
|
|
|
EnsureTimeStretcherInitialized();
|
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer);
|
|
|
|
double playbackRate = static_cast<double>(mInRate) / mOutRate;
|
|
|
|
uint32_t toPopBytes = FramesToBytes(ceil(aFrames / playbackRate));
|
|
|
|
uint32_t available = 0;
|
|
|
|
bool lowOnBufferedData = false;
|
|
|
|
do {
|
|
|
|
// Check if we already have enough data in the time stretcher pipeline.
|
|
|
|
if (mTimeStretcher->numSamples() <= static_cast<uint32_t>(aFrames)) {
|
|
|
|
void* input[2];
|
|
|
|
uint32_t input_size[2];
|
2013-01-15 04:22:03 -08:00
|
|
|
available = std::min(mBuffer.Length(), toPopBytes);
|
2012-11-22 02:38:28 -08:00
|
|
|
if (available != toPopBytes) {
|
|
|
|
lowOnBufferedData = true;
|
|
|
|
}
|
|
|
|
mBuffer.PopElements(available, &input[0], &input_size[0],
|
|
|
|
&input[1], &input_size[1]);
|
|
|
|
for(uint32_t i = 0; i < 2; i++) {
|
|
|
|
mTimeStretcher->putSamples(reinterpret_cast<AudioDataValue*>(input[i]), BytesToFrames(input_size[i]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
uint32_t receivedFrames = mTimeStretcher->receiveSamples(reinterpret_cast<AudioDataValue*>(wpos), aFrames - processedFrames);
|
|
|
|
wpos += FramesToBytes(receivedFrames);
|
|
|
|
processedFrames += receivedFrames;
|
|
|
|
} while (processedFrames < aFrames && !lowOnBufferedData);
|
|
|
|
|
|
|
|
return processedFrames;
|
|
|
|
}
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
long
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::DataCallback(void* aBuffer, long aFrames)
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
MonitorAutoLock mon(mMonitor);
|
2013-01-15 04:22:03 -08:00
|
|
|
uint32_t available = std::min(static_cast<uint32_t>(FramesToBytes(aFrames)), mBuffer.Length());
|
2012-01-12 13:20:36 -08:00
|
|
|
NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0, "Must copy complete frames");
|
2012-11-22 02:38:28 -08:00
|
|
|
uint32_t underrunFrames = 0;
|
|
|
|
uint32_t servicedFrames = 0;
|
2012-01-12 13:20:36 -08:00
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
if (available) {
|
|
|
|
AudioDataValue* output = reinterpret_cast<AudioDataValue*>(aBuffer);
|
|
|
|
if (mInRate == mOutRate) {
|
|
|
|
servicedFrames = GetUnprocessed(output, aFrames);
|
|
|
|
} else {
|
|
|
|
servicedFrames = GetTimeStretched(output, aFrames);
|
|
|
|
}
|
2012-10-25 03:09:40 -07:00
|
|
|
float scaled_volume = float(GetVolumeScale() * mVolume);
|
2012-01-12 13:20:36 -08:00
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
ScaleAudioSamples(output, aFrames * mChannels, scaled_volume);
|
2012-05-02 21:48:54 -07:00
|
|
|
|
|
|
|
NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Must copy complete frames");
|
2012-01-12 13:20:36 -08:00
|
|
|
|
2012-05-02 21:48:54 -07:00
|
|
|
// Notify any blocked Write() call that more space is available in mBuffer.
|
|
|
|
mon.NotifyAll();
|
|
|
|
}
|
2012-01-12 13:20:36 -08:00
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
underrunFrames = aFrames - servicedFrames;
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
if (mState != DRAINING) {
|
2012-11-22 02:38:28 -08:00
|
|
|
uint8_t* rpos = static_cast<uint8_t*>(aBuffer) + FramesToBytes(aFrames - underrunFrames);
|
|
|
|
memset(rpos, 0, FramesToBytes(underrunFrames));
|
|
|
|
mLostFrames += underrunFrames;
|
|
|
|
servicedFrames += underrunFrames;
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
mAudioClock.UpdateWritePosition(servicedFrames);
|
|
|
|
return servicedFrames;
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
2012-07-16 14:15:24 -07:00
|
|
|
void
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::StateCallback(cubeb_state aState)
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
2012-05-31 21:45:01 -07:00
|
|
|
MonitorAutoLock mon(mMonitor);
|
2012-01-12 13:20:36 -08:00
|
|
|
if (aState == CUBEB_STATE_DRAINED) {
|
|
|
|
mState = DRAINED;
|
2012-04-15 20:00:40 -07:00
|
|
|
} else if (aState == CUBEB_STATE_ERROR) {
|
|
|
|
mState = ERRORED;
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
2012-05-31 21:45:01 -07:00
|
|
|
mon.NotifyAll();
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
2012-11-22 02:38:28 -08:00
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
#endif
|
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
AudioClock::AudioClock(AudioStream* aStream)
|
|
|
|
:mAudioStream(aStream),
|
|
|
|
mOldOutRate(0),
|
|
|
|
mBasePosition(0),
|
|
|
|
mBaseOffset(0),
|
|
|
|
mOldBaseOffset(0),
|
2012-12-10 12:43:04 -08:00
|
|
|
mOldBasePosition(0),
|
2012-11-22 02:38:28 -08:00
|
|
|
mPlaybackRateChangeOffset(0),
|
|
|
|
mPreviousPosition(0),
|
|
|
|
mWritten(0),
|
|
|
|
mOutRate(0),
|
|
|
|
mInRate(0),
|
|
|
|
mPreservesPitch(true),
|
Backout b3a8618f901c (bug 829042), 34a9ef8f929d (bug 822933), 4c1215cefbab (bug 826349), 70bb7f775178 (bug 825325), e9c8447fb197 (bug 828713), eb6ebf01eafe (bug 828901), f1f3ef647920 (bug 825329), f9d7b5722d4f (bug 825329), 5add564d4546 (bug 819377), 55e93d1fa972 (bug 804875), f14639a3461e (bug 804875), 23456fc21052 (bug 814308) for Windows pgo-only mochitest-1 media test timeouts on a CLOSED TREE
2013-01-16 07:16:23 -08:00
|
|
|
mPlaybackRate(1.0),
|
2012-11-22 02:38:28 -08:00
|
|
|
mCompensatingLatency(false)
|
|
|
|
{}
|
|
|
|
|
|
|
|
void AudioClock::Init()
|
|
|
|
{
|
|
|
|
mOutRate = mAudioStream->GetRate();
|
|
|
|
mInRate = mAudioStream->GetRate();
|
Backout b3a8618f901c (bug 829042), 34a9ef8f929d (bug 822933), 4c1215cefbab (bug 826349), 70bb7f775178 (bug 825325), e9c8447fb197 (bug 828713), eb6ebf01eafe (bug 828901), f1f3ef647920 (bug 825329), f9d7b5722d4f (bug 825329), 5add564d4546 (bug 819377), 55e93d1fa972 (bug 804875), f14639a3461e (bug 804875), 23456fc21052 (bug 814308) for Windows pgo-only mochitest-1 media test timeouts on a CLOSED TREE
2013-01-16 07:16:23 -08:00
|
|
|
mPlaybackRate = 1.0;
|
2012-12-10 12:43:04 -08:00
|
|
|
mOldOutRate = mOutRate;
|
2012-11-22 02:38:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
void AudioClock::UpdateWritePosition(uint32_t aCount)
|
|
|
|
{
|
|
|
|
mWritten += aCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t AudioClock::GetPosition()
|
|
|
|
{
|
|
|
|
NS_ASSERTION(mInRate != 0 && mOutRate != 0, "AudioClock not initialized.");
|
|
|
|
int64_t position = mAudioStream->GetPositionInFramesInternal();
|
|
|
|
int64_t diffOffset;
|
|
|
|
if (position >= 0) {
|
|
|
|
if (position < mPlaybackRateChangeOffset) {
|
|
|
|
// See if we are still playing frames pushed with the old playback rate in
|
|
|
|
// the backend. If we are, use the old output rate to compute the
|
|
|
|
// position.
|
|
|
|
mCompensatingLatency = true;
|
|
|
|
diffOffset = position - mOldBaseOffset;
|
|
|
|
position = static_cast<uint64_t>(mOldBasePosition +
|
|
|
|
static_cast<float>(USECS_PER_S * diffOffset) / mOldOutRate);
|
|
|
|
mPreviousPosition = position;
|
|
|
|
return position;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mCompensatingLatency) {
|
|
|
|
diffOffset = position - mPlaybackRateChangeOffset;
|
|
|
|
mCompensatingLatency = false;
|
|
|
|
mBasePosition = mPreviousPosition;
|
|
|
|
} else {
|
|
|
|
diffOffset = position - mPlaybackRateChangeOffset;
|
|
|
|
}
|
|
|
|
position = static_cast<uint64_t>(mBasePosition +
|
|
|
|
(static_cast<float>(USECS_PER_S * diffOffset) / mOutRate));
|
|
|
|
return position;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t AudioClock::GetPositionInFrames()
|
|
|
|
{
|
|
|
|
return (GetPosition() * mOutRate) / USECS_PER_S;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioClock::SetPlaybackRate(double aPlaybackRate)
|
|
|
|
{
|
|
|
|
int64_t position = mAudioStream->GetPositionInFramesInternal();
|
|
|
|
if (position > mPlaybackRateChangeOffset) {
|
|
|
|
mOldBasePosition = mBasePosition;
|
|
|
|
mBasePosition = GetPosition();
|
|
|
|
mOldBaseOffset = mPlaybackRateChangeOffset;
|
|
|
|
mBaseOffset = position;
|
|
|
|
mPlaybackRateChangeOffset = mWritten;
|
|
|
|
mOldOutRate = mOutRate;
|
|
|
|
mOutRate = static_cast<int>(mInRate / aPlaybackRate);
|
|
|
|
} else {
|
|
|
|
// The playbackRate has been changed before the end of the latency
|
|
|
|
// compensation phase. We don't update the mOld* variable. That way, the
|
|
|
|
// last playbackRate set is taken into account.
|
|
|
|
mBasePosition = GetPosition();
|
|
|
|
mBaseOffset = position;
|
|
|
|
mPlaybackRateChangeOffset = mWritten;
|
|
|
|
mOutRate = static_cast<int>(mInRate / aPlaybackRate);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
double AudioClock::GetPlaybackRate()
|
|
|
|
{
|
Backout b3a8618f901c (bug 829042), 34a9ef8f929d (bug 822933), 4c1215cefbab (bug 826349), 70bb7f775178 (bug 825325), e9c8447fb197 (bug 828713), eb6ebf01eafe (bug 828901), f1f3ef647920 (bug 825329), f9d7b5722d4f (bug 825329), 5add564d4546 (bug 819377), 55e93d1fa972 (bug 804875), f14639a3461e (bug 804875), 23456fc21052 (bug 814308) for Windows pgo-only mochitest-1 media test timeouts on a CLOSED TREE
2013-01-16 07:16:23 -08:00
|
|
|
return mPlaybackRate;
|
2012-11-22 02:38:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
void AudioClock::SetPreservesPitch(bool aPreservesPitch)
|
|
|
|
{
|
|
|
|
mPreservesPitch = aPreservesPitch;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AudioClock::GetPreservesPitch()
|
|
|
|
{
|
|
|
|
return mPreservesPitch;
|
|
|
|
}
|
2012-11-14 11:45:33 -08:00
|
|
|
} // namespace mozilla
|