/* * Copyright 2020 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 #include "OboePlayer.h" #include "WaveTableSource.h" #include "AudioSource.h" static const char * const TAG = "OboePlayer(native)"; using namespace oboe; constexpr int32_t kBufferSizeInBursts = 2; // Use 2 bursts as the buffer size (double buffer) ChannelMask OboePlayer::javaChannelMaskToOboeChannelMask(int32_t javaMask) { return (ChannelMask) (javaMask >> 2); } int32_t OboePlayer::javaChannelMaskToChannelCount(int32_t javaMask) { // return the count of 1 bits return __builtin_popcount(static_cast(javaMask)); } OboePlayer::OboePlayer(JNIEnv *env, AudioSource* source, int subtype) : Player(source, subtype) { env->GetJavaVM(&mJvm); jclass clsAudioTimestamp = env->FindClass("android/media/AudioTimestamp"); mFidFramePosition = env->GetFieldID(clsAudioTimestamp, "framePosition", "J"); mFidNanoTime = env->GetFieldID(clsAudioTimestamp, "nanoTime", "J"); } DataCallbackResult OboePlayer::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) { StreamState streamState = oboeStream->getState(); if (streamState != StreamState::Open && streamState != StreamState::Started) { __android_log_print(ANDROID_LOG_ERROR, TAG, " streamState:%d", static_cast(streamState)); } if (streamState == StreamState::Disconnected) { __android_log_print(ANDROID_LOG_ERROR, TAG, " streamState::Disconnected"); } // Pull the data here! int numFramesRead = mAudioSource->pull((float*)audioData, numFrames, mNumExchangeChannels); // may need to handle 0-filling if numFramesRead < numFrames return numFramesRead != 0 ? DataCallbackResult::Continue : DataCallbackResult::Stop; } void OboePlayer::onErrorAfterClose(AudioStream *oboeStream, oboe::Result error) { } void OboePlayer::onErrorBeforeClose(AudioStream *, oboe::Result error) { } StreamBase::Result OboePlayer::setupStream(int32_t channelCount, int32_t channelMask, int32_t sampleRate, int32_t performanceMode, int32_t sharingMode, int32_t routeDeviceId) { oboe::Result result = oboe::Result::ErrorInternal; if (mAudioStream != nullptr) { return ERROR_INVALID_STATE; } else { std::lock_guard lock(mStreamLock); mChannelCount = channelCount; mChannelMask = channelMask; mSampleRate = sampleRate; mRouteDeviceId = routeDeviceId; // Create an audio stream AudioStreamBuilder builder; if (mChannelCount != 0) { builder.setChannelCount(mChannelCount); mNumExchangeChannels = mChannelCount; } else { builder.setChannelMask(javaChannelMaskToOboeChannelMask(mChannelMask)); mNumExchangeChannels = javaChannelMaskToChannelCount(mChannelMask); } builder.setSampleRate(mSampleRate); builder.setCallback(this); builder.setSampleRateConversionQuality(SampleRateConversionQuality::None); builder.setDirection(Direction::Output); switch (mSubtype) { case SUB_TYPE_OBOE_AAUDIO: builder.setAudioApi(AudioApi::AAudio); break; case SUB_TYPE_OBOE_OPENSL_ES: builder.setAudioApi(AudioApi::OpenSLES); break; } builder.setPerformanceMode((PerformanceMode) performanceMode); builder.setSharingMode((SharingMode) sharingMode); if (mRouteDeviceId != ROUTING_DEVICE_NONE) { builder.setDeviceId(mRouteDeviceId); } result = builder.openStream(mAudioStream); if (result != oboe::Result::OK){ __android_log_print( ANDROID_LOG_ERROR, TAG, "openStream failed. Error: %s", convertToText(result)); } else { // Reduce stream latency by setting the buffer size to a multiple of the burst size // Note: this will fail with ErrorUnimplemented if we are using a callback with // OpenSL ES. See oboe::AudioStreamBuffered::setBufferSizeInFrames // This doesn't affect the success of opening the stream. int32_t desiredSize = mAudioStream->getFramesPerBurst() * kBufferSizeInBursts; mAudioStream->setBufferSizeInFrames(desiredSize); mAudioSource->init(desiredSize , mNumExchangeChannels); } } __android_log_print(ANDROID_LOG_INFO, TAG, " Done - error:%d", static_cast(result)); return OboeErrorToMegaAudioError(result); } StreamBase::Result OboePlayer::startStream() { StreamBase::Result result = Player::startStream(); return result; } bool OboePlayer::getJavaTimestamp(jobject timestampObj) { oboe::FrameTimestamp nativeStamp; StreamBase::Result result = Player::getTimeStamp(&nativeStamp); if (result == OK) { JNIEnv* env; mJvm->AttachCurrentThread(&env, NULL); env->SetLongField(timestampObj, mFidFramePosition, nativeStamp.position); env->SetLongField(timestampObj, mFidNanoTime, nativeStamp.timestamp); } return result == OK; } int OboePlayer::getLastErrorCallbackResult() { return (int)(mAudioStream->getLastErrorCallbackResult()); } // // JNI functions // #include extern "C" { JNIEXPORT JNICALL jlong Java_org_hyphonate_megaaudio_player_OboePlayer_allocNativePlayer( JNIEnv *env, jobject thiz, jlong native_audio_source, jint playerSubtype) { return (jlong)new OboePlayer(env, (AudioSource*)native_audio_source, playerSubtype); } JNIEXPORT jint JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_setupStreamN( JNIEnv *env, jobject thiz, jlong native_player, jint channel_count, jint channel_mask, jint sample_rate, jint performanceMode, jint sharingMode, jint routeDeviceId) { OboePlayer* player = (OboePlayer*)native_player; return player->setupStream(channel_count, channel_mask, sample_rate, performanceMode, sharingMode, routeDeviceId); } JNIEXPORT int JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_teardownStreamN( JNIEnv *env, jobject thiz, jlong native_player) { OboePlayer* player = (OboePlayer*)native_player; return player->teardownStream(); } JNIEXPORT JNICALL jint Java_org_hyphonate_megaaudio_player_OboePlayer_startStreamN( JNIEnv *env, jobject thiz, jlong native_player, jint playerSubtype) { return ((OboePlayer*)(native_player))->startStream(); } JNIEXPORT JNICALL jint Java_org_hyphonate_megaaudio_player_OboePlayer_stopN(JNIEnv *env, jobject thiz, jlong native_player) { return ((OboePlayer*)(native_player))->stopStream(); } JNIEXPORT jint JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_getBufferFrameCountN(JNIEnv *env, jobject thiz, jlong native_player) { return ((OboePlayer*)(native_player))->getNumBufferFrames(); } JNIEXPORT jint JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_getRoutedDeviceIdN( JNIEnv *env, jobject thiz, jlong native_player) { return ((OboePlayer*)(native_player))->getRoutedDeviceId(); } JNIEXPORT jint JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_getSharingModeN( JNIEnv *env, jobject thiz, jlong native_player) { return ((OboePlayer*)(native_player))->getSharingMode(); } JNIEXPORT jint JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_getChannelCountN( JNIEnv *env, jobject thiz, jlong native_player) { return ((OboePlayer*)(native_player))->getChannelCount(); } JNIEXPORT jboolean JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_isMMapN( JNIEnv *env, jobject thiz, jlong native_player) { return ((OboePlayer*)(native_player))->isMMap(); } JNIEXPORT jboolean JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_getTimestampN( JNIEnv *env, jobject thiz, jlong native_player, jobject timestamp) { return ((OboePlayer*)native_player)->getJavaTimestamp(timestamp); } JNIEXPORT jint JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_getStreamStateN( JNIEnv *env, jobject thiz, jlong native_player) { return (int)((OboePlayer*)(native_player))->getState(); } JNIEXPORT jint JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_getLastErrorCallbackResultN( JNIEnv *env, jobject thiz, jlong native_player) { return (int)((OboePlayer*)(native_player))->getLastErrorCallbackResult(); } } // extern "C"