The problem can occur if a sample is started at the same time as the last AudioTrack callback for a playing sample is called. At this time, allocateChannel() can be called concurrently with moveToFront() which can cause an entry in mChannels being used by moveToFront() to be erased temporarily by allocateChannel(). The fix consists in making sure that the SoundPool mutex is held whenever play(), stop() or done() are called. In addition, other potential weaknesses have been removed by making sure that the channel mutex is held while starting, stopping and processing the AudioTrack call back. To that purpose, a mechanism similar to the channel restart method is implemented to avoid stopping channels from the AudioTrack call back but do it from the restart thread instead. The sound effects SounPool management in AudioService has also been improved to make sure that the samples have been loaded when a playback request is received and also to immediately release the SoundPool when the effects are unloaded without waiting for the GC to occur. The SoundPool.java class was modified to allow the use of a looper attached to the thread in which the sample loaded listener is running and not to the thread in which the SoundPool is created. The maximum number of samples that can be loaded in a SoundPool lifetime as been increased from 255 to 65535. Change-Id: I368a3bdfda4239f807f857c3e97b70f6b31b0af3
906 lines
24 KiB
C++
906 lines
24 KiB
C++
/*
|
|
* Copyright (C) 2007 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
//#define LOG_NDEBUG 0
|
|
#define LOG_TAG "SoundPool"
|
|
#include <utils/Log.h>
|
|
|
|
//#define USE_SHARED_MEM_BUFFER
|
|
|
|
// XXX needed for timing latency
|
|
#include <utils/Timers.h>
|
|
|
|
#include <sys/resource.h>
|
|
#include <media/AudioTrack.h>
|
|
#include <media/mediaplayer.h>
|
|
|
|
#include "SoundPool.h"
|
|
#include "SoundPoolThread.h"
|
|
|
|
namespace android
|
|
{
|
|
|
|
int kDefaultBufferCount = 4;
|
|
uint32_t kMaxSampleRate = 48000;
|
|
uint32_t kDefaultSampleRate = 44100;
|
|
uint32_t kDefaultFrameCount = 1200;
|
|
|
|
SoundPool::SoundPool(int maxChannels, int streamType, int srcQuality)
|
|
{
|
|
LOGV("SoundPool constructor: maxChannels=%d, streamType=%d, srcQuality=%d",
|
|
maxChannels, streamType, srcQuality);
|
|
|
|
// check limits
|
|
mMaxChannels = maxChannels;
|
|
if (mMaxChannels < 1) {
|
|
mMaxChannels = 1;
|
|
}
|
|
else if (mMaxChannels > 32) {
|
|
mMaxChannels = 32;
|
|
}
|
|
LOGW_IF(maxChannels != mMaxChannels, "App requested %d channels", maxChannels);
|
|
|
|
mQuit = false;
|
|
mDecodeThread = 0;
|
|
mStreamType = streamType;
|
|
mSrcQuality = srcQuality;
|
|
mAllocated = 0;
|
|
mNextSampleID = 0;
|
|
mNextChannelID = 0;
|
|
|
|
mCallback = 0;
|
|
mUserData = 0;
|
|
|
|
mChannelPool = new SoundChannel[mMaxChannels];
|
|
for (int i = 0; i < mMaxChannels; ++i) {
|
|
mChannelPool[i].init(this);
|
|
mChannels.push_back(&mChannelPool[i]);
|
|
}
|
|
|
|
// start decode thread
|
|
startThreads();
|
|
}
|
|
|
|
SoundPool::~SoundPool()
|
|
{
|
|
LOGV("SoundPool destructor");
|
|
mDecodeThread->quit();
|
|
quit();
|
|
|
|
Mutex::Autolock lock(&mLock);
|
|
|
|
mChannels.clear();
|
|
if (mChannelPool)
|
|
delete [] mChannelPool;
|
|
// clean up samples
|
|
LOGV("clear samples");
|
|
mSamples.clear();
|
|
|
|
if (mDecodeThread)
|
|
delete mDecodeThread;
|
|
}
|
|
|
|
void SoundPool::addToRestartList(SoundChannel* channel)
|
|
{
|
|
Mutex::Autolock lock(&mRestartLock);
|
|
if (!mQuit) {
|
|
mRestart.push_back(channel);
|
|
mCondition.signal();
|
|
}
|
|
}
|
|
|
|
void SoundPool::addToStopList(SoundChannel* channel)
|
|
{
|
|
Mutex::Autolock lock(&mRestartLock);
|
|
if (!mQuit) {
|
|
mStop.push_back(channel);
|
|
mCondition.signal();
|
|
}
|
|
}
|
|
|
|
int SoundPool::beginThread(void* arg)
|
|
{
|
|
SoundPool* p = (SoundPool*)arg;
|
|
return p->run();
|
|
}
|
|
|
|
int SoundPool::run()
|
|
{
|
|
mRestartLock.lock();
|
|
while (!mQuit) {
|
|
mCondition.wait(mRestartLock);
|
|
LOGV("awake");
|
|
if (mQuit) break;
|
|
|
|
while (!mStop.empty()) {
|
|
SoundChannel* channel;
|
|
LOGV("Getting channel from stop list");
|
|
List<SoundChannel* >::iterator iter = mStop.begin();
|
|
channel = *iter;
|
|
mStop.erase(iter);
|
|
mRestartLock.unlock();
|
|
if (channel != 0) {
|
|
Mutex::Autolock lock(&mLock);
|
|
channel->stop();
|
|
}
|
|
mRestartLock.lock();
|
|
if (mQuit) break;
|
|
}
|
|
|
|
while (!mRestart.empty()) {
|
|
SoundChannel* channel;
|
|
LOGV("Getting channel from list");
|
|
List<SoundChannel*>::iterator iter = mRestart.begin();
|
|
channel = *iter;
|
|
mRestart.erase(iter);
|
|
mRestartLock.unlock();
|
|
if (channel != 0) {
|
|
Mutex::Autolock lock(&mLock);
|
|
channel->nextEvent();
|
|
}
|
|
mRestartLock.lock();
|
|
if (mQuit) break;
|
|
}
|
|
}
|
|
|
|
mStop.clear();
|
|
mRestart.clear();
|
|
mCondition.signal();
|
|
mRestartLock.unlock();
|
|
LOGV("goodbye");
|
|
return 0;
|
|
}
|
|
|
|
void SoundPool::quit()
|
|
{
|
|
mRestartLock.lock();
|
|
mQuit = true;
|
|
mCondition.signal();
|
|
mCondition.wait(mRestartLock);
|
|
LOGV("return from quit");
|
|
mRestartLock.unlock();
|
|
}
|
|
|
|
bool SoundPool::startThreads()
|
|
{
|
|
createThreadEtc(beginThread, this, "SoundPool");
|
|
if (mDecodeThread == NULL)
|
|
mDecodeThread = new SoundPoolThread(this);
|
|
return mDecodeThread != NULL;
|
|
}
|
|
|
|
SoundChannel* SoundPool::findChannel(int channelID)
|
|
{
|
|
for (int i = 0; i < mMaxChannels; ++i) {
|
|
if (mChannelPool[i].channelID() == channelID) {
|
|
return &mChannelPool[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
SoundChannel* SoundPool::findNextChannel(int channelID)
|
|
{
|
|
for (int i = 0; i < mMaxChannels; ++i) {
|
|
if (mChannelPool[i].nextChannelID() == channelID) {
|
|
return &mChannelPool[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int SoundPool::load(const char* path, int priority)
|
|
{
|
|
LOGV("load: path=%s, priority=%d", path, priority);
|
|
Mutex::Autolock lock(&mLock);
|
|
sp<Sample> sample = new Sample(++mNextSampleID, path);
|
|
mSamples.add(sample->sampleID(), sample);
|
|
doLoad(sample);
|
|
return sample->sampleID();
|
|
}
|
|
|
|
int SoundPool::load(int fd, int64_t offset, int64_t length, int priority)
|
|
{
|
|
LOGV("load: fd=%d, offset=%lld, length=%lld, priority=%d",
|
|
fd, offset, length, priority);
|
|
Mutex::Autolock lock(&mLock);
|
|
sp<Sample> sample = new Sample(++mNextSampleID, fd, offset, length);
|
|
mSamples.add(sample->sampleID(), sample);
|
|
doLoad(sample);
|
|
return sample->sampleID();
|
|
}
|
|
|
|
void SoundPool::doLoad(sp<Sample>& sample)
|
|
{
|
|
LOGV("doLoad: loading sample sampleID=%d", sample->sampleID());
|
|
sample->startLoad();
|
|
mDecodeThread->loadSample(sample->sampleID());
|
|
}
|
|
|
|
bool SoundPool::unload(int sampleID)
|
|
{
|
|
LOGV("unload: sampleID=%d", sampleID);
|
|
Mutex::Autolock lock(&mLock);
|
|
return mSamples.removeItem(sampleID);
|
|
}
|
|
|
|
int SoundPool::play(int sampleID, float leftVolume, float rightVolume,
|
|
int priority, int loop, float rate)
|
|
{
|
|
LOGV("play sampleID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f",
|
|
sampleID, leftVolume, rightVolume, priority, loop, rate);
|
|
sp<Sample> sample;
|
|
SoundChannel* channel;
|
|
int channelID;
|
|
|
|
Mutex::Autolock lock(&mLock);
|
|
|
|
if (mQuit) {
|
|
return 0;
|
|
}
|
|
// is sample ready?
|
|
sample = findSample(sampleID);
|
|
if ((sample == 0) || (sample->state() != Sample::READY)) {
|
|
LOGW(" sample %d not READY", sampleID);
|
|
return 0;
|
|
}
|
|
|
|
dump();
|
|
|
|
// allocate a channel
|
|
channel = allocateChannel_l(priority);
|
|
|
|
// no channel allocated - return 0
|
|
if (!channel) {
|
|
LOGV("No channel allocated");
|
|
return 0;
|
|
}
|
|
|
|
channelID = ++mNextChannelID;
|
|
|
|
LOGV("play channel %p state = %d", channel, channel->state());
|
|
channel->play(sample, channelID, leftVolume, rightVolume, priority, loop, rate);
|
|
return channelID;
|
|
}
|
|
|
|
SoundChannel* SoundPool::allocateChannel_l(int priority)
|
|
{
|
|
List<SoundChannel*>::iterator iter;
|
|
SoundChannel* channel = NULL;
|
|
|
|
// allocate a channel
|
|
if (!mChannels.empty()) {
|
|
iter = mChannels.begin();
|
|
if (priority >= (*iter)->priority()) {
|
|
channel = *iter;
|
|
mChannels.erase(iter);
|
|
LOGV("Allocated active channel");
|
|
}
|
|
}
|
|
|
|
// update priority and put it back in the list
|
|
if (channel) {
|
|
channel->setPriority(priority);
|
|
for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
|
|
if (priority < (*iter)->priority()) {
|
|
break;
|
|
}
|
|
}
|
|
mChannels.insert(iter, channel);
|
|
}
|
|
return channel;
|
|
}
|
|
|
|
// move a channel from its current position to the front of the list
|
|
void SoundPool::moveToFront_l(SoundChannel* channel)
|
|
{
|
|
for (List<SoundChannel*>::iterator iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
|
|
if (*iter == channel) {
|
|
mChannels.erase(iter);
|
|
mChannels.push_front(channel);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SoundPool::pause(int channelID)
|
|
{
|
|
LOGV("pause(%d)", channelID);
|
|
Mutex::Autolock lock(&mLock);
|
|
SoundChannel* channel = findChannel(channelID);
|
|
if (channel) {
|
|
channel->pause();
|
|
}
|
|
}
|
|
|
|
void SoundPool::autoPause()
|
|
{
|
|
LOGV("autoPause()");
|
|
Mutex::Autolock lock(&mLock);
|
|
for (int i = 0; i < mMaxChannels; ++i) {
|
|
SoundChannel* channel = &mChannelPool[i];
|
|
channel->autoPause();
|
|
}
|
|
}
|
|
|
|
void SoundPool::resume(int channelID)
|
|
{
|
|
LOGV("resume(%d)", channelID);
|
|
Mutex::Autolock lock(&mLock);
|
|
SoundChannel* channel = findChannel(channelID);
|
|
if (channel) {
|
|
channel->resume();
|
|
}
|
|
}
|
|
|
|
void SoundPool::autoResume()
|
|
{
|
|
LOGV("autoResume()");
|
|
Mutex::Autolock lock(&mLock);
|
|
for (int i = 0; i < mMaxChannels; ++i) {
|
|
SoundChannel* channel = &mChannelPool[i];
|
|
channel->autoResume();
|
|
}
|
|
}
|
|
|
|
void SoundPool::stop(int channelID)
|
|
{
|
|
LOGV("stop(%d)", channelID);
|
|
Mutex::Autolock lock(&mLock);
|
|
SoundChannel* channel = findChannel(channelID);
|
|
if (channel) {
|
|
channel->stop();
|
|
} else {
|
|
channel = findNextChannel(channelID);
|
|
if (channel)
|
|
channel->clearNextEvent();
|
|
}
|
|
}
|
|
|
|
void SoundPool::setVolume(int channelID, float leftVolume, float rightVolume)
|
|
{
|
|
Mutex::Autolock lock(&mLock);
|
|
SoundChannel* channel = findChannel(channelID);
|
|
if (channel) {
|
|
channel->setVolume(leftVolume, rightVolume);
|
|
}
|
|
}
|
|
|
|
void SoundPool::setPriority(int channelID, int priority)
|
|
{
|
|
LOGV("setPriority(%d, %d)", channelID, priority);
|
|
Mutex::Autolock lock(&mLock);
|
|
SoundChannel* channel = findChannel(channelID);
|
|
if (channel) {
|
|
channel->setPriority(priority);
|
|
}
|
|
}
|
|
|
|
void SoundPool::setLoop(int channelID, int loop)
|
|
{
|
|
LOGV("setLoop(%d, %d)", channelID, loop);
|
|
Mutex::Autolock lock(&mLock);
|
|
SoundChannel* channel = findChannel(channelID);
|
|
if (channel) {
|
|
channel->setLoop(loop);
|
|
}
|
|
}
|
|
|
|
void SoundPool::setRate(int channelID, float rate)
|
|
{
|
|
LOGV("setRate(%d, %f)", channelID, rate);
|
|
Mutex::Autolock lock(&mLock);
|
|
SoundChannel* channel = findChannel(channelID);
|
|
if (channel) {
|
|
channel->setRate(rate);
|
|
}
|
|
}
|
|
|
|
// call with lock held
|
|
void SoundPool::done_l(SoundChannel* channel)
|
|
{
|
|
LOGV("done_l(%d)", channel->channelID());
|
|
// if "stolen", play next event
|
|
if (channel->nextChannelID() != 0) {
|
|
LOGV("add to restart list");
|
|
addToRestartList(channel);
|
|
}
|
|
|
|
// return to idle state
|
|
else {
|
|
LOGV("move to front");
|
|
moveToFront_l(channel);
|
|
}
|
|
}
|
|
|
|
void SoundPool::setCallback(SoundPoolCallback* callback, void* user)
|
|
{
|
|
Mutex::Autolock lock(&mCallbackLock);
|
|
mCallback = callback;
|
|
mUserData = user;
|
|
}
|
|
|
|
void SoundPool::notify(SoundPoolEvent event)
|
|
{
|
|
Mutex::Autolock lock(&mCallbackLock);
|
|
if (mCallback != NULL) {
|
|
mCallback(event, this, mUserData);
|
|
}
|
|
}
|
|
|
|
void SoundPool::dump()
|
|
{
|
|
for (int i = 0; i < mMaxChannels; ++i) {
|
|
mChannelPool[i].dump();
|
|
}
|
|
}
|
|
|
|
|
|
Sample::Sample(int sampleID, const char* url)
|
|
{
|
|
init();
|
|
mSampleID = sampleID;
|
|
mUrl = strdup(url);
|
|
LOGV("create sampleID=%d, url=%s", mSampleID, mUrl);
|
|
}
|
|
|
|
Sample::Sample(int sampleID, int fd, int64_t offset, int64_t length)
|
|
{
|
|
init();
|
|
mSampleID = sampleID;
|
|
mFd = dup(fd);
|
|
mOffset = offset;
|
|
mLength = length;
|
|
LOGV("create sampleID=%d, fd=%d, offset=%lld, length=%lld", mSampleID, mFd, mLength, mOffset);
|
|
}
|
|
|
|
void Sample::init()
|
|
{
|
|
mData = 0;
|
|
mSize = 0;
|
|
mRefCount = 0;
|
|
mSampleID = 0;
|
|
mState = UNLOADED;
|
|
mFd = -1;
|
|
mOffset = 0;
|
|
mLength = 0;
|
|
mUrl = 0;
|
|
}
|
|
|
|
Sample::~Sample()
|
|
{
|
|
LOGV("Sample::destructor sampleID=%d, fd=%d", mSampleID, mFd);
|
|
if (mFd > 0) {
|
|
LOGV("close(%d)", mFd);
|
|
::close(mFd);
|
|
}
|
|
mData.clear();
|
|
delete mUrl;
|
|
}
|
|
|
|
status_t Sample::doLoad()
|
|
{
|
|
uint32_t sampleRate;
|
|
int numChannels;
|
|
int format;
|
|
sp<IMemory> p;
|
|
LOGV("Start decode");
|
|
if (mUrl) {
|
|
p = MediaPlayer::decode(mUrl, &sampleRate, &numChannels, &format);
|
|
} else {
|
|
p = MediaPlayer::decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format);
|
|
LOGV("close(%d)", mFd);
|
|
::close(mFd);
|
|
mFd = -1;
|
|
}
|
|
if (p == 0) {
|
|
LOGE("Unable to load sample: %s", mUrl);
|
|
return -1;
|
|
}
|
|
LOGV("pointer = %p, size = %u, sampleRate = %u, numChannels = %d",
|
|
p->pointer(), p->size(), sampleRate, numChannels);
|
|
|
|
if (sampleRate > kMaxSampleRate) {
|
|
LOGE("Sample rate (%u) out of range", sampleRate);
|
|
return - 1;
|
|
}
|
|
|
|
if ((numChannels < 1) || (numChannels > 2)) {
|
|
LOGE("Sample channel count (%d) out of range", numChannels);
|
|
return - 1;
|
|
}
|
|
|
|
//_dumpBuffer(p->pointer(), p->size());
|
|
uint8_t* q = static_cast<uint8_t*>(p->pointer()) + p->size() - 10;
|
|
//_dumpBuffer(q, 10, 10, false);
|
|
|
|
mData = p;
|
|
mSize = p->size();
|
|
mSampleRate = sampleRate;
|
|
mNumChannels = numChannels;
|
|
mFormat = format;
|
|
mState = READY;
|
|
return 0;
|
|
}
|
|
|
|
|
|
void SoundChannel::init(SoundPool* soundPool)
|
|
{
|
|
mSoundPool = soundPool;
|
|
}
|
|
|
|
// call with sound pool lock held
|
|
void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftVolume,
|
|
float rightVolume, int priority, int loop, float rate)
|
|
{
|
|
AudioTrack* oldTrack;
|
|
AudioTrack* newTrack;
|
|
status_t status;
|
|
|
|
{ // scope for the lock
|
|
Mutex::Autolock lock(&mLock);
|
|
|
|
LOGV("SoundChannel::play %p: sampleID=%d, channelID=%d, leftVolume=%f, rightVolume=%f,"
|
|
" priority=%d, loop=%d, rate=%f",
|
|
this, sample->sampleID(), nextChannelID, leftVolume, rightVolume,
|
|
priority, loop, rate);
|
|
|
|
// if not idle, this voice is being stolen
|
|
if (mState != IDLE) {
|
|
LOGV("channel %d stolen - event queued for channel %d", channelID(), nextChannelID);
|
|
mNextEvent.set(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
|
|
stop_l();
|
|
return;
|
|
}
|
|
|
|
// initialize track
|
|
int afFrameCount;
|
|
int afSampleRate;
|
|
int streamType = mSoundPool->streamType();
|
|
if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
|
|
afFrameCount = kDefaultFrameCount;
|
|
}
|
|
if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
|
|
afSampleRate = kDefaultSampleRate;
|
|
}
|
|
int numChannels = sample->numChannels();
|
|
uint32_t sampleRate = uint32_t(float(sample->sampleRate()) * rate + 0.5);
|
|
uint32_t totalFrames = (kDefaultBufferCount * afFrameCount * sampleRate) / afSampleRate;
|
|
uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
|
|
uint32_t frameCount = 0;
|
|
|
|
if (loop) {
|
|
frameCount = sample->size()/numChannels/
|
|
((sample->format() == AudioSystem::PCM_16_BIT) ? sizeof(int16_t) : sizeof(uint8_t));
|
|
}
|
|
|
|
#ifndef USE_SHARED_MEM_BUFFER
|
|
// Ensure minimum audio buffer size in case of short looped sample
|
|
if(frameCount < totalFrames) {
|
|
frameCount = totalFrames;
|
|
}
|
|
#endif
|
|
|
|
// mToggle toggles each time a track is started on a given channel.
|
|
// The toggle is concatenated with the SoundChannel address and passed to AudioTrack
|
|
// as callback user data. This enables the detection of callbacks received from the old
|
|
// audio track while the new one is being started and avoids processing them with
|
|
// wrong audio audio buffer size (mAudioBufferSize)
|
|
unsigned long toggle = mToggle ^ 1;
|
|
void *userData = (void *)((unsigned long)this | toggle);
|
|
uint32_t channels = (numChannels == 2) ?
|
|
AudioSystem::CHANNEL_OUT_STEREO : AudioSystem::CHANNEL_OUT_MONO;
|
|
|
|
// do not create a new audio track if current track is compatible with sample parameters
|
|
#ifdef USE_SHARED_MEM_BUFFER
|
|
newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
|
|
channels, sample->getIMemory(), 0, callback, userData);
|
|
#else
|
|
newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
|
|
channels, frameCount, 0, callback, userData, bufferFrames);
|
|
#endif
|
|
oldTrack = mAudioTrack;
|
|
status = newTrack->initCheck();
|
|
if (status != NO_ERROR) {
|
|
LOGE("Error creating AudioTrack");
|
|
goto exit;
|
|
}
|
|
LOGV("setVolume %p", newTrack);
|
|
newTrack->setVolume(leftVolume, rightVolume);
|
|
newTrack->setLoop(0, frameCount, loop);
|
|
|
|
// From now on, AudioTrack callbacks recevieved with previous toggle value will be ignored.
|
|
mToggle = toggle;
|
|
mAudioTrack = newTrack;
|
|
mPos = 0;
|
|
mSample = sample;
|
|
mChannelID = nextChannelID;
|
|
mPriority = priority;
|
|
mLoop = loop;
|
|
mLeftVolume = leftVolume;
|
|
mRightVolume = rightVolume;
|
|
mNumChannels = numChannels;
|
|
mRate = rate;
|
|
clearNextEvent();
|
|
mState = PLAYING;
|
|
mAudioTrack->start();
|
|
mAudioBufferSize = newTrack->frameCount()*newTrack->frameSize();
|
|
}
|
|
|
|
exit:
|
|
LOGV("delete oldTrack %p", oldTrack);
|
|
delete oldTrack;
|
|
if (status != NO_ERROR) {
|
|
delete newTrack;
|
|
mAudioTrack = NULL;
|
|
}
|
|
}
|
|
|
|
void SoundChannel::nextEvent()
|
|
{
|
|
sp<Sample> sample;
|
|
int nextChannelID;
|
|
float leftVolume;
|
|
float rightVolume;
|
|
int priority;
|
|
int loop;
|
|
float rate;
|
|
|
|
// check for valid event
|
|
{
|
|
Mutex::Autolock lock(&mLock);
|
|
nextChannelID = mNextEvent.channelID();
|
|
if (nextChannelID == 0) {
|
|
LOGV("stolen channel has no event");
|
|
return;
|
|
}
|
|
|
|
sample = mNextEvent.sample();
|
|
leftVolume = mNextEvent.leftVolume();
|
|
rightVolume = mNextEvent.rightVolume();
|
|
priority = mNextEvent.priority();
|
|
loop = mNextEvent.loop();
|
|
rate = mNextEvent.rate();
|
|
}
|
|
|
|
LOGV("Starting stolen channel %d -> %d", channelID(), nextChannelID);
|
|
play(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
|
|
}
|
|
|
|
void SoundChannel::callback(int event, void* user, void *info)
|
|
{
|
|
SoundChannel* channel = static_cast<SoundChannel*>((void *)((unsigned long)user & ~1));
|
|
|
|
channel->process(event, info, (unsigned long)user & 1);
|
|
}
|
|
|
|
void SoundChannel::process(int event, void *info, unsigned long toggle)
|
|
{
|
|
//LOGV("process(%d)", mChannelID);
|
|
|
|
Mutex::Autolock lock(&mLock);
|
|
|
|
AudioTrack::Buffer* b = NULL;
|
|
if (event == AudioTrack::EVENT_MORE_DATA) {
|
|
b = static_cast<AudioTrack::Buffer *>(info);
|
|
}
|
|
|
|
if (mToggle != toggle) {
|
|
LOGV("process wrong toggle %p channel %d", this, mChannelID);
|
|
if (b != NULL) {
|
|
b->size = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
sp<Sample> sample = mSample;
|
|
|
|
// LOGV("SoundChannel::process event %d", event);
|
|
|
|
if (event == AudioTrack::EVENT_MORE_DATA) {
|
|
|
|
// check for stop state
|
|
if (b->size == 0) return;
|
|
|
|
if (mState == IDLE) {
|
|
b->size = 0;
|
|
return;
|
|
}
|
|
|
|
if (sample != 0) {
|
|
// fill buffer
|
|
uint8_t* q = (uint8_t*) b->i8;
|
|
size_t count = 0;
|
|
|
|
if (mPos < (int)sample->size()) {
|
|
uint8_t* p = sample->data() + mPos;
|
|
count = sample->size() - mPos;
|
|
if (count > b->size) {
|
|
count = b->size;
|
|
}
|
|
memcpy(q, p, count);
|
|
// LOGV("fill: q=%p, p=%p, mPos=%u, b->size=%u, count=%d", q, p, mPos, b->size, count);
|
|
} else if (mPos < mAudioBufferSize) {
|
|
count = mAudioBufferSize - mPos;
|
|
if (count > b->size) {
|
|
count = b->size;
|
|
}
|
|
memset(q, 0, count);
|
|
// LOGV("fill extra: q=%p, mPos=%u, b->size=%u, count=%d", q, mPos, b->size, count);
|
|
}
|
|
|
|
mPos += count;
|
|
b->size = count;
|
|
//LOGV("buffer=%p, [0]=%d", b->i16, b->i16[0]);
|
|
}
|
|
} else if (event == AudioTrack::EVENT_UNDERRUN) {
|
|
LOGV("process %p channel %d EVENT_UNDERRUN", this, mChannelID);
|
|
mSoundPool->addToStopList(this);
|
|
} else if (event == AudioTrack::EVENT_LOOP_END) {
|
|
LOGV("End loop %p channel %d count %d", this, mChannelID, *(int *)info);
|
|
}
|
|
}
|
|
|
|
|
|
// call with lock held
|
|
bool SoundChannel::doStop_l()
|
|
{
|
|
if (mState != IDLE) {
|
|
setVolume_l(0, 0);
|
|
LOGV("stop");
|
|
mAudioTrack->stop();
|
|
mSample.clear();
|
|
mState = IDLE;
|
|
mPriority = IDLE_PRIORITY;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// call with lock held and sound pool lock held
|
|
void SoundChannel::stop_l()
|
|
{
|
|
if (doStop_l()) {
|
|
mSoundPool->done_l(this);
|
|
}
|
|
}
|
|
|
|
// call with sound pool lock held
|
|
void SoundChannel::stop()
|
|
{
|
|
bool stopped;
|
|
{
|
|
Mutex::Autolock lock(&mLock);
|
|
stopped = doStop_l();
|
|
}
|
|
|
|
if (stopped) {
|
|
mSoundPool->done_l(this);
|
|
}
|
|
}
|
|
|
|
//FIXME: Pause is a little broken right now
|
|
void SoundChannel::pause()
|
|
{
|
|
Mutex::Autolock lock(&mLock);
|
|
if (mState == PLAYING) {
|
|
LOGV("pause track");
|
|
mState = PAUSED;
|
|
mAudioTrack->pause();
|
|
}
|
|
}
|
|
|
|
void SoundChannel::autoPause()
|
|
{
|
|
Mutex::Autolock lock(&mLock);
|
|
if (mState == PLAYING) {
|
|
LOGV("pause track");
|
|
mState = PAUSED;
|
|
mAutoPaused = true;
|
|
mAudioTrack->pause();
|
|
}
|
|
}
|
|
|
|
void SoundChannel::resume()
|
|
{
|
|
Mutex::Autolock lock(&mLock);
|
|
if (mState == PAUSED) {
|
|
LOGV("resume track");
|
|
mState = PLAYING;
|
|
mAutoPaused = false;
|
|
mAudioTrack->start();
|
|
}
|
|
}
|
|
|
|
void SoundChannel::autoResume()
|
|
{
|
|
Mutex::Autolock lock(&mLock);
|
|
if (mAutoPaused && (mState == PAUSED)) {
|
|
LOGV("resume track");
|
|
mState = PLAYING;
|
|
mAutoPaused = false;
|
|
mAudioTrack->start();
|
|
}
|
|
}
|
|
|
|
void SoundChannel::setRate(float rate)
|
|
{
|
|
Mutex::Autolock lock(&mLock);
|
|
if (mAudioTrack != 0 && mSample.get() != 0) {
|
|
uint32_t sampleRate = uint32_t(float(mSample->sampleRate()) * rate + 0.5);
|
|
mAudioTrack->setSampleRate(sampleRate);
|
|
mRate = rate;
|
|
}
|
|
}
|
|
|
|
// call with lock held
|
|
void SoundChannel::setVolume_l(float leftVolume, float rightVolume)
|
|
{
|
|
mLeftVolume = leftVolume;
|
|
mRightVolume = rightVolume;
|
|
if (mAudioTrack != 0) mAudioTrack->setVolume(leftVolume, rightVolume);
|
|
}
|
|
|
|
void SoundChannel::setVolume(float leftVolume, float rightVolume)
|
|
{
|
|
Mutex::Autolock lock(&mLock);
|
|
setVolume_l(leftVolume, rightVolume);
|
|
}
|
|
|
|
void SoundChannel::setLoop(int loop)
|
|
{
|
|
Mutex::Autolock lock(&mLock);
|
|
if (mAudioTrack != 0 && mSample.get() != 0) {
|
|
uint32_t loopEnd = mSample->size()/mNumChannels/
|
|
((mSample->format() == AudioSystem::PCM_16_BIT) ? sizeof(int16_t) : sizeof(uint8_t));
|
|
mAudioTrack->setLoop(0, loopEnd, loop);
|
|
mLoop = loop;
|
|
}
|
|
}
|
|
|
|
SoundChannel::~SoundChannel()
|
|
{
|
|
LOGV("SoundChannel destructor %p", this);
|
|
{
|
|
Mutex::Autolock lock(&mLock);
|
|
clearNextEvent();
|
|
doStop_l();
|
|
}
|
|
// do not call AudioTrack destructor with mLock held as it will wait for the AudioTrack
|
|
// callback thread to exit which may need to execute process() and acquire the mLock.
|
|
delete mAudioTrack;
|
|
}
|
|
|
|
void SoundChannel::dump()
|
|
{
|
|
LOGV("mState = %d mChannelID=%d, mNumChannels=%d, mPos = %d, mPriority=%d, mLoop=%d",
|
|
mState, mChannelID, mNumChannels, mPos, mPriority, mLoop);
|
|
}
|
|
|
|
void SoundEvent::set(const sp<Sample>& sample, int channelID, float leftVolume,
|
|
float rightVolume, int priority, int loop, float rate)
|
|
{
|
|
mSample = sample;
|
|
mChannelID = channelID;
|
|
mLeftVolume = leftVolume;
|
|
mRightVolume = rightVolume;
|
|
mPriority = priority;
|
|
mLoop = loop;
|
|
mRate =rate;
|
|
}
|
|
|
|
} // end namespace android
|