Move dead submodules in-tree

Signed-off-by: swurl <swurl@swurl.xyz>
This commit is contained in:
swurl 2025-05-31 02:33:02 -04:00
parent c0cceff365
commit 6c655321e6
No known key found for this signature in database
GPG key ID: A5A7629F109C8FD1
4081 changed files with 1185566 additions and 45 deletions

View file

@ -0,0 +1,36 @@
LiveEffect Sample
============
This sample simply loops audio from input stream to output stream to demonstrate
the usage of the 2 stream interfaces.
Screenshots
-----------
![Screenshot](screenshot.png)
### Stream Configurations
- 48kHz
- oboe::I16
- stereo or mono
### Customizing the App
If you want to customize the effects processing then modify the
onBothStreamsReady() method in "src/main/cpp/FullDuplexPass.h"
### Caveats
OpenES SL does not allow setting the recording or playback device.
Synchronizing input and output streams for full-duplex operation is tricky. 
Input and output have different startup times. The input side may have to charge up the microphone circuit.
Also the initial timing for the output callback may be bursty as it fills the buffer up.
So when the output stream makes its first callback, the input buffer may be overflowing or empty or partially full.
In order to get into sync we go through a few phases.
* In Phase 1 we always drain the input buffer as much as possible, more than the output callback asks for. When we have done this for a while, we move to phase 2.
* In Phase 2 we optionally skip reading the input once to allow it to fill up with one burst. This makes it less likely to underflow on future reads.
* In Phase 3 we should be in a stable situation where the output is nearly full and the input is nearly empty.  You should be able to run for hours like this with no glitches.

View file

