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>
|
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
|
|
|
|
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-01-12 13:20:36 -08:00
|
|
|
#define PREF_VOLUME_SCALE "media.volume_scale"
|
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;
|
2012-08-22 08:56:38 -07:00
|
|
|
static uint32_t gCubebLatency;
|
2012-05-02 21:48:54 -07:00
|
|
|
|
2013-04-03 16:12:27 -07:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
* the audio for the stream including any skips due to underruns.
|
|
|
|
*/
|
|
|
|
static int gDumpedAudioCount = 0;
|
|
|
|
|
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
|
|
|
}
|
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)
|
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
|
|
|
|
|
2013-03-20 19:36:29 -07:00
|
|
|
#if defined(MOZ_CUBEB) && defined(__ANDROID__) && defined(MOZ_B2G)
|
2013-03-11 20:46:32 -07:00
|
|
|
static cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannelType aType)
|
|
|
|
{
|
|
|
|
switch(aType) {
|
|
|
|
case dom::AUDIO_CHANNEL_NORMAL:
|
|
|
|
return CUBEB_STREAM_TYPE_SYSTEM;
|
|
|
|
case dom::AUDIO_CHANNEL_CONTENT:
|
|
|
|
return CUBEB_STREAM_TYPE_MUSIC;
|
|
|
|
case dom::AUDIO_CHANNEL_NOTIFICATION:
|
|
|
|
return CUBEB_STREAM_TYPE_NOTIFICATION;
|
|
|
|
case dom::AUDIO_CHANNEL_ALARM:
|
|
|
|
return CUBEB_STREAM_TYPE_ALARM;
|
|
|
|
case dom::AUDIO_CHANNEL_TELEPHONY:
|
|
|
|
return CUBEB_STREAM_TYPE_VOICE_CALL;
|
|
|
|
case dom::AUDIO_CHANNEL_RINGER:
|
|
|
|
return CUBEB_STREAM_TYPE_RING;
|
|
|
|
// Currently Android openSLES library doesn't support FORCE_AUDIBLE yet.
|
|
|
|
case dom::AUDIO_CHANNEL_PUBLICNOTIFICATION:
|
|
|
|
default:
|
|
|
|
NS_ERROR("The value of AudioChannelType is invalid");
|
|
|
|
return CUBEB_STREAM_TYPE_MAX;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
AudioStream::AudioStream()
|
|
|
|
: mInRate(0),
|
|
|
|
mOutRate(0),
|
|
|
|
mChannels(0),
|
2012-11-26 06:13:08 -08:00
|
|
|
mWritten(0),
|
2012-11-22 02:38:28 -08:00
|
|
|
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_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)
|
2013-03-18 21:12:36 -07:00
|
|
|
Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY);
|
2012-01-12 13:20:36 -08:00
|
|
|
#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
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2013-03-04 06:48:58 -08:00
|
|
|
nsresult AudioStream::EnsureTimeStretcherInitialized()
|
2012-11-22 02:38:28 -08:00
|
|
|
{
|
2012-11-28 08:25:57 -08:00
|
|
|
if (!mTimeStretcher) {
|
2013-03-04 06:48:58 -08:00
|
|
|
// SoundTouch does not support a number of channels > 2
|
|
|
|
if (mChannels > 2) {
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
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
|
|
|
}
|
2013-03-04 06:48:58 -08:00
|
|
|
return NS_OK;
|
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;
|
|
|
|
}
|
2013-03-04 06:48:58 -08:00
|
|
|
|
|
|
|
if (EnsureTimeStretcherInitialized() != NS_OK) {
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
|
|
|
|
2012-11-22 02:38:28 -08:00
|
|
|
mAudioClock.SetPlaybackRate(aPlaybackRate);
|
|
|
|
mOutRate = mInRate / aPlaybackRate;
|
2012-11-29 06:40:57 -08:00
|
|
|
|
|
|
|
|
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
|
|
|
|
2013-03-04 06:48:58 -08:00
|
|
|
if (EnsureTimeStretcherInitialized() != NS_OK) {
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
2012-11-29 06:40:57 -08:00
|
|
|
|
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-26 06:13:08 -08:00
|
|
|
int64_t AudioStream::GetWritten()
|
|
|
|
{
|
|
|
|
return mWritten;
|
|
|
|
}
|
|
|
|
|
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();
|
2013-01-22 21:53:10 -08:00
|
|
|
void Start();
|
2012-01-12 13:20:36 -08:00
|
|
|
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();
|
2013-01-18 05:21:47 -08:00
|
|
|
// This method acquires the monitor and forward the call to the base
|
|
|
|
// class, to prevent a race on |mTimeStretcher|, in
|
|
|
|
// |AudioStream::EnsureTimeStretcherInitialized|.
|
2013-03-04 06:48:58 -08:00
|
|
|
nsresult EnsureTimeStretcherInitialized();
|
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
|
|
|
|
2013-01-22 21:53:10 -08:00
|
|
|
void StartUnlocked();
|
|
|
|
|
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
|
|
|
|
2013-04-03 16:12:27 -07:00
|
|
|
// Output file for dumping audio
|
|
|
|
FILE* mDumpFile;
|
|
|
|
|
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)
|
2013-03-18 21:12:36 -07:00
|
|
|
return new BufferedAudioStream();
|
2012-01-12 13:20:36 -08:00
|
|
|
#endif
|
2013-03-18 21:12:36 -07:00
|
|
|
return nullptr;
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
2013-04-03 16:12:27 -07:00
|
|
|
static void SetUint16LE(PRUint8* aDest, PRUint16 aValue)
|
|
|
|
{
|
|
|
|
aDest[0] = aValue & 0xFF;
|
|
|
|
aDest[1] = aValue >> 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void SetUint32LE(PRUint8* aDest, PRUint32 aValue)
|
|
|
|
{
|
|
|
|
SetUint16LE(aDest, aValue & 0xFFFF);
|
|
|
|
SetUint16LE(aDest + 2, aValue >> 16);
|
|
|
|
}
|
|
|
|
|
|
|
|
static FILE*
|
|
|
|
OpenDumpFile(AudioStream* aStream)
|
|
|
|
{
|
|
|
|
if (!getenv("MOZ_DUMP_AUDIO"))
|
|
|
|
return nullptr;
|
|
|
|
char buf[100];
|
|
|
|
sprintf(buf, "dumped-audio-%d.wav", gDumpedAudioCount);
|
|
|
|
FILE* f = fopen(buf, "wb");
|
|
|
|
if (!f)
|
|
|
|
return nullptr;
|
|
|
|
++gDumpedAudioCount;
|
|
|
|
|
|
|
|
PRUint8 header[] = {
|
|
|
|
// RIFF header
|
|
|
|
0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45,
|
|
|
|
// fmt chunk. We always write 16-bit samples.
|
|
|
|
0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF,
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x10, 0x00,
|
|
|
|
// data chunk
|
|
|
|
0x64, 0x61, 0x74, 0x61, 0xFE, 0xFF, 0xFF, 0x7F
|
|
|
|
};
|
|
|
|
static const int CHANNEL_OFFSET = 22;
|
|
|
|
static const int SAMPLE_RATE_OFFSET = 24;
|
|
|
|
static const int BLOCK_ALIGN_OFFSET = 32;
|
|
|
|
SetUint16LE(header + CHANNEL_OFFSET, aStream->GetChannels());
|
|
|
|
SetUint32LE(header + SAMPLE_RATE_OFFSET, aStream->GetRate());
|
|
|
|
SetUint16LE(header + BLOCK_ALIGN_OFFSET, aStream->GetChannels()*2);
|
|
|
|
fwrite(header, sizeof(header), 1, f);
|
|
|
|
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
WriteDumpFile(FILE* aDumpFile, AudioStream* aStream, PRUint32 aFrames,
|
|
|
|
void* aBuffer)
|
|
|
|
{
|
|
|
|
if (!aDumpFile)
|
|
|
|
return;
|
|
|
|
|
|
|
|
PRUint32 samples = aStream->GetChannels()*aFrames;
|
|
|
|
if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) {
|
|
|
|
fwrite(aBuffer, 2, samples, aDumpFile);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_ASSERTION(AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_FLOAT32, "bad format");
|
|
|
|
nsAutoTArray<PRUint8, 1024*2> buf;
|
|
|
|
buf.SetLength(samples*2);
|
|
|
|
float* input = static_cast<float*>(aBuffer);
|
|
|
|
PRUint8* output = buf.Elements();
|
|
|
|
for (PRUint32 i = 0; i < samples; ++i) {
|
|
|
|
SetUint16LE(output + i*2, PRInt16(input[i]*32767.0f));
|
|
|
|
}
|
|
|
|
fwrite(output, 2, samples, aDumpFile);
|
|
|
|
fflush(aDumpFile);
|
|
|
|
}
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
#if defined(MOZ_CUBEB)
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::BufferedAudioStream()
|
2013-04-03 16:12:27 -07:00
|
|
|
: mMonitor("BufferedAudioStream"), mLostFrames(0), mDumpFile(nullptr),
|
|
|
|
mVolume(1.0), mBytesPerFrame(0), mState(INITIALIZED)
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2012-11-28 11:40:07 -08:00
|
|
|
BufferedAudioStream::~BufferedAudioStream()
|
2012-01-12 13:20:36 -08:00
|
|
|
{
|
|
|
|
Shutdown();
|
2013-04-03 16:12:27 -07:00
|
|
|
if (mDumpFile) {
|
|
|
|
fclose(mDumpFile);
|
|
|
|
}
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
2013-03-04 06:48:58 -08:00
|
|
|
nsresult
|
2013-01-18 05:21:47 -08:00
|
|
|
BufferedAudioStream::EnsureTimeStretcherInitialized()
|
|
|
|
{
|
|
|
|
MonitorAutoLock mon(mMonitor);
|
2013-03-04 06:48:58 -08:00
|
|
|
return AudioStream::EnsureTimeStretcherInitialized();
|
2013-01-18 05:21:47 -08:00
|
|
|
}
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
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;
|
|
|
|
|
2013-04-03 16:12:27 -07:00
|
|
|
mDumpFile = OpenDumpFile(this);
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
cubeb_stream_params params;
|
|
|
|
params.rate = aRate;
|
|
|
|
params.channels = aNumChannels;
|
2013-03-11 20:46:32 -07:00
|
|
|
#if defined(__ANDROID__)
|
2013-03-20 19:36:29 -07:00
|
|
|
#if defined(MOZ_B2G)
|
2013-03-11 20:46:32 -07:00
|
|
|
params.stream_type = ConvertChannelToCubebType(aAudioChannelType);
|
2013-03-20 19:36:29 -07:00
|
|
|
#else
|
|
|
|
params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
|
|
|
|
#endif
|
2013-03-11 20:46:32 -07:00
|
|
|
|
|
|
|
if (params.stream_type == CUBEB_STREAM_TYPE_MAX) {
|
|
|
|
return NS_ERROR_INVALID_ARG;
|
|
|
|
}
|
|
|
|
#endif
|
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;
|
|
|
|
|
2012-11-26 06:13:08 -08:00
|
|
|
if (bytesToCopy > 0) {
|
|
|
|
// If we are not playing, but our buffer is full, start playing to make
|
|
|
|
// room for soon-to-be-decoded data.
|
2013-01-22 21:53:10 -08:00
|
|
|
if (mState != STARTED) {
|
|
|
|
StartUnlocked();
|
|
|
|
if (mState != STARTED) {
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
2012-05-31 21:45:01 -07:00
|
|
|
}
|
|
|
|
mon.Wait();
|
|
|
|
}
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
2012-11-26 06:13:08 -08:00
|
|
|
mWritten += aFrames;
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2013-01-22 21:53:10 -08:00
|
|
|
NS_ASSERTION(mBuffer.Available() == 0, "Draining with unplayed audio");
|
2012-01-12 13:20:36 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
mState = DRAINING;
|
2012-04-15 20:00:40 -07:00
|
|
|
while (mState == DRAINING) {
|
2012-01-12 13:20:36 -08:00
|
|
|
mon.Wait();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-22 21:53:10 -08:00
|
|
|
void
|
2012-11-26 06:13:08 -08:00
|
|
|
BufferedAudioStream::Start()
|
|
|
|
{
|
2013-01-22 21:53:10 -08:00
|
|
|
MonitorAutoLock mon(mMonitor);
|
|
|
|
StartUnlocked();
|
2012-11-26 06:13:08 -08:00
|
|
|
}
|
|
|
|
|
2013-01-22 21:53:10 -08:00
|
|
|
void
|
|
|
|
BufferedAudioStream::StartUnlocked()
|
2012-11-26 06:13:08 -08:00
|
|
|
{
|
2013-01-22 21:53:10 -08:00
|
|
|
mMonitor.AssertCurrentThreadOwns();
|
|
|
|
if (!mCubebStream || mState != INITIALIZED) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (mState != STARTED) {
|
|
|
|
int r;
|
|
|
|
{
|
|
|
|
MonitorAutoUnlock mon(mMonitor);
|
|
|
|
r = cubeb_stream_start(mCubebStream);
|
|
|
|
}
|
|
|
|
if (mState != ERRORED) {
|
|
|
|
mState = r == CUBEB_OK ? STARTED : ERRORED;
|
|
|
|
}
|
|
|
|
}
|
2012-11-26 06:13:08 -08:00
|
|
|
}
|
|
|
|
|
2012-01-12 13:20:36 -08:00
|
|
|
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
|
|
|
|
2013-01-18 05:21:47 -08:00
|
|
|
// We need to call the non-locking version, because we already have the lock.
|
2013-03-04 06:48:58 -08:00
|
|
|
if (AudioStream::EnsureTimeStretcherInitialized() != NS_OK) {
|
|
|
|
return 0;
|
|
|
|
}
|
2012-11-29 06:40:57 -08:00
|
|
|
|
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));
|
2013-04-03 16:12:27 -07:00
|
|
|
#ifdef PR_LOGGING
|
|
|
|
if (underrunFrames) {
|
|
|
|
PR_LOG(gAudioStreamLog, PR_LOG_WARNING,
|
|
|
|
("AudioStream %p lost %d frames", this, underrunFrames));
|
|
|
|
}
|
|
|
|
#endif
|
2012-11-22 02:38:28 -08:00
|
|
|
mLostFrames += underrunFrames;
|
|
|
|
servicedFrames += underrunFrames;
|
2012-01-12 13:20:36 -08:00
|
|
|
}
|
|
|
|
|
2013-04-03 16:12:27 -07:00
|
|
|
WriteDumpFile(mDumpFile, this, aFrames, aBuffer);
|
|
|
|
|
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()
|
|
|
|
{
|
|
|
|
int64_t position = mAudioStream->GetPositionInFramesInternal();
|
|
|
|
int64_t diffOffset;
|
2013-02-28 09:11:04 -08:00
|
|
|
NS_ASSERTION(position < 0 || (mInRate != 0 && mOutRate != 0), "AudioClock not initialized.");
|
2012-11-22 02:38:28 -08:00
|
|
|
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
|