| /* |
| * 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. |
| */ |
| |
| #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 |
| |
| // 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); |
| builder.setDeviceId(deviceId); |
| builder.setDirection(direction); |
| aaudio_result_t result = mStreamInternal.open(builder); |
| if (result == AAUDIO_OK) { |
| mMixer.allocate(mStreamInternal.getSamplesPerFrame(), mStreamInternal.getFramesPerBurst()); |
| } |
| 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) { |
| ALOGD("AAudioServiceEndpoint::registerStream(%p)", sharedStream); |
| // TODO use real-time technique to avoid mutex, eg. atomic command FIFO |
| std::lock_guard<std::mutex> lock(mLockStreams); |
| mRegisteredStreams.push_back(sharedStream); |
| return AAUDIO_OK; |
| } |
| |
| aaudio_result_t AAudioServiceEndpoint::unregisterStream(AAudioServiceStreamShared *sharedStream) { |
| ALOGD("AAudioServiceEndpoint::unregisterStream(%p)", 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 |
| ALOGD("AAudioServiceEndpoint(): startStream() entering"); |
| 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() { |
| aaudio_result_t result = AAUDIO_OK; |
| |
| ALOGD("AAudioServiceEndpoint(): callbackLoop() entering"); |
| |
| result = mStreamInternal.requestStart(); |
| ALOGD("AAudioServiceEndpoint(): callbackLoop() after requestStart() %d, isPlaying() = %d", |
| result, (int) mStreamInternal.isPlaying()); |
| |
| // 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 |
| mMixer.mix(fifo, volume); |
| } |
| } |
| |
| // Write audio data to stream using a blocking write. |
| ALOGD("AAudioServiceEndpoint(): callbackLoop() write(%d)", getFramesPerBurst()); |
| 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; |
| } |
| } |
| |
| ALOGD("AAudioServiceEndpoint(): callbackLoop() exiting, result = %d, isPlaying() = %d", |
| result, (int) mStreamInternal.isPlaying()); |
| |
| result = mStreamInternal.requestStop(); |
| |
| 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(); |
| } |