@ -0,0 +1,43 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 35
defaultConfig {
applicationId 'com.google.oboe.samples.liveeffect'
minSdkVersion 21
targetSdkVersion 35
versionCode 1
versionName '1.0'
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
externalNativeBuild {
cmake {
arguments '-DANDROID_TOOLCHAIN=clang'
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_18
targetCompatibility JavaVersion.VERSION_18
}
externalNativeBuild {
cmake {
path 'src/main/cpp/CMakeLists.txt'
}
}
namespace 'com.google.oboe.samples.liveEffect'
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation project(':audio-device')
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.microphone" android:required="true" />
<uses-feature android:name="android.hardware.audio.output" android:required="true" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<application
android:allowBackup="false"
android:fullBackupContent="false"
android:supportsRtl="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.google.oboe.samples.liveEffect.MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".DuplexStreamForegroundService"
android:foregroundServiceType="mediaPlayback|microphone"
android:exported="false">
</service>
</application>
</manifest>

View file

@ -0,0 +1,46 @@
#
# Copyright 2018 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.
#
cmake_minimum_required(VERSION 3.4.1)
project(liveEffect LANGUAGES C CXX)
get_filename_component(SAMPLE_ROOT_DIR
${CMAKE_CURRENT_SOURCE_DIR}/../../../.. ABSOLUTE)
### INCLUDE OBOE LIBRARY ###
set (OBOE_DIR ${SAMPLE_ROOT_DIR}/..)
add_subdirectory(${OBOE_DIR} ./oboe-bin)
add_library(liveEffect
SHARED
LiveEffectEngine.cpp
jni_bridge.cpp
${SAMPLE_ROOT_DIR}/debug-utils/trace.cpp)
target_include_directories(liveEffect
PRIVATE
${SAMPLE_ROOT_DIR}/debug-utils
${OBOE_DIR}/include)
target_link_libraries(liveEffect
PRIVATE
oboe
android
atomic
log)
target_link_options(liveEffect PRIVATE "-Wl,-z,max-page-size=16384")
# Enable optimization flags: if having problems with source level debugging,
# disable -Ofast ( and debug ), re-enable it after done debugging.
target_compile_options(liveEffect PRIVATE -Wall -Werror "$<$<CONFIG:RELEASE>:-Ofast>")

View file

@ -0,0 +1,54 @@
/*
* Copyright 2018 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.
*/
#ifndef SAMPLES_FULLDUPLEXPASS_H
#define SAMPLES_FULLDUPLEXPASS_H
class FullDuplexPass : public oboe::FullDuplexStream {
public:
virtual oboe::DataCallbackResult
onBothStreamsReady(
const void *inputData,
int numInputFrames,
void *outputData,
int numOutputFrames) {
// Copy the input samples to the output with a little arbitrary gain change.
// This code assumes the data format for both streams is Float.
const float *inputFloats = static_cast<const float *>(inputData);
float *outputFloats = static_cast<float *>(outputData);
// It also assumes the channel count for each stream is the same.
int32_t samplesPerFrame = getOutputStream()->getChannelCount();
int32_t numInputSamples = numInputFrames * samplesPerFrame;
int32_t numOutputSamples = numOutputFrames * samplesPerFrame;
// It is possible that there may be fewer input than output samples.
int32_t samplesToProcess = std::min(numInputSamples, numOutputSamples);
for (int32_t i = 0; i < samplesToProcess; i++) {
*outputFloats++ = *inputFloats++ * 0.95; // do some arbitrary processing
}
// If there are fewer input samples then clear the rest of the buffer.
int32_t samplesLeft = numOutputSamples - numInputSamples;
for (int32_t i = 0; i < samplesLeft; i++) {
*outputFloats++ = 0.0; // silence
}
return oboe::DataCallbackResult::Continue;
}
};
#endif //SAMPLES_FULLDUPLEXPASS_H

View file

@ -0,0 +1,245 @@
/**
* Copyright 2018 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 <cassert>
#include <logging_macros.h>
#include "LiveEffectEngine.h"
LiveEffectEngine::LiveEffectEngine() {
assert(mOutputChannelCount == mInputChannelCount);
}
void LiveEffectEngine::setRecordingDeviceId(int32_t deviceId) {
mRecordingDeviceId = deviceId;
}
void LiveEffectEngine::setPlaybackDeviceId(int32_t deviceId) {
mPlaybackDeviceId = deviceId;
}
bool LiveEffectEngine::isAAudioRecommended() {
return oboe::AudioStreamBuilder::isAAudioRecommended();
}
bool LiveEffectEngine::setAudioApi(oboe::AudioApi api) {
if (mIsEffectOn) return false;
mAudioApi = api;
return true;
}
bool LiveEffectEngine::setEffectOn(bool isOn) {
bool success = true;
if (isOn != mIsEffectOn) {
if (isOn) {
success = openStreams() == oboe::Result::OK;
if (success) {
mIsEffectOn = isOn;
}
} else {
closeStreams();
mIsEffectOn = isOn;
}
}
return success;
}
void LiveEffectEngine::closeStreams() {
/*
* Note: The order of events is important here.
* The playback stream must be closed before the recording stream. If the
* recording stream were to be closed first the playback stream's
* callback may attempt to read from the recording stream
* which would cause the app to crash since the recording stream would be
* null.
*/
mDuplexStream->stop();
closeStream(mPlayStream);
closeStream(mRecordingStream);
mDuplexStream.reset();
}
oboe::Result LiveEffectEngine::openStreams() {
// Note: The order of stream creation is important. We create the playback
// stream first, then use properties from the playback stream
// (e.g. sample rate) to create the recording stream. By matching the
// properties we should get the lowest latency path
oboe::AudioStreamBuilder inBuilder, outBuilder;
setupPlaybackStreamParameters(&outBuilder);
oboe::Result result = outBuilder.openStream(mPlayStream);
if (result != oboe::Result::OK) {
LOGE("Failed to open output stream. Error %s", oboe::convertToText(result));
mSampleRate = oboe::kUnspecified;
return result;
} else {
// The input stream needs to run at the same sample rate as the output.
mSampleRate = mPlayStream->getSampleRate();
}
warnIfNotLowLatency(mPlayStream);
setupRecordingStreamParameters(&inBuilder, mSampleRate);
result = inBuilder.openStream(mRecordingStream);
if (result != oboe::Result::OK) {
LOGE("Failed to open input stream. Error %s", oboe::convertToText(result));
closeStream(mPlayStream);
return result;
}
warnIfNotLowLatency(mRecordingStream);
mDuplexStream = std::make_unique<FullDuplexPass>();
mDuplexStream->setSharedInputStream(mRecordingStream);
mDuplexStream->setSharedOutputStream(mPlayStream);
mDuplexStream->start();
return result;
}
/**
* Sets the stream parameters which are specific to recording,
* including the sample rate which is determined from the
* playback stream.
*
* @param builder The recording stream builder
* @param sampleRate The desired sample rate of the recording stream
*/
oboe::AudioStreamBuilder *LiveEffectEngine::setupRecordingStreamParameters(
oboe::AudioStreamBuilder *builder, int32_t sampleRate) {
// This sample uses blocking read() because we don't specify a callback
builder->setDeviceId(mRecordingDeviceId)
->setDirection(oboe::Direction::Input)
->setSampleRate(sampleRate)
->setChannelCount(mInputChannelCount);
return setupCommonStreamParameters(builder);
}
/**
* Sets the stream parameters which are specific to playback, including device
* id and the dataCallback function, which must be set for low latency
* playback.
* @param builder The playback stream builder
*/
oboe::AudioStreamBuilder *LiveEffectEngine::setupPlaybackStreamParameters(
oboe::AudioStreamBuilder *builder) {
builder->setDataCallback(this)
->setErrorCallback(this)
->setDeviceId(mPlaybackDeviceId)
->setDirection(oboe::Direction::Output)
->setChannelCount(mOutputChannelCount);
return setupCommonStreamParameters(builder);
}
/**
* Set the stream parameters which are common to both recording and playback
* streams.
* @param builder The playback or recording stream builder
*/
oboe::AudioStreamBuilder *LiveEffectEngine::setupCommonStreamParameters(
oboe::AudioStreamBuilder *builder) {
// We request EXCLUSIVE mode since this will give us the lowest possible
// latency.
// If EXCLUSIVE mode isn't available the builder will fall back to SHARED
// mode.
builder->setAudioApi(mAudioApi)
->setFormat(mFormat)
->setFormatConversionAllowed(true)
->setSharingMode(oboe::SharingMode::Exclusive)
->setPerformanceMode(oboe::PerformanceMode::LowLatency);
return builder;
}
/**
* Close the stream. AudioStream::close() is a blocking call so
* the application does not need to add synchronization between
* onAudioReady() function and the thread calling close().
* [the closing thread is the UI thread in this sample].
* @param stream the stream to close
*/
void LiveEffectEngine::closeStream(std::shared_ptr<oboe::AudioStream> &stream) {
if (stream) {
oboe::Result result = stream->stop();
if (result != oboe::Result::OK) {
LOGW("Error stopping stream: %s", oboe::convertToText(result));
}
result = stream->close();
if (result != oboe::Result::OK) {
LOGE("Error closing stream: %s", oboe::convertToText(result));
} else {
LOGW("Successfully closed streams");
}
stream.reset();
}
}
/**
* Warn in logcat if non-low latency stream is created
* @param stream: newly created stream
*
*/
void LiveEffectEngine::warnIfNotLowLatency(std::shared_ptr<oboe::AudioStream> &stream) {
if (stream->getPerformanceMode() != oboe::PerformanceMode::LowLatency) {
LOGW(
"Stream is NOT low latency."
"Check your requested format, sample rate and channel count");
}
}
/**
* Handles playback stream's audio request. In this sample, we simply block-read
* from the record stream for the required samples.
*
* @param oboeStream: the playback stream that requesting additional samples
* @param audioData: the buffer to load audio samples for playback stream
* @param numFrames: number of frames to load to audioData buffer
* @return: DataCallbackResult::Continue.
*/
oboe::DataCallbackResult LiveEffectEngine::onAudioReady(
oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) {
return mDuplexStream->onAudioReady(oboeStream, audioData, numFrames);
}
/**
* Oboe notifies the application for "about to close the stream".
*
* @param oboeStream: the stream to close
* @param error: oboe's reason for closing the stream
*/
void LiveEffectEngine::onErrorBeforeClose(oboe::AudioStream *oboeStream,
oboe::Result error) {
LOGE("%s stream Error before close: %s",
oboe::convertToText(oboeStream->getDirection()),
oboe::convertToText(error));
}
/**
* Oboe notifies application that "the stream is closed"
*
* @param oboeStream
* @param error
*/
void LiveEffectEngine::onErrorAfterClose(oboe::AudioStream *oboeStream,
oboe::Result error) {
LOGE("%s stream Error after close: %s",
oboe::convertToText(oboeStream->getDirection()),
oboe::convertToText(error));
closeStreams();
// Restart the stream if the error is a disconnect.
if (error == oboe::Result::ErrorDisconnected) {
LOGI("Restarting AudioStream");
openStreams();
}
}

View file

@ -0,0 +1,83 @@
/*
* Copyright 2018 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.
*/
#ifndef OBOE_LIVEEFFECTENGINE_H
#define OBOE_LIVEEFFECTENGINE_H
#include <jni.h>
#include <oboe/Oboe.h>
#include <string>
#include <thread>
#include "FullDuplexPass.h"
class LiveEffectEngine : public oboe::AudioStreamCallback {
public:
LiveEffectEngine();
void setRecordingDeviceId(int32_t deviceId);
void setPlaybackDeviceId(int32_t deviceId);
/**
* @param isOn
* @return true if it succeeds
*/
bool setEffectOn(bool isOn);
/*
* oboe::AudioStreamDataCallback interface implementation
*/
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream,
void *audioData, int32_t numFrames) override;
/*
* oboe::AudioStreamErrorCallback interface implementation
*/
void onErrorBeforeClose(oboe::AudioStream *oboeStream, oboe::Result error) override;
void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override;
bool setAudioApi(oboe::AudioApi);
bool isAAudioRecommended(void);
private:
bool mIsEffectOn = false;
int32_t mRecordingDeviceId = oboe::kUnspecified;
int32_t mPlaybackDeviceId = oboe::kUnspecified;
const oboe::AudioFormat mFormat = oboe::AudioFormat::Float; // for easier processing
oboe::AudioApi mAudioApi = oboe::AudioApi::AAudio;
int32_t mSampleRate = oboe::kUnspecified;
const int32_t mInputChannelCount = oboe::ChannelCount::Stereo;
const int32_t mOutputChannelCount = oboe::ChannelCount::Stereo;
std::unique_ptr<FullDuplexPass> mDuplexStream;
std::shared_ptr<oboe::AudioStream> mRecordingStream;
std::shared_ptr<oboe::AudioStream> mPlayStream;
oboe::Result openStreams();
void closeStreams();
void closeStream(std::shared_ptr<oboe::AudioStream> &stream);
oboe::AudioStreamBuilder *setupCommonStreamParameters(
oboe::AudioStreamBuilder *builder);
oboe::AudioStreamBuilder *setupRecordingStreamParameters(
oboe::AudioStreamBuilder *builder, int32_t sampleRate);
oboe::AudioStreamBuilder *setupPlaybackStreamParameters(
oboe::AudioStreamBuilder *builder);
void warnIfNotLowLatency(std::shared_ptr<oboe::AudioStream> &stream);
};
#endif // OBOE_LIVEEFFECTENGINE_H

