blob: b19779820c157289671ca2241fc04485cbe6c5a0 [file] [log] [blame]
/*
* Copyright (C) 2017 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_TAG "AAudioService"
//#define LOG_NDEBUG 0
#include <utils/Log.h>
#include <assert.h>
#include <map>
#include <mutex>
#include <utils/Singleton.h>
#include "AAudioEndpointManager.h"
#include "AAudioServiceEndpoint.h"
#include <algorithm>
#include <mutex>
#include <vector>
#include "core/AudioStreamBuilder.h"
#include "AAudioServiceEndpoint.h"
#include "AAudioServiceStreamShared.h"
using namespace android; // TODO just import names needed
using namespace aaudio; // TODO just import names needed
#define MIN_TIMEOUT_NANOS (1000 * AAUDIO_NANOS_PER_MILLISECOND)
// Wait at least this many times longer than the operation should take.
#define MIN_TIMEOUT_OPERATIONS 4
// This is the maximum size in frames. The effective size can be tuned smaller at runtime.
#define DEFAULT_BUFFER_CAPACITY (48 * 8)
// Use 2 for "double buffered"
#define BUFFER_SIZE_IN_BURSTS 2
// The mStreamInternal will use a service interface that does not go through Binder.
AAudioServiceEndpoint::AAudioServiceEndpoint(AAudioService &audioService)
: mStreamInternal(audioService, true)
{
}
AAudioServiceEndpoint::~AAudioServiceEndpoint() {
}
// Set up an EXCLUSIVE MMAP stream that will be shared.
aaudio_result_t AAudioServiceEndpoint::open(int32_t deviceId, aaudio_direction_t direction) {
AudioStreamBuilder builder;
builder.setSharingMode(AAUDIO_SHARING_MODE_EXCLUSIVE);
// Don't fall back to SHARED because that would cause recursion.
builder.setSharingModeMatchRequired(true);
builder.setDeviceId(deviceId);
builder.setDirection(direction);
builder.setBufferCapacity(DEFAULT_BUFFER_CAPACITY);
aaudio_result_t result = mStreamInternal.open(builder);
if (result == AAUDIO_OK) {
mMixer.allocate(mStreamInternal.getSamplesPerFrame(), mStreamInternal.getFramesPerBurst());
int32_t desiredBufferSize = BUFFER_SIZE_IN_BURSTS * mStreamInternal.getFramesPerBurst();
mStreamInternal.setBufferSize(desiredBufferSize);
}
return result;
}
aaudio_result_t AAudioServiceEndpoint::close() {
return mStreamInternal.close();
}
// TODO, maybe use an interface to reduce exposure
aaudio_result_t AAudioServiceEndpoint::registerStream(AAudioServiceStreamShared *sharedStream) {
std::lock_guard<std::mutex> lock(mLockStreams);
mRegisteredStreams.push_back(sharedStream);
return AAUDIO_OK;
}
aaudio_result_t AAudioServiceEndpoint::unregisterStream(AAudioServiceStreamShared *sharedStream) {
std::lock_guard<std::mutex> lock(mLockStreams);
mRegisteredStreams.erase(std::remove(mRegisteredStreams.begin(), mRegisteredStreams.end(), sharedStream),
mRegisteredStreams.end());
return AAUDIO_OK;
}
aaudio_result_t AAudioServiceEndpoint::startStream(AAudioServiceStreamShared *sharedStream) {
// TODO use real-time technique to avoid mutex, eg. atomic command FIFO
std::lock_guard<std::mutex> lock(mLockStreams);
mRunningStreams.push_back(sharedStream);
if (mRunningStreams.size() == 1) {
startMixer_l();
}
return AAUDIO_OK;
}
aaudio_result_t AAudioServiceEndpoint::stopStream(AAudioServiceStreamShared *sharedStream) {
std::lock_guard<std::mutex> lock(mLockStreams);
mRunningStreams.erase(std::remove(mRunningStreams.begin(), mRunningStreams.end(), sharedStream),
mRunningStreams.end());
if (mRunningStreams.size() == 0) {
stopMixer_l();
}
return AAUDIO_OK;
}
static void *aaudio_mixer_thread_proc(void *context) {
AAudioServiceEndpoint *stream = (AAudioServiceEndpoint *) context;
//LOGD("AudioStreamAAudio(): oboe_callback_thread, stream = %p", stream);
if (stream != NULL) {
return stream->callbackLoop();
} else {
return NULL;
}
}
// Render audio in the application callback and then write the data to the stream.
void *AAudioServiceEndpoint::callbackLoop() {
ALOGD("AAudioServiceEndpoint(): callbackLoop() entering");
int32_t underflowCount = 0;
aaudio_result_t result = mStreamInternal.requestStart();
// result might be a frame count
while (mCallbackEnabled.load() && mStreamInternal.isPlaying() && (result >= 0)) {
// Mix data from each active stream.
{
mMixer.clear();
std::lock_guard<std::mutex> lock(mLockStreams);
for(AAudioServiceStreamShared *sharedStream : mRunningStreams) {
FifoBuffer *fifo = sharedStream->getDataFifoBuffer();
float volume = 0.5; // TODO get from system
bool underflowed = mMixer.mix(fifo, volume);
underflowCount += underflowed ? 1 : 0;
// TODO log underflows in each stream
sharedStream->markTransferTime(AudioClock::getNanoseconds());
}
}
// Write audio data to stream using a blocking write.
int64_t timeoutNanos = calculateReasonableTimeout(mStreamInternal.getFramesPerBurst());
result = mStreamInternal.write(mMixer.getOutputBuffer(), getFramesPerBurst(), timeoutNanos);
if (result == AAUDIO_ERROR_DISCONNECTED) {
disconnectRegisteredStreams();
break;
} else if (result != getFramesPerBurst()) {
ALOGW("AAudioServiceEndpoint(): callbackLoop() wrote %d / %d",
result, getFramesPerBurst());
break;
}
}
result = mStreamInternal.requestStop();
ALOGD("AAudioServiceEndpoint(): callbackLoop() exiting, %d underflows", underflowCount);
return NULL; // TODO review
}
aaudio_result_t AAudioServiceEndpoint::startMixer_l() {
// Launch the callback loop thread.
int64_t periodNanos = mStreamInternal.getFramesPerBurst()
* AAUDIO_NANOS_PER_SECOND
/ getSampleRate();
mCallbackEnabled.store(true);
return mStreamInternal.createThread(periodNanos, aaudio_mixer_thread_proc, this);
}
aaudio_result_t AAudioServiceEndpoint::stopMixer_l() {
mCallbackEnabled.store(false);
return mStreamInternal.joinThread(NULL, calculateReasonableTimeout(mStreamInternal.getFramesPerBurst()));
}
// TODO Call method in AudioStreamInternal when that callback CL is merged.
int64_t AAudioServiceEndpoint::calculateReasonableTimeout(int32_t framesPerOperation) {
// Wait for at least a second or some number of callbacks to join the thread.
int64_t timeoutNanoseconds = (MIN_TIMEOUT_OPERATIONS * framesPerOperation * AAUDIO_NANOS_PER_SECOND)
/ getSampleRate();
if (timeoutNanoseconds < MIN_TIMEOUT_NANOS) { // arbitrary number of seconds
timeoutNanoseconds = MIN_TIMEOUT_NANOS;
}
return timeoutNanoseconds;
}
void AAudioServiceEndpoint::disconnectRegisteredStreams() {
std::lock_guard<std::mutex> lock(mLockStreams);
for(AAudioServiceStreamShared *sharedStream : mRunningStreams) {
sharedStream->onStop();
}
mRunningStreams.clear();
for(AAudioServiceStreamShared *sharedStream : mRegisteredStreams) {
sharedStream->onDisconnect();
}
mRegisteredStreams.clear();
}