View file

@ -0,0 +1,134 @@
/**
* Copyright 2018 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 <jni.h>
#include <logging_macros.h>
#include "LiveEffectEngine.h"
static const int kOboeApiAAudio = 0;
static const int kOboeApiOpenSLES = 1;
static LiveEffectEngine *engine = nullptr;
extern "C" {
JNIEXPORT jboolean JNICALL
Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_create(JNIEnv *env,
jclass) {
if (engine == nullptr) {
engine = new LiveEffectEngine();
}
return (engine != nullptr) ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT void JNICALL
Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_delete(JNIEnv *env,
jclass) {
if (engine) {
engine->setEffectOn(false);
delete engine;
engine = nullptr;
}
}
JNIEXPORT jboolean JNICALL
Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_setEffectOn(
JNIEnv *env, jclass, jboolean isEffectOn) {
if (engine == nullptr) {
LOGE(
"Engine is null, you must call createEngine before calling this "
"method");
return JNI_FALSE;
}
return engine->setEffectOn(isEffectOn) ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT void JNICALL
Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_setRecordingDeviceId(
JNIEnv *env, jclass, jint deviceId) {
if (engine == nullptr) {
LOGE(
"Engine is null, you must call createEngine before calling this "
"method");
return;
}
engine->setRecordingDeviceId(deviceId);
}
JNIEXPORT void JNICALL
Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_setPlaybackDeviceId(
JNIEnv *env, jclass, jint deviceId) {
if (engine == nullptr) {
LOGE(
"Engine is null, you must call createEngine before calling this "
"method");
return;
}
engine->setPlaybackDeviceId(deviceId);
}
JNIEXPORT jboolean JNICALL
Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_setAPI(JNIEnv *env,
jclass type,
jint apiType) {
if (engine == nullptr) {
LOGE(
"Engine is null, you must call createEngine "
"before calling this method");
return JNI_FALSE;
}
oboe::AudioApi audioApi;
switch (apiType) {
case kOboeApiAAudio:
audioApi = oboe::AudioApi::AAudio;
break;
case kOboeApiOpenSLES:
audioApi = oboe::AudioApi::OpenSLES;
break;
default:
LOGE("Unknown API selection to setAPI() %d", apiType);
return JNI_FALSE;
}
return engine->setAudioApi(audioApi) ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jboolean JNICALL
Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_isAAudioRecommended(
JNIEnv *env, jclass type) {
if (engine == nullptr) {
LOGE(
"Engine is null, you must call createEngine "
"before calling this method");
return JNI_FALSE;
}
return engine->isAAudioRecommended() ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT void JNICALL
Java_com_google_oboe_samples_liveEffect_LiveEffectEngine_native_1setDefaultStreamValues(JNIEnv *env,
jclass type,
jint sampleRate,
jint framesPerBurst) {
oboe::DefaultStreamValues::SampleRate = (int32_t) sampleRate;
oboe::DefaultStreamValues::FramesPerBurst = (int32_t) framesPerBurst;
}
} // extern "C"

View file

@ -0,0 +1,40 @@
# Copy shared STL files to Android Studio output directory so they can be
# packaged in the APK.
# Usage:
#
# find_package(ndk-stl REQUIRED)
#
# or
#
# find_package(ndk-stl REQUIRED PATHS ".")
if(NOT ${ANDROID_STL} MATCHES "_shared")
return()
endif()
function(configure_shared_stl lib_path so_base)
message("Configuring STL ${so_base} for ${ANDROID_ABI}")
configure_file(
"${ANDROID_NDK}/sources/cxx-stl/${lib_path}/libs/${ANDROID_ABI}/lib${so_base}.so"
"${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/lib${so_base}.so"
COPYONLY)
endfunction()
if("${ANDROID_STL}" STREQUAL "libstdc++")
# The default minimal system C++ runtime library.
elseif("${ANDROID_STL}" STREQUAL "gabi++_shared")
# The GAbi++ runtime (shared).
message(FATAL_ERROR "gabi++_shared was not configured by ndk-stl package")
elseif("${ANDROID_STL}" STREQUAL "stlport_shared")
# The STLport runtime (shared).
configure_shared_stl("stlport" "stlport_shared")
elseif("${ANDROID_STL}" STREQUAL "gnustl_shared")
# The GNU STL (shared).
configure_shared_stl("gnu-libstdc++/4.9" "gnustl_shared")
elseif("${ANDROID_STL}" STREQUAL "c++_shared")
# The LLVM libc++ runtime (shared).
configure_shared_stl("llvm-libc++" "c++_shared")
else()
message(FATAL_ERROR "STL configuration ANDROID_STL=${ANDROID_STL} is not supported")
endif()

View file

@ -0,0 +1,92 @@
/*
* Copyright 2024 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.
*/
package com.google.oboe.samples.liveEffect;
import android.app.ForegroundServiceStartNotAllowedException;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import androidx.core.app.ServiceCompat;
import androidx.core.content.ContextCompat;
public class DuplexStreamForegroundService extends Service {
private static final String TAG = "DuplexStreamFS";
public static final String ACTION_START = "ACTION_START";
public static final String ACTION_STOP = "ACTION_STOP";
@Override
public IBinder onBind(Intent intent) {
// We don't provide binding, so return null
return null;
}
private Notification buildNotification() {
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
manager.createNotificationChannel(new NotificationChannel(
"all",
"All Notifications",
NotificationManager.IMPORTANCE_NONE));
return new Notification.Builder(this, "all")
.setContentTitle("Playing/recording audio")
.setContentText("playing/recording...")
.setSmallIcon(R.mipmap.ic_launcher)
.build();
}
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Receive onStartCommand" + intent);
switch (intent.getAction()) {
case ACTION_START:
Log.i(TAG, "Receive ACTION_START" + intent.getExtras());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
startForeground(1, buildNotification(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
| ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
}
break;
case ACTION_STOP:
Log.i(TAG, "Receive ACTION_STOP" + intent.getExtras());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
stopForeground(STOP_FOREGROUND_REMOVE);
}
break;
}
return START_NOT_STICKY;
}
}

View file

@ -0,0 +1,52 @@
package com.google.oboe.samples.liveEffect;
/*
* Copyright 2018 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.
*/
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
public enum LiveEffectEngine {
INSTANCE;
// Load native library
static {
System.loadLibrary("liveEffect");
}
// Native methods
static native boolean create();
static native boolean isAAudioRecommended();
static native boolean setAPI(int apiType);
static native boolean setEffectOn(boolean isEffectOn);
static native void setRecordingDeviceId(int deviceId);
static native void setPlaybackDeviceId(int deviceId);
static native void delete();
static native void native_setDefaultStreamValues(int defaultSampleRate, int defaultFramesPerBurst);
static void setDefaultStreamValues(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){
AudioManager myAudioMgr = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
String sampleRateStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
int defaultSampleRate = Integer.parseInt(sampleRateStr);
String framesPerBurstStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
int defaultFramesPerBurst = Integer.parseInt(framesPerBurstStr);
native_setDefaultStreamValues(defaultSampleRate, defaultFramesPerBurst);
}
}
}

View file

@ -0,0 +1,299 @@
/*
* Copyright 2018 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.
*/
package com.google.oboe.samples.liveEffect;
import static com.google.oboe.samples.liveEffect.DuplexStreamForegroundService.ACTION_START;
import static com.google.oboe.samples.liveEffect.DuplexStreamForegroundService.ACTION_STOP;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.google.oboe.samples.audio_device.AudioDeviceListEntry;
import com.google.oboe.samples.audio_device.AudioDeviceSpinner;
/**
* TODO: Update README.md and go through and comment sample
*/
public class MainActivity extends Activity
implements ActivityCompat.OnRequestPermissionsResultCallback {
private static final String TAG = MainActivity.class.getName();
private static final int AUDIO_EFFECT_REQUEST = 0;
private static final int OBOE_API_AAUDIO = 0;
private static final int OBOE_API_OPENSL_ES=1;
private TextView statusText;
private Button toggleEffectButton;
private AudioDeviceSpinner recordingDeviceSpinner;
private AudioDeviceSpinner playbackDeviceSpinner;
private boolean isPlaying = false;
private int apiSelection = OBOE_API_AAUDIO;
private boolean mAAudioRecommended = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusText = findViewById(R.id.status_view_text);
toggleEffectButton = findViewById(R.id.button_toggle_effect);
toggleEffectButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
toggleEffect();
}
});
toggleEffectButton.setText(getString(R.string.start_effect));
recordingDeviceSpinner = findViewById(R.id.recording_devices_spinner);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
recordingDeviceSpinner.setDirectionType(AudioManager.GET_DEVICES_INPUTS);
recordingDeviceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
LiveEffectEngine.setRecordingDeviceId(getRecordingDeviceId());
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
// Do nothing
}
});
}
playbackDeviceSpinner = findViewById(R.id.playback_devices_spinner);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
playbackDeviceSpinner.setDirectionType(AudioManager.GET_DEVICES_OUTPUTS);
playbackDeviceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
LiveEffectEngine.setPlaybackDeviceId(getPlaybackDeviceId());
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
// Do nothing
}
});
}
((RadioGroup)findViewById(R.id.apiSelectionGroup)).check(R.id.aaudioButton);
findViewById(R.id.aaudioButton).setOnClickListener(new RadioButton.OnClickListener(){
@Override
public void onClick(View v) {
if (((RadioButton)v).isChecked()) {
apiSelection = OBOE_API_AAUDIO;
setSpinnersEnabled(true);
}
}
});
findViewById(R.id.slesButton).setOnClickListener(new RadioButton.OnClickListener(){
@Override
public void onClick(View v) {
if (((RadioButton)v).isChecked()) {
apiSelection = OBOE_API_OPENSL_ES;
setSpinnersEnabled(false);
}
}
});
LiveEffectEngine.setDefaultStreamValues(this);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
if (!isRecordPermissionGranted()){
requestRecordPermission();
} else {
startForegroundService();
}
onStartTest();
}
private void EnableAudioApiUI(boolean enable) {
if(apiSelection == OBOE_API_AAUDIO && !mAAudioRecommended)
{
apiSelection = OBOE_API_OPENSL_ES;
}
findViewById(R.id.slesButton).setEnabled(enable);
if(!mAAudioRecommended) {
findViewById(R.id.aaudioButton).setEnabled(false);
} else {
findViewById(R.id.aaudioButton).setEnabled(enable);
}
((RadioGroup)findViewById(R.id.apiSelectionGroup))
.check(apiSelection == OBOE_API_AAUDIO ? R.id.aaudioButton : R.id.slesButton);
setSpinnersEnabled(enable);
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onDestroy() {
onStopTest();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Intent serviceIntent = new Intent(ACTION_STOP, null, this,
DuplexStreamForegroundService.class);
startForegroundService(serviceIntent);
}
super.onDestroy();
}
private void onStartTest() {
LiveEffectEngine.create();
mAAudioRecommended = LiveEffectEngine.isAAudioRecommended();
EnableAudioApiUI(true);
LiveEffectEngine.setAPI(apiSelection);
}
private void onStopTest() {
stopEffect();
LiveEffectEngine.delete();
}
public void toggleEffect() {
if (isPlaying) {
stopEffect();
} else {
LiveEffectEngine.setAPI(apiSelection);
startEffect();
}
}
private void startEffect() {
Log.d(TAG, "Attempting to start");
boolean success = LiveEffectEngine.setEffectOn(true);
if (success) {
statusText.setText(R.string.status_playing);
toggleEffectButton.setText(R.string.stop_effect);
isPlaying = true;
EnableAudioApiUI(false);
} else {
statusText.setText(R.string.status_open_failed);
isPlaying = false;
}
}
private void stopEffect() {
Log.d(TAG, "Playing, attempting to stop");
LiveEffectEngine.setEffectOn(false);
resetStatusView();
toggleEffectButton.setText(R.string.start_effect);
isPlaying = false;
EnableAudioApiUI(true);
}
private void setSpinnersEnabled(boolean isEnabled){
if (((RadioButton)findViewById(R.id.slesButton)).isChecked())
{
isEnabled = false;
playbackDeviceSpinner.setSelection(0);
recordingDeviceSpinner.setSelection(0);
}
recordingDeviceSpinner.setEnabled(isEnabled);
playbackDeviceSpinner.setEnabled(isEnabled);
}
private int getRecordingDeviceId(){
return ((AudioDeviceListEntry)recordingDeviceSpinner.getSelectedItem()).getId();
}
private int getPlaybackDeviceId(){
return ((AudioDeviceListEntry)playbackDeviceSpinner.getSelectedItem()).getId();
}
private boolean isRecordPermissionGranted() {
return (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) ==
PackageManager.PERMISSION_GRANTED);
}
private void requestRecordPermission(){
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.RECORD_AUDIO},
AUDIO_EFFECT_REQUEST);
}
private void resetStatusView() {
statusText.setText(R.string.status_warning);
}
private void startForegroundService() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Intent serviceIntent = new Intent(ACTION_START, null, this,
DuplexStreamForegroundService.class);
startForegroundService(serviceIntent);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (AUDIO_EFFECT_REQUEST != requestCode) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
return;
}
if (grantResults.length != 1 ||
grantResults[0] != PackageManager.PERMISSION_GRANTED) {
// User denied the permission, without this we cannot record audio
// Show a toast and update the status accordingly
statusText.setText(R.string.status_record_audio_denied);
Toast.makeText(getApplicationContext(),
getString(R.string.need_record_audio_permission),
Toast.LENGTH_SHORT)
.show();
EnableAudioApiUI(false);
toggleEffectButton.setEnabled(false);
} else {
// Permission was granted, start foreground service.
startForegroundService();
}
}
}

View file

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2018 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.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.google.oboe.samples.liveEffect.MainActivity"
tools:layout_editor_absoluteY="81dp">
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/apiSelectionGroup"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/apiTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/apiSelection" />
<RadioButton
android:id="@+id/aaudioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="@string/aaudio" />
<RadioButton
android:id="@+id/slesButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="@string/sles" />
</RadioGroup>
<TextView
android:id="@+id/recDeviceLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:text="@string/recording_device"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/apiSelectionGroup"/>
<com.google.oboe.samples.audio_device.AudioDeviceSpinner
android:id="@+id/recording_devices_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginTop="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/recDeviceLabel" />
<TextView
android:id="@+id/playDeviceLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:text="@string/playback_device"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/recording_devices_spinner" />
<com.google.oboe.samples.audio_device.AudioDeviceSpinner
android:id="@+id/playback_devices_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginTop="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/playDeviceLabel" />
<Button
android:id="@+id/button_toggle_effect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="72dp"
android:gravity="center"
android:text="@string/start_effect"
android:textAllCaps="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.53"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/playback_devices_spinner" />
<TextView
android:id="@+id/status_view_text"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_group_margin"
android:lines="6"
android:text="@string/status_warning"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_toggle_effect"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2018 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.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.google.oboe.samples.liveEffect.MainActivity"
tools:layout_editor_absoluteY="81dp">
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/apiSelectionGroup"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/apiTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/apiSelection" />
<RadioButton
android:id="@+id/aaudioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="@string/aaudio" />
<RadioButton
android:id="@+id/slesButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="@string/sles" />
</RadioGroup>
<TextView
android:id="@+id/recDeviceLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:text="@string/recording_device"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/apiSelectionGroup"/>
<com.google.oboe.samples.audio_device.AudioDeviceSpinner
android:id="@+id/recording_devices_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginTop="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/recDeviceLabel" />
<TextView
android:id="@+id/playDeviceLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:text="@string/playback_device"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/recording_devices_spinner" />
<com.google.oboe.samples.audio_device.AudioDeviceSpinner
android:id="@+id/playback_devices_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginTop="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/playDeviceLabel" />
<Button
android:id="@+id/button_toggle_effect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:textAllCaps="false"
android:text="@string/start_effect"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/playback_devices_spinner" />
<TextView
android:id="@+id/status_view_text"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_group_margin"
android:lines="6"
android:text="@string/status_warning"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_toggle_effect"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="android:Theme.Material.Light">
</style>
</resources>

View file

@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View file

@ -0,0 +1,3 @@
<resources>
<color name="colorBlue">#4444CC</color>
</resources>

View file

@ -0,0 +1,6 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="activity_vertical_group_margin">32dp</dimen>
</resources>

View file

@ -0,0 +1,19 @@
<resources>
<string name="app_name">LiveEffect</string>
<string name="action_settings">Settings</string>
<string name="start_effect">Start</string>
<string name="stop_effect">Stop</string>
<string name="need_record_audio_permission">"This sample needs RECORD_AUDIO permission"</string>
<string name="status_playing">Engine Playing ....</string>
<string name="status_open_failed">Engine Failed to Open Streams!</string>
<string name="status_record_audio_denied">Error: Permission for RECORD_AUDIO was denied</string>
<string name="status_touch_to_begin">RECORD_AUDIO permission granted, touch START to begin</string>
<string name="status_warning">Warning: If you run this sample using the built-in microphone
and speaker you may create a feedback loop which will not be pleasant to listen to.</string>
<string name="recording_device">Recording device</string>
<string name="playback_device">Playback device</string>
<string name="apiSelection">APIs</string>
<string name="aaudio">AAudio</string>
<string name="sles">OpenSL ES</string>
</resources>

View file

@ -0,0 +1,8 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
</resources>