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

47
externals/oboe/tests/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,47 @@
cmake_minimum_required(VERSION 3.4.1)
# Usually this file is called from run_tests.sh which requires the $ANDROID_NDK variable to be set so there's no need
# to set it here. Comments below are left in intentionally in case they're useful in future.
# This may work on Mac OS.
# set(ANDROID_NDK $ENV{HOME}/Library/Android/sdk/ndk-bundle)
# This may work on Linux.
# set(ANDROID_NDK $ENV{HOME}/Android/sdk/ndk-bundle)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -std=c++17 -DOBOE_SUPPRESS_LOG_SPAM")
# Include GoogleTest library
set(GOOGLETEST_ROOT ${ANDROID_NDK}/sources/third_party/googletest)
add_library(gtest STATIC ${GOOGLETEST_ROOT}/src/gtest_main.cc ${GOOGLETEST_ROOT}/src/gtest-all.cc)
target_include_directories(gtest PRIVATE ${GOOGLETEST_ROOT})
target_include_directories(gtest PUBLIC ${GOOGLETEST_ROOT}/include)
# Include Oboe sources
set (OBOE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..)
add_subdirectory(${OBOE_DIR} ./oboe-bin)
include_directories(
${OBOE_DIR}/include
${OBOE_DIR}/src
)
# Build the test binary
add_executable(
testOboe
testAAudio.cpp
testAudioClock.cpp
testFlowgraph.cpp
testFullDuplexStream.cpp
testResampler.cpp
testReturnStop.cpp
testReturnStopDeadlock.cpp
testStreamClosedMethods.cpp
testStreamFramesProcessed.cpp
testStreamOpen.cpp
testStreamStates.cpp
testStreamStop.cpp
testStreamWaitState.cpp
testXRunBehaviour.cpp
testUtilities.cpp
)
target_link_libraries(testOboe gtest oboe)
target_link_options(testOboe PRIVATE "-Wl,-z,max-page-size=16384")

70
externals/oboe/tests/README.md vendored Normal file
View file

@ -0,0 +1,70 @@
# Oboe Unit Tests
This directory contains the Oboe unit tests. They are run using the bash script `run_tests.sh`.
The basic operation is:
1. Connect an Android device or start the Android emulator
2. Open a terminal window and execute `run_tests.sh`
## Prerequisites/caveats
1. Java JDK installed.
2. On Mac, you must agree to the XCode license. The script will prompt you.
3. You must have compiled and executed one of the Oboe examples or OboeTester. That ensures that the NDK and cmake is installed.
4. You must define `ANDROID_NDK` as an environment variable and make sure `cmake` is on your path.
To test this on Mac or Linux enter:
echo $ANDROID_HOME
echo $ANDROID_NDK
cmake --version
They may already be set. If so then skip to "Running the Tests" below.
If not, then this may work on Mac OS:
export ANDROID_HOME=$HOME/Library/Android/sdk
or this may work on Linux:
export ANDROID_HOME=$HOME/Android/Sdk
Tadb rooto determine the latest installed version of the NDK. Enter:
ls $ANDROID_HOME/ndk
Make note of the folder name. Mine was "21.3.6528147" so I entered:
export ANDROID_NDK=$ANDROID_HOME/ndk/21.3.6528147/
If you need to add `cmake` to your path then you can find it by entering:
ls $ANDROID_HOME/cmake
Make note of the folder name. Mine was "3.10.2.4988404" so I entered:
export PATH=$PATH:$ANDROID_HOME/cmake/3.10.2.4988404/bin
cmake --version
## Running the Tests
To run the tests, enter:
cd tests
./run_tests.sh
When the tests finish, you may need to enter \<control-c\> to exit the script.
If you get this error:
com.android.builder.testing.api.DeviceException: com.android.ddmlib.InstallException:
INSTALL_FAILED_UPDATE_INCOMPATIBLE: Package com.google.oboe.tests.unittestrunner
signatures do not match previously installed version; ignoring!
then uninstall the app "UnitTestRunner" from the Android device. Or try:
adb root
adb remount -R
See `run_tests.sh` for more documentation

View file

@ -0,0 +1,11 @@
*.iml
.gradle
/local.properties
/.idea/caches/build_file_checksums.ser
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
.DS_Store
/build
/captures
.externalNativeBuild

View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,30 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 34
defaultConfig {
applicationId "com.google.oboe.tests.unittestrunner"
minSdkVersion 21
targetSdkVersion 34
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path file('../../CMakeLists.txt')
}
}
namespace 'com.google.oboe.tests.unittestrunner'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}

View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,2 @@
/assets
/jniLibs

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity" android:exported="true" android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,198 @@
package com.google.oboe.tests.unittestrunner;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
public class MainActivity extends AppCompatActivity {
private final String TAG = MainActivity.class.getName();
private static final String TEST_BINARY_FILENAME = "testOboe.so";
private static final int APP_PERMISSION_REQUEST = 0;
private TextView outputText;
private ScrollView scrollView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
outputText = findViewById(R.id.output_view_text);
scrollView = findViewById(R.id.scroll_view);
runCommand();
}
private void runCommand(){
if (!isRecordPermissionGranted()){
requestPermissions();
} else {
Log.d(TAG, "Got RECORD_AUDIO permission");
Thread commandThread = new Thread(new UnitTestCommand());
commandThread.start();
}
}
private String executeBinary() {
StringBuilder output = new StringBuilder();
try {
String executablePath;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
executablePath = getApplicationInfo().nativeLibraryDir + "/" + TEST_BINARY_FILENAME;
} else {
executablePath = getExecutablePathFromAssets();
}
Log.d(TAG, "Attempting to execute " + executablePath);
Process process = Runtime.getRuntime().exec(executablePath);
BufferedReader stdInput = new BufferedReader(new
InputStreamReader(process.getInputStream()));
BufferedReader stdError = new BufferedReader(new
InputStreamReader(process.getErrorStream()));
// read the output from the command
String s;
while ((s = stdInput.readLine()) != null) {
Log.d(TAG, s);
output.append(s).append("\n");
}
// read any errors from the attempted command
while ((s = stdError.readLine()) != null) {
Log.e(TAG, "ERROR: " + s);
output.append("ERROR: ").append(s).append("\n");
}
process.waitFor();
Log.d(TAG, "Finished executing binary");
} catch (IOException e){
Log.e(TAG, "Could not execute binary ", e);
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted", e);
}
return output.toString();
}
// Legacy method to get asset path.
// This will not work on more recent Android releases.
private String getExecutablePathFromAssets() {
AssetManager assetManager = getAssets();
String abi = Build.SUPPORTED_ABIS[0];
String extraStringForDebugBuilds = "-hwasan";
if (abi.endsWith(extraStringForDebugBuilds)) {
abi = abi.substring(0, abi.length() - extraStringForDebugBuilds.length());
}
String filesDir = getFilesDir().getPath();
String testBinaryPath = abi + "/" + TEST_BINARY_FILENAME;
try {
InputStream inStream = assetManager.open(testBinaryPath);
Log.d(TAG, "Opened " + testBinaryPath);
// Copy this file to an executable location
File outFile = new File(filesDir, TEST_BINARY_FILENAME);
OutputStream outStream = new FileOutputStream(outFile);
byte[] buffer = new byte[1024];
int read;
while ((read = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, read);
}
inStream.close();
outStream.flush();
outStream.close();
Log.d(TAG, "Copied " + testBinaryPath + " to " + filesDir);
String executablePath = filesDir + "/" + TEST_BINARY_FILENAME;
Log.d(TAG, "Setting execute permission on " + executablePath);
boolean success = new File(executablePath).setExecutable(true, false);
if (!success) {
Log.d(TAG, "Could not set execute permission on " + executablePath);
}
return executablePath;
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
private boolean isRecordPermissionGranted() {
return (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) ==
PackageManager.PERMISSION_GRANTED);
}
private void requestPermissions(){
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.RECORD_AUDIO},
APP_PERMISSION_REQUEST);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (APP_PERMISSION_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
outputText.setText(R.string.status_record_audio_denied);
Toast.makeText(getApplicationContext(),
getString(R.string.need_record_audio_permission),
Toast.LENGTH_SHORT)
.show();
} else {
// Permission was granted, run the command
outputText.setText(R.string.status_record_audio_granted);
runCommand();
}
}
class UnitTestCommand implements Runnable {
@Override
public void run() {
final String output = executeBinary();
runOnUiThread(() -> {
outputText.setText(output);
// Scroll to the bottom so we can see the test result
scrollView.postDelayed(() -> scrollView.scrollTo(0, outputText.getBottom()), 100);
});
}
}
}

View file

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View file

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<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"
android:orientation="horizontal"
tools:context=".MainActivity">
<ScrollView
android:id="@+id/scroll_view"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/output_view_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:gravity="bottom"
android:text="Running tests..."
/>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>

View file

@ -0,0 +1,6 @@
<resources>
<string name="app_name">Unit Test Runner</string>
<string name="need_record_audio_permission">"This app needs RECORD_AUDIO permission"</string>
<string name="status_record_audio_denied">Error: Permission for RECORD_AUDIO was denied</string>
<string name="status_record_audio_granted">RECORD_AUDIO permission granted. Running tests...</string>
</resources>

View file

@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View file

@ -0,0 +1,29 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
mavenCentral()
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:8.5.1'
}
}
allprojects {
repositories {
mavenCentral()
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View file

@ -0,0 +1,22 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View file

@ -0,0 +1,6 @@
#Thu Sep 27 15:12:10 BST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip

172
externals/oboe/tests/UnitTestRunner/gradlew vendored Executable file
View file

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

View file

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -0,0 +1 @@
include ':app'

158
externals/oboe/tests/run_tests.sh vendored Executable file
View file

@ -0,0 +1,158 @@
#!/bin/bash
# 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.
################################################
# Script to build and run the Oboe tests on an attached Android device or emulator
#
# Prerequisites:
# - CMake on PATH. This is usually found in $ANDROID_HOME/cmake/<version>/bin.
# - ANDROID_NDK environment variable is set to your Android NDK location
# e.g. $HOME/Library/Android/sdk/ndk/<version>
# - Android device or emulator attached and accessible via adb
#
# Instructions:
# - Run this script
# - Check the test results on your target device
#
# What does the script do?
# - Builds a test binary for the target architecture
# - Copies the test binary into the UnitTestRunner app
# - Builds, installs and runs the app on the target device
#
# The initial run may take some time as GTest is built, subsequent runs should be much faster.
#
# If you want to perform a clean build just delete the 'build' folder and re-run this script. You will need to do
# this if you change target architectures (e.g. when changing between real device and emulator)
#
# Why is running the tests so convoluted?
# The tests require the RECORDING permission and on many devices (e.g Samsung) the adb user does not have this
# permission (and `run-as` is broken). This means that the test binary must be executed by an app which has this
# permission, hence the need for the UnitTestRunner app.
#
################################################
# Directories, paths and filenames
BUILD_DIR=build
CMAKE=cmake
TEST_BINARY_FILENAME=testOboe
TEST_RUNNER_DIR=UnitTestRunner
TEST_RUNNER_PACKAGE_NAME=com.google.oboe.tests.unittestrunner
TEST_RUNNER_JNILIBS_DIR=${TEST_RUNNER_DIR}/app/src/main/jniLibs
TEST_RUNNER_ASSETS_DIR=${TEST_RUNNER_DIR}/app/src/main/assets
# Check prerequisites
if [ -z "$ANDROID_NDK" ]; then
echo "Please set ANDROID_NDK to the Android NDK folder"
exit 1
fi
if [ ! $(type -P ${CMAKE}) ]; then
echo "${CMAKE} was not found on your path. You can install it using Android Studio using Tools->Android->SDK Manager->SDK Tools."
echo "Once done you will need to add ${HOME}/Library/Android/sdk/cmake/<current_version>/bin to your path."
exit 1
fi
# Get the device ABI
ABI=$(adb shell getprop ro.product.cpu.abi | tr -d '\n\r')
if [ -z "$ABI" ]; then
echo "No device ABI was set. Please ensure a device or emulator is running. You may need to unplug extra devices."
exit 1
fi
echo "Device/emulator architecture is $ABI"
if [ ${ABI} == "arm64-v8a" ] || [ ${ABI} == "x86_64" ]; then
PLATFORM=android-21
elif [ ${ABI} == "armeabi-v7a" ] || [ ${ABI} == "x86" ]; then
PLATFORM=android-16
else
echo "Unrecognised ABI: ${ABI}. Supported ABIs are: arm64-v8a, armeabi-v7a, x86_64, x86. If you feel ${ABI} should be supported please file an issue on github.com/google/oboe"
exit 1
fi
mkdir -p ${BUILD_DIR}
echo "Cleaning up previous build because swapping phones may result in stale binaries"
rm -r ${BUILD_DIR}
# Configure the build
echo "Building tests for ${ABI} using ${PLATFORM}"
CMAKE_ARGS="-S. \
-B${BUILD_DIR} \
-DANDROID_ABI=${ABI} \
-DANDROID_PLATFORM=${PLATFORM} \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_FLAGS=-std=c++17 \
-DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake \
-DCMAKE_VERBOSE_MAKEFILE=1"
cmake ${CMAKE_ARGS}
# Perform the build
pushd ${BUILD_DIR}
make -j5
if [ $? -eq 0 ]; then
echo "Tests built successfully"
else
echo "Building tests FAILED"
exit 1
fi
popd
# Copy the binary into the jniLibs and assets folders of the unit test runner app
# The assets folder does not work after Android R for security reasons
# The jniLibs folder doesn't seem to work before Android O
# Thus, copy into both
mkdir ${TEST_RUNNER_JNILIBS_DIR}
mkdir ${TEST_RUNNER_JNILIBS_DIR}/${ABI}
DESTINATION_DIR=${TEST_RUNNER_JNILIBS_DIR}/${ABI}/${TEST_BINARY_FILENAME}
echo "Copying binary to ${DESTINATION_DIR}"
cp ${BUILD_DIR}/${TEST_BINARY_FILENAME} ${DESTINATION_DIR}.so
mkdir ${TEST_RUNNER_ASSETS_DIR}
mkdir ${TEST_RUNNER_ASSETS_DIR}/${ABI}
DESTINATION_DIR=${TEST_RUNNER_ASSETS_DIR}/${ABI}/${TEST_BINARY_FILENAME}
echo "Copying binary to ${DESTINATION_DIR}"
cp ${BUILD_DIR}/${TEST_BINARY_FILENAME} ${DESTINATION_DIR}.so
# Build and install the unit test runner app
pushd ${TEST_RUNNER_DIR}
echo "Building test runner app"
./gradlew assembleDebug
if [ $? -ne 0 ]; then
echo "Building test app FAILED"
exit 1
fi
echo "Installing to device"
./gradlew installDebug
if [ $? -ne 0 ]; then
echo "Installing tests FAILED"
exit 1
fi
popd
echo "Clear logcat from before the test."
adb logcat -c
echo "Starting app - Check your device for test results"
adb shell am start ${TEST_RUNNER_PACKAGE_NAME}/.MainActivity
sleep 1
echo "Logging test logs and Oboe logs. Run adb logcat for complete logs."
adb logcat ${TEST_RUNNER_PACKAGE_NAME}.MainActivity:V OboeAudio:V *:S

74
externals/oboe/tests/testAAudio.cpp vendored Normal file
View file

@ -0,0 +1,74 @@
/*
* 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 <gtest/gtest.h>
#include <oboe/Oboe.h>
#include "../src/aaudio/AAudioLoader.h"
using namespace oboe;
class AAudioDirect : public ::testing::Test {
public:
/**
* @return true if AAudio NOT supported
*/
bool openAAudio() {
mAAudioLoader = AAudioLoader::getInstance();
return (mAAudioLoader->open() != 0);
}
void createBuilder() {
ASSERT_NE(mAAudioLoader, nullptr);
ASSERT_EQ(0, mAAudioLoader->open());
ASSERT_NE(mAAudioLoader->createStreamBuilder, nullptr);
aaudio_result_t result = mAAudioLoader->createStreamBuilder(&mBuilder);
ASSERT_NE(mBuilder, nullptr);
ASSERT_EQ(result, 0);
}
void openCloseStream() {
AAudioStream *stream = nullptr;
aaudio_result_t result = mAAudioLoader->builder_openStream(mBuilder, &stream);
ASSERT_EQ(result, 0);
ASSERT_NE(stream, nullptr);
result = mAAudioLoader->stream_close(stream);
ASSERT_EQ(result, 0);
}
AAudioStreamBuilder *mBuilder = nullptr;
AAudioLoader *mAAudioLoader = nullptr;
};
TEST_F(AAudioDirect, InstantiateAAudioLoader) {
AAudioLoader *aaudioLoader = AAudioLoader::getInstance();
ASSERT_NE(aaudioLoader, nullptr);
}
TEST_F(AAudioDirect, OpenCloseHighLatencyStream) {
if (openAAudio()) return;
createBuilder();
mAAudioLoader->builder_setPerformanceMode(mBuilder, AAUDIO_PERFORMANCE_MODE_NONE);
openCloseStream();
}
TEST_F(AAudioDirect, OpenCloseLowLatencyStream) {
if (openAAudio()) return;
createBuilder();
mAAudioLoader->builder_setPerformanceMode(mBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
openCloseStream();
}

92
externals/oboe/tests/testAudioClock.cpp vendored Normal file
View file

@ -0,0 +1,92 @@
/*
* Copyright 2022 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.
*/
/*
* Test FlowGraph
*/
#include "math.h"
#include "stdio.h"
#include <gtest/gtest.h>
#include <oboe/Oboe.h>
using namespace oboe;
#define NANOS_PER_MICROSECOND ((int64_t) 1000)
constexpr int64_t kSleepTimeMicroSec = 50 * 1000;
constexpr double kMaxLatenessMicroSec = 20 * 1000;
TEST(TestAudioClock, GetNanosecondsMonotonic) {
int64_t startNanos = AudioClock::getNanoseconds(CLOCK_MONOTONIC);
usleep(kSleepTimeMicroSec);
int64_t endNanos = AudioClock::getNanoseconds(CLOCK_MONOTONIC);
ASSERT_GE(endNanos, startNanos + kSleepTimeMicroSec * kNanosPerMicrosecond);
ASSERT_LT(endNanos, startNanos + ((kSleepTimeMicroSec + kMaxLatenessMicroSec)
* kNanosPerMicrosecond));
}
TEST(TestAudioClock, GetNanosecondsRealtime) {
int64_t startNanos = AudioClock::getNanoseconds(CLOCK_REALTIME);
usleep(kSleepTimeMicroSec);
int64_t endNanos = AudioClock::getNanoseconds(CLOCK_REALTIME);
ASSERT_GE(endNanos, startNanos + kSleepTimeMicroSec * kNanosPerMicrosecond);
ASSERT_LT(endNanos, startNanos + ((kSleepTimeMicroSec + kMaxLatenessMicroSec)
* kNanosPerMicrosecond));
}
TEST(TestAudioClock, SleepUntilNanoTimeMonotonic) {
int64_t startNanos = AudioClock::getNanoseconds(CLOCK_MONOTONIC);
AudioClock::sleepUntilNanoTime(startNanos + kSleepTimeMicroSec * kNanosPerMicrosecond, CLOCK_MONOTONIC);
int64_t endNanos = AudioClock::getNanoseconds(CLOCK_MONOTONIC);
ASSERT_GE(endNanos, startNanos + kSleepTimeMicroSec * kNanosPerMicrosecond);
ASSERT_LT(endNanos, startNanos + ((kSleepTimeMicroSec + kMaxLatenessMicroSec)
* kNanosPerMicrosecond));
}
TEST(TestAudioClock, SleepUntilNanoTimeRealtime) {
int64_t startNanos = AudioClock::getNanoseconds(CLOCK_REALTIME);
AudioClock::sleepUntilNanoTime(startNanos + kSleepTimeMicroSec * kNanosPerMicrosecond, CLOCK_REALTIME);
int64_t endNanos = AudioClock::getNanoseconds(CLOCK_REALTIME);
ASSERT_GE(endNanos, startNanos + kSleepTimeMicroSec * kNanosPerMicrosecond);
ASSERT_LT(endNanos, startNanos + ((kSleepTimeMicroSec + kMaxLatenessMicroSec)
* kNanosPerMicrosecond));
}
TEST(TestAudioClock, SleepForNanosMonotonic) {
int64_t startNanos = AudioClock::getNanoseconds(CLOCK_MONOTONIC);
AudioClock::sleepForNanos(kSleepTimeMicroSec * kNanosPerMicrosecond, CLOCK_MONOTONIC);
int64_t endNanos = AudioClock::getNanoseconds(CLOCK_MONOTONIC);
ASSERT_GE(endNanos, startNanos + kSleepTimeMicroSec * kNanosPerMicrosecond);
ASSERT_LT(endNanos, startNanos + ((kSleepTimeMicroSec + kMaxLatenessMicroSec)
* kNanosPerMicrosecond));
}
TEST(TestAudioClock, SleepForNanosRealtime) {
int64_t startNanos = AudioClock::getNanoseconds(CLOCK_REALTIME);
AudioClock::sleepForNanos(kSleepTimeMicroSec * kNanosPerMicrosecond, CLOCK_REALTIME);
int64_t endNanos = AudioClock::getNanoseconds(CLOCK_REALTIME);
ASSERT_GE(endNanos, startNanos + kSleepTimeMicroSec * kNanosPerMicrosecond);
ASSERT_LT(endNanos, startNanos + ((kSleepTimeMicroSec + kMaxLatenessMicroSec)
* kNanosPerMicrosecond));
}

269
externals/oboe/tests/testFlowgraph.cpp vendored Normal file
View file

@ -0,0 +1,269 @@
/*
* 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.
*/
/*
* Test FlowGraph
*/
#include "stdio.h"
#include <gtest/gtest.h>
#include <oboe/Oboe.h>
#include "flowgraph/ClipToRange.h"
#include "flowgraph/Limiter.h"
#include "flowgraph/MonoToMultiConverter.h"
#include "flowgraph/SourceFloat.h"
#include "flowgraph/RampLinear.h"
#include "flowgraph/SinkFloat.h"
#include "flowgraph/SinkI16.h"
#include "flowgraph/SinkI24.h"
#include "flowgraph/SinkI32.h"
#include "flowgraph/SourceI16.h"
#include "flowgraph/SourceI24.h"
using namespace oboe::flowgraph;
constexpr int kBytesPerI24Packed = 3;
TEST(test_flowgraph, module_sinki16) {
static const float input[] = {1.0f, 0.5f, -0.25f, -1.0f, 0.0f, 53.9f, -87.2f};
static const int16_t expected[] = {32767, 16384, -8192, -32768, 0, 32767, -32768};
int16_t output[20];
SourceFloat sourceFloat{1};
SinkI16 sinkI16{1};
int numInputFrames = sizeof(input) / sizeof(input[0]);
sourceFloat.setData(input, numInputFrames);
sourceFloat.output.connect(&sinkI16.input);
int numOutputFrames = sizeof(output) / sizeof(int16_t);
int32_t numRead = sinkI16.read(output, numOutputFrames);
ASSERT_EQ(numInputFrames, numRead);
for (int i = 0; i < numRead; i++) {
EXPECT_EQ(expected[i], output[i]);
}
}
TEST(test_flowgraph, module_mono_to_stereo) {
static const float input[] = {1.0f, 2.0f, 3.0f};
float output[100] = {};
SourceFloat sourceFloat{1};
MonoToMultiConverter monoToStereo{2};
SinkFloat sinkFloat{2};
sourceFloat.setData(input, 3);
sourceFloat.output.connect(&monoToStereo.input);
monoToStereo.output.connect(&sinkFloat.input);
int32_t numRead = sinkFloat.read(output, 8);
ASSERT_EQ(3, numRead);
EXPECT_EQ(input[0], output[0]);
EXPECT_EQ(input[0], output[1]);
EXPECT_EQ(input[1], output[2]);
EXPECT_EQ(input[1], output[3]);
}
TEST(test_flowgraph, module_ramp_linear) {
constexpr int singleNumOutput = 1;
constexpr int rampSize = 5;
constexpr int numOutput = 100;
constexpr float value = 1.0f;
constexpr float initialTarget = 10.0f;
constexpr float finalTarget = 100.0f;
constexpr float tolerance = 0.0001f; // arbitrary
float output[numOutput] = {};
RampLinear rampLinear{1};
SinkFloat sinkFloat{1};
rampLinear.input.setValue(value);
rampLinear.setLengthInFrames(rampSize);
rampLinear.output.connect(&sinkFloat.input);
// Check that the values go to the initial target instantly.
rampLinear.setTarget(initialTarget);
int32_t singleNumRead = sinkFloat.read(output, singleNumOutput);
ASSERT_EQ(singleNumRead, singleNumOutput);
EXPECT_NEAR(value * initialTarget, output[0], tolerance);
// Now set target and check that the linear ramp works as expected.
rampLinear.setTarget(finalTarget);
int32_t numRead = sinkFloat.read(output, numOutput);
const float incrementSize = (finalTarget - initialTarget) / rampSize;
ASSERT_EQ(numOutput, numRead);
int i = 0;
for (; i < rampSize; i++) {
float expected = value * (initialTarget + i * incrementSize);
EXPECT_NEAR(expected, output[i], tolerance);
}
for (; i < numOutput; i++) {
float expected = value * finalTarget;
EXPECT_NEAR(expected, output[i], tolerance);
}
}
// It is easiest to represent packed 24-bit data as a byte array.
// This test will read from input, convert to float, then write
// back to output as bytes.
TEST(test_flowgraph, module_packed_24) {
static const uint8_t input[] = {0x01, 0x23, 0x45,
0x67, 0x89, 0xAB,
0xCD, 0xEF, 0x5A};
uint8_t output[99] = {};
SourceI24 sourceI24{1};
SinkI24 sinkI24{1};
int numInputFrames = sizeof(input) / kBytesPerI24Packed;
sourceI24.setData(input, numInputFrames);
sourceI24.output.connect(&sinkI24.input);
int32_t numRead = sinkI24.read(output, sizeof(output) / kBytesPerI24Packed);
ASSERT_EQ(numInputFrames, numRead);
for (size_t i = 0; i < sizeof(input); i++) {
EXPECT_EQ(input[i], output[i]);
}
}
TEST(test_flowgraph, module_clip_to_range) {
constexpr float myMin = -2.0f;
constexpr float myMax = 1.5f;
static const float input[] = {-9.7, 0.5f, -0.25, 1.0f, 12.3};
static const float expected[] = {myMin, 0.5f, -0.25, 1.0f, myMax};
float output[100];
SourceFloat sourceFloat{1};
ClipToRange clipper{1};
SinkFloat sinkFloat{1};
int numInputFrames = sizeof(input) / sizeof(input[0]);
sourceFloat.setData(input, numInputFrames);
clipper.setMinimum(myMin);
clipper.setMaximum(myMax);
sourceFloat.output.connect(&clipper.input);
clipper.output.connect(&sinkFloat.input);
int numOutputFrames = sizeof(output) / sizeof(output[0]);
int32_t numRead = sinkFloat.read(output, numOutputFrames);
ASSERT_EQ(numInputFrames, numRead);
constexpr float tolerance = 0.000001f; // arbitrary
for (int i = 0; i < numRead; i++) {
EXPECT_NEAR(expected[i], output[i], tolerance);
}
}
TEST(test_flowgraph, module_sinki32) {
static constexpr int kNumSamples = 8;
static const float input[] = {
1.0f, 0.5f, -0.25f, -1.0f,
0.0f, 53.9f, -87.2f, -1.02f};
static const int32_t expected[] = {
INT32_MAX, 1 << 30, INT32_MIN / 4, INT32_MIN,
0, INT32_MAX, INT32_MIN, INT32_MIN};
int32_t output[kNumSamples + 10]; // larger than input
SourceFloat sourceFloat{1};
SinkI32 sinkI32{1};
sourceFloat.setData(input, kNumSamples);
sourceFloat.output.connect(&sinkI32.input);
int numOutputFrames = sizeof(output) / sizeof(int32_t);
int32_t numRead = sinkI32.read(output, numOutputFrames);
ASSERT_EQ(kNumSamples, numRead);
for (int i = 0; i < numRead; i++) {
EXPECT_EQ(expected[i], output[i]) << ", i = " << i;
}
}
TEST(test_flowgraph, module_limiter) {
constexpr int kNumSamples = 101;
constexpr float kLastSample = 3.0f;
constexpr float kFirstSample = -kLastSample;
constexpr float kDeltaBetweenSamples = (kLastSample - kFirstSample) / (kNumSamples - 1);
constexpr float kTolerance = 0.00001f;
float input[kNumSamples];
float output[kNumSamples];
SourceFloat sourceFloat{1};
Limiter limiter{1};
SinkFloat sinkFloat{1};
for (int i = 0; i < kNumSamples; i++) {
input[i] = kFirstSample + i * kDeltaBetweenSamples;
}
const int numInputFrames = std::size(input);
sourceFloat.setData(input, numInputFrames);
sourceFloat.output.connect(&limiter.input);
limiter.output.connect(&sinkFloat.input);
const int numOutputFrames = std::size(output);
int32_t numRead = sinkFloat.read(output, numOutputFrames);
ASSERT_EQ(numInputFrames, numRead);
for (int i = 0; i < numRead; i++) {
// limiter must be symmetric wrt 0.
EXPECT_NEAR(output[i], -output[kNumSamples - i - 1], kTolerance);
if (i > 0) {
EXPECT_GE(output[i], output[i - 1]); // limiter must be monotonic
}
if (input[i] == 0.f) {
EXPECT_EQ(0.f, output[i]);
} else if (input[i] > 0.0f) {
EXPECT_GE(output[i], 0.0f);
EXPECT_LE(output[i], M_SQRT2); // limiter actually limits
EXPECT_LE(output[i], input[i]); // a limiter, gain <= 1
} else {
EXPECT_LE(output[i], 0.0f);
EXPECT_GE(output[i], -M_SQRT2); // limiter actually limits
EXPECT_GE(output[i], input[i]); // a limiter, gain <= 1
}
if (-1.f <= input[i] && input[i] <= 1.f) {
EXPECT_EQ(input[i], output[i]);
}
}
}
TEST(test_flowgraph, module_limiter_nan) {
constexpr int kArbitraryOutputSize = 100;
constexpr float kFloatNan = NAN;
static const float input[] = {kFloatNan, 0.5f, kFloatNan, kFloatNan, -10.0f, kFloatNan};
static const float expected[] = {0.0f, 0.5f, 0.5f, 0.5f, -M_SQRT2, -M_SQRT2};
constexpr float tolerance = 0.00001f;
float output[kArbitraryOutputSize];
SourceFloat sourceFloat{1};
Limiter limiter{1};
SinkFloat sinkFloat{1};
const int numInputFrames = std::size(input);
sourceFloat.setData(input, numInputFrames);
sourceFloat.output.connect(&limiter.input);
limiter.output.connect(&sinkFloat.input);
const int numOutputFrames = std::size(output);
int32_t numRead = sinkFloat.read(output, numOutputFrames);
ASSERT_EQ(numInputFrames, numRead);
for (int i = 0; i < numRead; i++) {
EXPECT_NEAR(expected[i], output[i], tolerance);
}
}

View file

@ -0,0 +1,206 @@
/*
* Copyright 2023 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 <thread>
#include <gtest/gtest.h>
#include <oboe/Oboe.h>
using namespace oboe;
static constexpr int kTimeToSleepMicros = 5 * 1000 * 1000; // 5 s
using TestFullDuplexStreamParams = std::tuple<AudioApi, PerformanceMode, AudioApi, PerformanceMode>;
class TestFullDuplexStream : public ::testing::Test,
public ::testing::WithParamInterface<TestFullDuplexStreamParams>,
public FullDuplexStream {
public:
DataCallbackResult onBothStreamsReady(
const void *inputData,
int numInputFrames,
void *outputData,
int numOutputFrames) override {
mCallbackCount++;
if (numInputFrames == numOutputFrames) {
mGoodCallbackCount++;
}
return DataCallbackResult::Continue;
}
protected:
void openStream(AudioApi inputAudioApi, PerformanceMode inputPerfMode,
AudioApi outputAudioApi, PerformanceMode outputPerfMode) {
mOutputBuilder.setDirection(Direction::Output);
if (mOutputBuilder.isAAudioRecommended()) {
mOutputBuilder.setAudioApi(outputAudioApi);
}
mOutputBuilder.setPerformanceMode(outputPerfMode);
mOutputBuilder.setChannelCount(1);
mOutputBuilder.setFormat(AudioFormat::Float);
mOutputBuilder.setDataCallback(this);
Result r = mOutputBuilder.openStream(mOutputStream);
ASSERT_EQ(r, Result::OK) << "Failed to open output stream " << convertToText(r);
mInputBuilder.setDirection(Direction::Input);
if (mInputBuilder.isAAudioRecommended()) {
mInputBuilder.setAudioApi(inputAudioApi);
}
mInputBuilder.setPerformanceMode(inputPerfMode);
mInputBuilder.setChannelCount(1);
mInputBuilder.setFormat(AudioFormat::Float);
mInputBuilder.setBufferCapacityInFrames(mOutputStream->getBufferCapacityInFrames() * 2);
mInputBuilder.setSampleRate(mOutputStream->getSampleRate());
r = mInputBuilder.openStream(mInputStream);
ASSERT_EQ(r, Result::OK) << "Failed to open input stream " << convertToText(r);
setSharedInputStream(mInputStream);
setSharedOutputStream(mOutputStream);
}
void startStream() {
Result r = start();
ASSERT_EQ(r, Result::OK) << "Failed to start streams " << convertToText(r);
}
void stopStream() {
Result r = stop();
ASSERT_EQ(r, Result::OK) << "Failed to stop streams " << convertToText(r);
}
void closeStream() {
Result r = mOutputStream->close();
ASSERT_EQ(r, Result::OK) << "Failed to close output stream " << convertToText(r);
r = mInputStream->close();
ASSERT_EQ(r, Result::OK) << "Failed to close input stream " << convertToText(r);
}
void checkXRuns() {
// Expect few xRuns with the use of full duplex stream
EXPECT_LT(mInputStream->getXRunCount().value(), 10);
EXPECT_LT(mOutputStream->getXRunCount().value(), 10);
}
void checkInputAndOutputBufferSizesMatch() {
// Expect the large majority of callbacks to have the same sized input and output
EXPECT_GE(mGoodCallbackCount, mCallbackCount * 4 / 5);
}
AudioStreamBuilder mInputBuilder;
AudioStreamBuilder mOutputBuilder;
std::shared_ptr<AudioStream> mInputStream;
std::shared_ptr<AudioStream> mOutputStream;
std::atomic<int32_t> mCallbackCount{0};
std::atomic<int32_t> mGoodCallbackCount{0};
};
TEST_P(TestFullDuplexStream, VerifyFullDuplexStream) {
const AudioApi inputAudioApi = std::get<0>(GetParam());
const PerformanceMode inputPerformanceMode = std::get<1>(GetParam());
const AudioApi outputAudioApi = std::get<2>(GetParam());
const PerformanceMode outputPerformanceMode = std::get<3>(GetParam());
openStream(inputAudioApi, inputPerformanceMode, outputAudioApi, outputPerformanceMode);
startStream();
usleep(kTimeToSleepMicros);
checkXRuns();
checkInputAndOutputBufferSizesMatch();
stopStream();
closeStream();
}
INSTANTIATE_TEST_SUITE_P(
TestFullDuplexStreamTest,
TestFullDuplexStream,
::testing::Values(
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::LowLatency,
AudioApi::AAudio, PerformanceMode::LowLatency}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::LowLatency,
AudioApi::AAudio, PerformanceMode::None}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::LowLatency,
AudioApi::AAudio, PerformanceMode::PowerSaving}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::LowLatency,
AudioApi::OpenSLES, PerformanceMode::LowLatency}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::LowLatency,
AudioApi::OpenSLES, PerformanceMode::None}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::LowLatency,
AudioApi::OpenSLES, PerformanceMode::PowerSaving}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::None,
AudioApi::AAudio, PerformanceMode::LowLatency}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::None,
AudioApi::AAudio, PerformanceMode::None}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::None,
AudioApi::AAudio, PerformanceMode::PowerSaving}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::None,
AudioApi::OpenSLES, PerformanceMode::LowLatency}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::None,
AudioApi::OpenSLES, PerformanceMode::None}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::None,
AudioApi::OpenSLES, PerformanceMode::PowerSaving}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::PowerSaving,
AudioApi::AAudio, PerformanceMode::LowLatency}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::PowerSaving,
AudioApi::AAudio, PerformanceMode::None}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::PowerSaving,
AudioApi::AAudio, PerformanceMode::PowerSaving}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::PowerSaving,
AudioApi::OpenSLES, PerformanceMode::LowLatency}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::PowerSaving,
AudioApi::OpenSLES, PerformanceMode::None}),
TestFullDuplexStreamParams({AudioApi::AAudio, PerformanceMode::PowerSaving,
AudioApi::OpenSLES, PerformanceMode::PowerSaving}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::LowLatency,
AudioApi::AAudio, PerformanceMode::LowLatency}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::LowLatency,
AudioApi::AAudio, PerformanceMode::None}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::LowLatency,
AudioApi::AAudio, PerformanceMode::PowerSaving}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::LowLatency,
AudioApi::OpenSLES, PerformanceMode::LowLatency}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::LowLatency,
AudioApi::OpenSLES, PerformanceMode::None}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::LowLatency,
AudioApi::OpenSLES, PerformanceMode::PowerSaving}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::None,
AudioApi::AAudio, PerformanceMode::LowLatency}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::None,
AudioApi::AAudio, PerformanceMode::None}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::None,
AudioApi::AAudio, PerformanceMode::PowerSaving}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::None,
AudioApi::OpenSLES, PerformanceMode::LowLatency}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::None,
AudioApi::OpenSLES, PerformanceMode::None}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::None,
AudioApi::OpenSLES, PerformanceMode::PowerSaving}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::PowerSaving,
AudioApi::AAudio, PerformanceMode::LowLatency}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::PowerSaving,
AudioApi::AAudio, PerformanceMode::None}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::PowerSaving,
AudioApi::AAudio, PerformanceMode::PowerSaving}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::PowerSaving,
AudioApi::OpenSLES, PerformanceMode::LowLatency}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::PowerSaving,
AudioApi::OpenSLES, PerformanceMode::None}),
TestFullDuplexStreamParams({AudioApi::OpenSLES, PerformanceMode::PowerSaving,
AudioApi::OpenSLES, PerformanceMode::PowerSaving})
)
);

225
externals/oboe/tests/testResampler.cpp vendored Normal file
View file

@ -0,0 +1,225 @@
/*
* Copyright 2022 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.
*/
/*
* Test FlowGraph
*/
#include "math.h"
#include "stdio.h"
#include <gtest/gtest.h>
#include <oboe/Oboe.h>
#include "flowgraph/resampler/MultiChannelResampler.h"
using namespace oboe::resampler;
// Measure zero crossings.
static int32_t countZeroCrossingsWithHysteresis(float *input, int32_t numSamples) {
const float kHysteresisLevel = 0.25f;
int zeroCrossingCount = 0;
int state = 0; // can be -1, 0, +1
for (int i = 0; i < numSamples; i++) {
if (input[i] >= kHysteresisLevel) {
if (state < 0) {
zeroCrossingCount++;
}
state = 1;
} else if (input[i] <= -kHysteresisLevel) {
if (state > 0) {
zeroCrossingCount++;
}
state = -1;
}
}
return zeroCrossingCount;
}
static constexpr int kChannelCount = 1;
/**
* Convert a sine wave and then look for glitches.
* Glitches have a high value in the second derivative.
*/
static void checkResampler(int32_t sourceRate, int32_t sinkRate,
MultiChannelResampler::Quality quality) {
const int kNumOutputSamples = 10000;
const double framesPerCycle = 81.379; // target output period
int numInputSamples = kNumOutputSamples * sourceRate / sinkRate;
std::unique_ptr<float[]> inputBuffer = std::make_unique<float[]>(numInputSamples);
std::unique_ptr<float[]> outputBuffer = std::make_unique<float[]>(kNumOutputSamples);
// Generate a sine wave for input.
const double kPhaseIncrement = 2.0 * sinkRate / (framesPerCycle * sourceRate);
double phase = 0.0;
for (int i = 0; i < numInputSamples; i++) {
inputBuffer[i] = sin(phase * M_PI);
phase += kPhaseIncrement;
while (phase > 1.0) {
phase -= 2.0;
}
}
int sourceZeroCrossingCount = countZeroCrossingsWithHysteresis(inputBuffer.get(), numInputSamples);
// Use a MultiChannelResampler to convert from the sourceRate to the sinkRate.
std::unique_ptr<MultiChannelResampler> mcResampler;
mcResampler.reset(MultiChannelResampler::make(kChannelCount,
sourceRate,
sinkRate,
quality));
int inputFramesLeft = numInputSamples;
int numRead = 0;
float *input = inputBuffer.get(); // for iteration
float *output = outputBuffer.get();
while (inputFramesLeft > 0) {
if (mcResampler->isWriteNeeded()) {
mcResampler->writeNextFrame(input);
input++;
inputFramesLeft--;
} else {
mcResampler->readNextFrame(output);
output++;
numRead++;
}
}
// Flush out remaining frames from the flowgraph
while (!mcResampler->isWriteNeeded()) {
mcResampler->readNextFrame(output);
output++;
numRead++;
}
ASSERT_LE(numRead, kNumOutputSamples);
// Some frames are lost priming the FIR filter.
const int kMaxAlgorithmicFrameLoss = 5;
EXPECT_GT(numRead, kNumOutputSamples - kMaxAlgorithmicFrameLoss);
int sinkZeroCrossingCount = countZeroCrossingsWithHysteresis(outputBuffer.get(), numRead);
// The sine wave may be cut off partially. This may cause multiple crossing
// differences when upsampling.
const int kMaxZeroCrossingDelta = std::max(sinkRate / sourceRate / 2, 1);
EXPECT_LE(abs(sourceZeroCrossingCount - sinkZeroCrossingCount), kMaxZeroCrossingDelta);
// Detect glitches by looking for spikes in the second derivative.
output = outputBuffer.get();
float previousValue = output[0];
float previousSlope = output[1] - output[0];
for (int i = 0; i < numRead; i++) {
float slope = output[i] - previousValue;
float slopeDelta = fabs(slope - previousSlope);
// Skip a few samples because there are often some steep slope changes at the beginning.
if (i > 10) {
EXPECT_LT(slopeDelta, 0.1);
}
previousValue = output[i];
previousSlope = slope;
}
#if 0
// Save to disk for inspection.
FILE *fp = fopen( "/sdcard/Download/src_float_out.raw" , "wb" );
fwrite(outputBuffer.get(), sizeof(float), numRead, fp );
fclose(fp);
#endif
}
TEST(test_resampler, resampler_scan_all) {
const int rates[] = {8000, 11025, 22050, 32000, 44100, 48000, 64000, 88200, 96000};
const MultiChannelResampler::Quality qualities[] =
{
MultiChannelResampler::Quality::Fastest,
MultiChannelResampler::Quality::Low,
MultiChannelResampler::Quality::Medium,
MultiChannelResampler::Quality::High,
MultiChannelResampler::Quality::Best
};
for (int srcRate : rates) {
for (int destRate : rates) {
for (auto quality : qualities) {
if (srcRate != destRate) {
checkResampler(srcRate, destRate, quality);
}
}
}
}
}
TEST(test_resampler, resampler_8000_11025_best) {
checkResampler(8000, 11025, MultiChannelResampler::Quality::Best);
}
TEST(test_resampler, resampler_8000_48000_best) {
checkResampler(8000, 48000, MultiChannelResampler::Quality::Best);
}
TEST(test_resampler, resampler_8000_44100_best) {
checkResampler(8000, 44100, MultiChannelResampler::Quality::Best);
}
TEST(test_resampler, resampler_11025_24000_best) {
checkResampler(11025, 24000, MultiChannelResampler::Quality::Best);
}
TEST(test_resampler, resampler_11025_48000_fastest) {
checkResampler(11025, 48000, MultiChannelResampler::Quality::Fastest);
}
TEST(test_resampler, resampler_11025_48000_low) {
checkResampler(11025, 48000, MultiChannelResampler::Quality::Low);
}
TEST(test_resampler, resampler_11025_48000_medium) {
checkResampler(11025, 48000, MultiChannelResampler::Quality::Medium);
}
TEST(test_resampler, resampler_11025_48000_high) {
checkResampler(11025, 48000, MultiChannelResampler::Quality::High);
}
TEST(test_resampler, resampler_11025_48000_best) {
checkResampler(11025, 48000, MultiChannelResampler::Quality::Best);
}
TEST(test_resampler, resampler_11025_44100_best) {
checkResampler(11025, 44100, MultiChannelResampler::Quality::Best);
}
TEST(test_resampler, resampler_11025_88200_best) {
checkResampler(11025, 88200, MultiChannelResampler::Quality::Best);
}
TEST(test_resampler, resampler_16000_48000_best) {
checkResampler(16000, 48000, MultiChannelResampler::Quality::Best);
}
TEST(test_resampler, resampler_44100_48000_low) {
checkResampler(44100, 48000, MultiChannelResampler::Quality::Low);
}
TEST(test_resampler, resampler_44100_48000_best) {
checkResampler(44100, 48000, MultiChannelResampler::Quality::Best);
}
// Look for glitches when downsampling.
TEST(test_resampler, resampler_48000_11025_best) {
checkResampler(48000, 11025, MultiChannelResampler::Quality::Best);
}
TEST(test_resampler, resampler_48000_44100_best) {
checkResampler(48000, 44100, MultiChannelResampler::Quality::Best);
}
TEST(test_resampler, resampler_44100_11025_best) {
checkResampler(44100, 11025, MultiChannelResampler::Quality::Best);
}

133
externals/oboe/tests/testReturnStop.cpp vendored Normal file
View file

@ -0,0 +1,133 @@
/*
* Copyright 2021 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 <atomic>
#include <tuple>
#include <gtest/gtest.h>
#include <oboe/Oboe.h>
// Test returning DataCallbackResult::Stop from a callback.
using namespace oboe;
static constexpr int kTimeoutInNanos = 500 * kNanosPerMillisecond;
class ReturnStopCallback : public AudioStreamDataCallback {
public:
DataCallbackResult onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override {
return (++callbackCount < kMaxCallbacks) ? DataCallbackResult::Continue : DataCallbackResult::Stop;
}
void reset() {
callbackCount = 0;
}
int getMaxCallbacks() const { return kMaxCallbacks; }
std::atomic<int> callbackCount{0};
private:
// I get strange linker errors with GTest if I try to reference this directly.
static constexpr int kMaxCallbacks = 40;
};
using StreamReturnStopParams = std::tuple<Direction, AudioApi, PerformanceMode, bool>;
class StreamReturnStop : public ::testing::Test,
public ::testing::WithParamInterface<StreamReturnStopParams> {
protected:
void TearDown() override;
AudioStreamBuilder mBuilder;
std::shared_ptr<AudioStream> mStream;
};
void StreamReturnStop::TearDown() {
if (mStream) {
mStream->close();
}
}
TEST_P(StreamReturnStop, VerifyStreamReturnStop) {
const Direction direction = std::get<0>(GetParam());
const AudioApi audioApi = std::get<1>(GetParam());
const PerformanceMode performanceMode = std::get<2>(GetParam());
const bool useRequestStart = std::get<3>(GetParam());
ReturnStopCallback *callback = new ReturnStopCallback();
mBuilder.setDirection(direction)
->setFormat(AudioFormat::I16)
->setPerformanceMode(performanceMode)
->setDataCallback(callback);
if (mBuilder.isAAudioRecommended()) {
mBuilder.setAudioApi(audioApi);
}
Result r = mBuilder.openStream(mStream);
ASSERT_EQ(r, Result::OK) << "Failed to open stream. " << convertToText(r);
// Start and stop several times.
for (int i = 0; i < 3; i++) {
callback->reset();
// Oboe has two ways to start a stream.
if (useRequestStart) {
r = mStream->requestStart();
} else {
r = mStream->start();
}
ASSERT_EQ(r, Result::OK) << "Failed to start stream. " << convertToText(r);
// Wait for callbacks to complete.
const int kMaxCallbackPeriodMillis = 500;
const int kPollPeriodMillis = 20;
int timeout = 2 * callback->getMaxCallbacks() * kMaxCallbackPeriodMillis / kPollPeriodMillis;
do {
usleep(kPollPeriodMillis * 1000);
} while (callback->callbackCount < callback->getMaxCallbacks() && timeout-- > 0);
EXPECT_GT(timeout, 0) << "timed out waiting for enough callbacks";
StreamState next = StreamState::Unknown;
r = mStream->waitForStateChange(StreamState::Started, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK) << "waitForStateChange(Started) timed out. " << convertToText(r);
r = mStream->waitForStateChange(StreamState::Stopping, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK) << "waitForStateChange(Stopping) timed out. " << convertToText(r);
EXPECT_EQ(next, StreamState::Stopped) << "Stream not in state Stopped, was " << convertToText(next);
EXPECT_EQ(callback->callbackCount, callback->getMaxCallbacks()) << "Too many callbacks = " << callback->callbackCount;
const int kOboeStartStopSleepMSec = 10;
usleep(kOboeStartStopSleepMSec * 1000); // avoid race condition in emulator
}
ASSERT_EQ(Result::OK, mStream->close());
}
INSTANTIATE_TEST_SUITE_P(
StreamReturnStopTest,
StreamReturnStop,
::testing::Values(
// Last boolean is true if requestStart() should be called instead of start().
StreamReturnStopParams({Direction::Output, AudioApi::AAudio, PerformanceMode::LowLatency, true}),
StreamReturnStopParams({Direction::Output, AudioApi::AAudio, PerformanceMode::LowLatency, false}),
StreamReturnStopParams({Direction::Output, AudioApi::AAudio, PerformanceMode::None, true}),
StreamReturnStopParams({Direction::Output, AudioApi::AAudio, PerformanceMode::None, false}),
StreamReturnStopParams({Direction::Output, AudioApi::OpenSLES, PerformanceMode::LowLatency, true}),
StreamReturnStopParams({Direction::Output, AudioApi::OpenSLES, PerformanceMode::LowLatency, false}),
StreamReturnStopParams({Direction::Input, AudioApi::AAudio, PerformanceMode::LowLatency, true}),
StreamReturnStopParams({Direction::Input, AudioApi::AAudio, PerformanceMode::LowLatency, false})
)
);

View file

@ -0,0 +1,161 @@
/*
* 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.
*/
#include <atomic>
#include <tuple>
#include <gtest/gtest.h>
#include <oboe/Oboe.h>
#include <thread>
#include <future>
// Test returning DataCallbackResult::Stop from a callback.
using namespace oboe;
// Test whether there is a deadlock when stopping streams.
// See Issue #2059
class TestReturnStopDeadlock : public ::testing::Test {
public:
void start(bool useOpenSL);
void stop();
int32_t getCycleCount() {
return mCycleCount.load();
}
protected:
void TearDown() override;
private:
void cycleRapidly(bool useOpenSL);
class MyDataCallback : public oboe::AudioStreamDataCallback { public:
MyDataCallback() {}
oboe::DataCallbackResult onAudioReady(
oboe::AudioStream *audioStream,
void *audioData,
int32_t numFrames) override;
std::atomic<bool> returnStop = false;
std::atomic<int32_t> callbackCount{0};
};
std::shared_ptr<oboe::AudioStream> mStream;
std::shared_ptr<MyDataCallback> mDataCallback;
std::atomic<int32_t> mCycleCount{0};
std::atomic<bool> mThreadEnabled{false};
std::thread mCycleThread;
static constexpr int kChannelCount = 1;
static constexpr int kMaxSleepMicros = 25000;
};
// start a thread to cycle through stream tests
void TestReturnStopDeadlock::start(bool useOpenSL) {
mThreadEnabled = true;
mCycleCount = 0;
mCycleThread = std::thread([this, useOpenSL]() {
cycleRapidly(useOpenSL);
});
}
void TestReturnStopDeadlock::stop() {
mThreadEnabled = false;
// Terminate the thread with a timeout.
const int timeout = 1;
auto future = std::async(std::launch::async, &std::thread::join, &mCycleThread);
ASSERT_NE(future.wait_for(std::chrono::seconds(timeout)), std::future_status::timeout)
<< " join() timed out! cycles = " << getCycleCount();
}
void TestReturnStopDeadlock::TearDown() {
if (mStream) {
mStream->close();
}
}
void TestReturnStopDeadlock::cycleRapidly(bool useOpenSL) {
while(mThreadEnabled) {
mCycleCount++;
mDataCallback = std::make_shared<MyDataCallback>();
AudioStreamBuilder builder;
oboe::Result result = builder.setFormat(oboe::AudioFormat::Float)
->setAudioApi(useOpenSL ? oboe::AudioApi::OpenSLES : oboe::AudioApi::AAudio)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setChannelCount(kChannelCount)
->setDataCallback(mDataCallback)
->setUsage(oboe::Usage::Notification)
->openStream(mStream);
ASSERT_EQ(result, oboe::Result::OK);
mStream->setDelayBeforeCloseMillis(0);
result = mStream->requestStart();
ASSERT_EQ(result, oboe::Result::OK);
// Sleep for some random time.
int countdown = 100;
while ((mDataCallback->callbackCount < 4) && (--countdown > 0)) {
int32_t durationMicros = (int32_t)(drand48() * kMaxSleepMicros);
usleep(durationMicros);
}
mDataCallback->returnStop = true;
result = mStream->close();
ASSERT_EQ(result, oboe::Result::OK);
mStream = nullptr;
ASSERT_GT(mDataCallback->callbackCount, 1) << " cycleCount = " << mCycleCount;
}
}
// Callback that returns Continue or Stop
DataCallbackResult TestReturnStopDeadlock::MyDataCallback::onAudioReady(
AudioStream *audioStream,
void *audioData,
int32_t numFrames) {
float *floatData = (float *) audioData;
const int numSamples = numFrames * kChannelCount;
callbackCount++;
// Fill buffer with white noise.
for (int i = 0; i < numSamples; i++) {
floatData[i] = ((float) drand48() - 0.5f) * 2 * 0.1f;
}
usleep(500); // half a millisecond
if (returnStop) {
usleep(20 * 1000);
return DataCallbackResult::Stop;
} else {
return DataCallbackResult::Continue;
}
}
TEST_F(TestReturnStopDeadlock, RapidCycleAAudio){
start(false);
usleep(3000 * 1000);
stop();
}
TEST_F(TestReturnStopDeadlock, RapidCycleOpenSL){
start(true);
usleep(3000 * 1000);
stop();
}

View file

@ -0,0 +1,432 @@
/*
* 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 <gtest/gtest.h>
#include <oboe/Oboe.h>
using namespace oboe;
class MyCallback : public AudioStreamDataCallback {
public:
DataCallbackResult onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override {
return DataCallbackResult::Continue;
}
};
class StreamClosedReturnValues : public ::testing::Test {
protected:
bool openStream() {
Result r = mBuilder.openStream(mStream);
EXPECT_EQ(r, Result::OK) << "Failed to open stream " << convertToText(r);
return (r == Result::OK);
}
bool releaseStream() {
Result r = mStream->release();
if (getSdkVersion() > __ANDROID_API_R__ && mBuilder.getAudioApi() != AudioApi::OpenSLES) {
EXPECT_EQ(r, Result::OK) << "Failed to release stream. " << convertToText(r);
return (r == Result::OK);
} else {
EXPECT_EQ(r, Result::ErrorUnimplemented) << "Did not get ErrorUnimplemented" << convertToText(r);
return (r == Result::ErrorUnimplemented);
}
}
bool closeStream() {
Result r = mStream->close();
EXPECT_EQ(r, Result::OK) << "Failed to close stream. " << convertToText(r);
return (r == Result::OK);
}
bool openAndCloseStream() {
if (!openStream() || !closeStream())
return false;
StreamState s = mStream->getState();
EXPECT_EQ(s, StreamState::Closed) << "Stream state " << convertToText(mStream->getState());
return (s == StreamState::Closed);
}
static int64_t getNanoseconds() {
struct timespec time;
int result = clock_gettime(CLOCK_MONOTONIC, &time);
if (result < 0) {
return result;
}
return (time.tv_sec * (int64_t)1e9) + time.tv_nsec;
}
// ASSERT_* requires a void return type.
void measureCloseTime(int32_t delayMillis) {
ASSERT_TRUE(openStream());
mStream->setDelayBeforeCloseMillis(delayMillis);
ASSERT_EQ(delayMillis, mStream->getDelayBeforeCloseMillis());
// Measure time it takes to close.
int64_t startTimeMillis = getNanoseconds() / 1e6;
ASSERT_TRUE(closeStream());
int64_t stopTimeMillis = getNanoseconds() / 1e6;
int32_t elapsedTimeMillis = (int32_t)(stopTimeMillis - startTimeMillis);
ASSERT_GE(elapsedTimeMillis, delayMillis);
}
void testDelayBeforeClose() {
const int32_t delayMillis = 500;
measureCloseTime(delayMillis);
}
AudioStreamBuilder mBuilder;
std::shared_ptr<AudioStream> mStream;
};
TEST_F(StreamClosedReturnValues, GetChannelCountReturnsLastKnownValue){
mBuilder.setChannelCount(2);
ASSERT_TRUE(openAndCloseStream());
ASSERT_EQ(mStream->getChannelCount(), 2);
}
TEST_F(StreamClosedReturnValues, GetDirectionReturnsLastKnownValue){
// Note that when testing on the emulator setting the direction to Input will result in ErrorInternal when
// opening the stream
mBuilder.setDirection(Direction::Input);
ASSERT_TRUE(openAndCloseStream());
ASSERT_EQ(mStream->getDirection(), Direction::Input);
}
TEST_F(StreamClosedReturnValues, GetSampleRateReturnsLastKnownValue){
mBuilder.setSampleRate(8000);
ASSERT_TRUE(openAndCloseStream());
ASSERT_EQ(mStream->getSampleRate(), 8000);
}
TEST_F(StreamClosedReturnValues, GetFramesPerCallbackReturnsLastKnownValue) {
mBuilder.setFramesPerCallback(192);
ASSERT_TRUE(openAndCloseStream());
ASSERT_EQ(mStream->getFramesPerCallback(), 192);
}
TEST_F(StreamClosedReturnValues, GetFormatReturnsLastKnownValue) {
mBuilder.setFormat(AudioFormat::I16);
ASSERT_TRUE(openAndCloseStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::I16);
}
TEST_F(StreamClosedReturnValues, GetBufferSizeInFramesReturnsLastKnownValue) {
ASSERT_TRUE(openStream());
int32_t bufferSize = mStream->getBufferSizeInFrames();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getBufferSizeInFrames(), bufferSize);
}
TEST_F(StreamClosedReturnValues, GetBufferCapacityInFramesReturnsLastKnownValue) {
ASSERT_TRUE(openStream());
int32_t bufferCapacity = mStream->getBufferCapacityInFrames();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getBufferCapacityInFrames(), bufferCapacity);
}
TEST_F(StreamClosedReturnValues, GetSharingModeReturnsLastKnownValue) {
ASSERT_TRUE(openStream());
SharingMode s = mStream->getSharingMode();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getSharingMode(), s);
}
TEST_F(StreamClosedReturnValues, GetPerformanceModeReturnsLastKnownValue) {
ASSERT_TRUE(openStream());
PerformanceMode p = mStream->getPerformanceMode();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getPerformanceMode(), p);
}
TEST_F(StreamClosedReturnValues, GetDeviceIdReturnsLastKnownValue) {
ASSERT_TRUE(openStream());
int32_t d = mStream->getDeviceId();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getDeviceId(), d);
}
TEST_F(StreamClosedReturnValues, GetDataCallbackReturnsLastKnownValue) {
AudioStreamDataCallback *callback = new MyCallback();
mBuilder.setDataCallback(callback);
ASSERT_TRUE(openAndCloseStream());
AudioStreamDataCallback *callback2 = mStream->getDataCallback();
ASSERT_EQ(callback, callback2);
}
TEST_F(StreamClosedReturnValues, GetUsageReturnsLastKnownValue){
ASSERT_TRUE(openStream());
Usage u = mStream->getUsage();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getUsage(), u);
}
TEST_F(StreamClosedReturnValues, GetContentTypeReturnsLastKnownValue){
ASSERT_TRUE(openStream());
ContentType c = mStream->getContentType();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getContentType(), c);
}
TEST_F(StreamClosedReturnValues, GetInputPresetReturnsLastKnownValue){
ASSERT_TRUE(openStream());
auto i = mStream->getInputPreset();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getInputPreset(), i);
}
TEST_F(StreamClosedReturnValues, GetSessionIdReturnsLastKnownValue){
ASSERT_TRUE(openStream());
auto s = mStream->getSessionId();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getSessionId(), s);
}
TEST_F(StreamClosedReturnValues, StreamStateIsClosed){
ASSERT_TRUE(openAndCloseStream());
ASSERT_EQ(mStream->getState(), StreamState::Closed);
}
TEST_F(StreamClosedReturnValues, GetXRunCountReturnsLastKnownValue){
ASSERT_TRUE(openStream());
if (mStream->isXRunCountSupported()){
auto i = mStream->getXRunCount();
ASSERT_EQ(mStream->getXRunCount(), i);
}
ASSERT_TRUE(closeStream());
}
TEST_F(StreamClosedReturnValues, GetFramesPerBurstReturnsLastKnownValue){
ASSERT_TRUE(openStream());
auto f = mStream->getFramesPerBurst();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getFramesPerBurst(), f);
}
TEST_F(StreamClosedReturnValues, GetBytesPerFrameReturnsLastKnownValue){
ASSERT_TRUE(openStream());
auto f = mStream->getBytesPerFrame();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getBytesPerFrame(), f);
}
TEST_F(StreamClosedReturnValues, GetBytesPerSampleReturnsLastKnownValue){
ASSERT_TRUE(openStream());
auto f = mStream->getBytesPerSample();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getBytesPerSample(), f);
}
TEST_F(StreamClosedReturnValues, GetFramesWrittenReturnsLastKnownValue){
mBuilder.setFormat(AudioFormat::I16);
mBuilder.setChannelCount(1);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->setBufferSizeInFrames(mStream->getBufferCapacityInFrames()), Result::OK);
mStream->start();
int16_t buffer[4] = { 1, 2, 3, 4 };
Result r = mStream->write(&buffer, 4, 0);
if (r != Result::OK) {
FAIL() << "Could not write to audio stream";
}
auto f = mStream->getFramesWritten();
ASSERT_EQ(f, 4);
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getFramesWritten(), f);
}
TEST_F(StreamClosedReturnValues, GetFramesReadReturnsLastKnownValue) {
mBuilder.setDirection(Direction::Input);
mBuilder.setFormat(AudioFormat::I16);
mBuilder.setChannelCount(1);
ASSERT_TRUE(openStream());
mStream->start();
int16_t buffer[192];
auto r = mStream->read(&buffer, 192, 1000 * kNanosPerMillisecond);
ASSERT_EQ(r.value(), 192);
auto f = mStream->getFramesRead();
ASSERT_EQ(f, 192);
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getFramesRead(), f);
}
TEST_F(StreamClosedReturnValues, GetTimestampReturnsErrorClosedIfSupported){
ASSERT_TRUE(openStream());
int64_t framePosition;
int64_t presentationTime;
auto r = mStream->getTimestamp(CLOCK_MONOTONIC, &framePosition, &presentationTime);
bool isTimestampSupported = (r == Result::OK);
ASSERT_TRUE(closeStream());
if (isTimestampSupported){
ASSERT_EQ(mStream->getTimestamp(CLOCK_MONOTONIC, &framePosition, &presentationTime), Result::ErrorClosed);
}
}
TEST_F(StreamClosedReturnValues, GetAudioApiReturnsLastKnownValue){
ASSERT_TRUE(openStream());
AudioApi a = mStream->getAudioApi();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->getAudioApi(), a);
}
TEST_F(StreamClosedReturnValues, GetUsesAAudioReturnsLastKnownValue){
ASSERT_TRUE(openStream());
bool a = mStream->usesAAudio();
ASSERT_TRUE(closeStream());
ASSERT_EQ(mStream->usesAAudio(), a);
}
TEST_F(StreamClosedReturnValues, StreamStateControlsReturnClosed){
ASSERT_TRUE(openAndCloseStream());
Result r = mStream->close();
EXPECT_EQ(r, Result::ErrorClosed) << convertToText(r);
r = mStream->start();
EXPECT_EQ(r, Result::ErrorClosed) << convertToText(r);
EXPECT_EQ(mStream->pause(), Result::ErrorClosed);
EXPECT_EQ(mStream->flush(), Result::ErrorClosed);
EXPECT_EQ(mStream->stop(), Result::ErrorClosed);
EXPECT_EQ(mStream->requestStart(), Result::ErrorClosed);
EXPECT_EQ(mStream->requestPause(), Result::ErrorClosed);
EXPECT_EQ(mStream->requestFlush(), Result::ErrorClosed);
EXPECT_EQ(mStream->requestStop(), Result::ErrorClosed);
}
TEST_F(StreamClosedReturnValues, WaitForStateChangeReturnsClosed){
ASSERT_TRUE(openAndCloseStream());
StreamState next;
Result r = mStream->waitForStateChange(StreamState::Open, &next, 0);
EXPECT_TRUE(r == Result::OK || r == Result::ErrorClosed) << convertToText(r);
}
TEST_F(StreamClosedReturnValues, SetBufferSizeInFramesReturnsClosed){
ASSERT_TRUE(openAndCloseStream());
auto r = mStream->setBufferSizeInFrames(192);
ASSERT_EQ(r.error(), Result::ErrorClosed);
}
TEST_F(StreamClosedReturnValues, CalculateLatencyInMillisReturnsClosedIfSupported){
ASSERT_TRUE(openAndCloseStream());
if (mStream->getAudioApi() == AudioApi::AAudio){
auto r = mStream->calculateLatencyMillis();
ASSERT_EQ(r.error(), Result::ErrorInvalidState);
}
}
TEST_F(StreamClosedReturnValues, ReadReturnsClosed){
ASSERT_TRUE(openAndCloseStream());
int buffer[8]{0};
auto r = mStream->read(buffer, 1, 0);
ASSERT_EQ(r.error(), Result::ErrorClosed);
}
TEST_F(StreamClosedReturnValues, WriteReturnsClosed){
ASSERT_TRUE(openAndCloseStream());
int buffer[8]{0};
auto r = mStream->write(buffer, 1, 0);
ASSERT_EQ(r.error(), Result::ErrorClosed);
}
TEST_F(StreamClosedReturnValues, DelayBeforeCloseInput){
if (AudioStreamBuilder::isAAudioRecommended()) {
mBuilder.setDirection(Direction::Input);
testDelayBeforeClose();
}
}
TEST_F(StreamClosedReturnValues, DelayBeforeCloseOutput){
if (AudioStreamBuilder::isAAudioRecommended()) {
mBuilder.setDirection(Direction::Output);
testDelayBeforeClose();
}
}
TEST_F(StreamClosedReturnValues, DelayBeforeCloseInputOpenSL){
mBuilder.setAudioApi(AudioApi::OpenSLES);
mBuilder.setDirection(Direction::Input);
testDelayBeforeClose();
}
TEST_F(StreamClosedReturnValues, DelayBeforeCloseOutputOpenSL){
mBuilder.setAudioApi(AudioApi::OpenSLES);
mBuilder.setDirection(Direction::Output);
testDelayBeforeClose();
}
TEST_F(StreamClosedReturnValues, TestReleaseInput){
mBuilder.setDirection(Direction::Input);
ASSERT_TRUE(openStream());
ASSERT_TRUE(releaseStream());
ASSERT_TRUE(closeStream());
}
TEST_F(StreamClosedReturnValues, TestReleaseInputOpenSLES){
mBuilder.setAudioApi(AudioApi::OpenSLES);
mBuilder.setDirection(Direction::Input);
ASSERT_TRUE(openStream());
ASSERT_TRUE(releaseStream());
ASSERT_TRUE(closeStream());
}
TEST_F(StreamClosedReturnValues, TestReleaseOutput){
mBuilder.setDirection(Direction::Output);
ASSERT_TRUE(openStream());
ASSERT_TRUE(releaseStream());
ASSERT_TRUE(closeStream());
}
TEST_F(StreamClosedReturnValues, TestReleaseOutputOpenSLES){
mBuilder.setAudioApi(AudioApi::OpenSLES);
mBuilder.setDirection(Direction::Output);
ASSERT_TRUE(openStream());
ASSERT_TRUE(releaseStream());
ASSERT_TRUE(closeStream());
}

View file

@ -0,0 +1,99 @@
/*
* Copyright 2021 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 <gtest/gtest.h>
#include <oboe/Oboe.h>
#include <tuple>
using namespace oboe;
class FramesProcessedCallback : public AudioStreamDataCallback {
public:
DataCallbackResult onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override {
return DataCallbackResult::Continue;
}
};
using StreamFramesProcessedParams = std::tuple<Direction, int32_t, bool>;
class StreamFramesProcessed : public ::testing::Test,
public ::testing::WithParamInterface<StreamFramesProcessedParams> {
protected:
void TearDown() override;
static constexpr int PROCESS_TIME_SECONDS = 5;
AudioStreamBuilder mBuilder;
std::shared_ptr<AudioStream> mStream;
};
void StreamFramesProcessed::TearDown() {
if (mStream) {
mStream->close();
}
}
TEST_P(StreamFramesProcessed, VerifyFramesProcessed) {
const Direction direction = std::get<0>(GetParam());
const int32_t sampleRate = std::get<1>(GetParam());
const bool useOboeSampleRateConversion = std::get<2>(GetParam());
SampleRateConversionQuality srcQuality = useOboeSampleRateConversion ?
SampleRateConversionQuality::Medium : SampleRateConversionQuality::None;
AudioStreamDataCallback *callback = new FramesProcessedCallback();
mBuilder.setDirection(direction)
->setFormat(AudioFormat::I16)
->setSampleRate(sampleRate)
->setSampleRateConversionQuality(srcQuality)
->setPerformanceMode(PerformanceMode::LowLatency)
->setSharingMode(SharingMode::Exclusive)
->setDataCallback(callback);
Result r = mBuilder.openStream(mStream);
ASSERT_EQ(r, Result::OK) << "Failed to open stream." << convertToText(r);
r = mStream->start();
ASSERT_EQ(r, Result::OK) << "Failed to start stream." << convertToText(r);
sleep(PROCESS_TIME_SECONDS);
// The frames written should be close to sampleRate * PROCESS_TIME_SECONDS
const int kDeltaFramesWindowInFrames = 30000;
const int64_t framesWritten = mStream->getFramesWritten();
const int64_t framesRead = mStream->getFramesRead();
EXPECT_NEAR(framesWritten, sampleRate * PROCESS_TIME_SECONDS, kDeltaFramesWindowInFrames);
EXPECT_NEAR(framesRead, sampleRate * PROCESS_TIME_SECONDS, kDeltaFramesWindowInFrames);
}
INSTANTIATE_TEST_CASE_P(
StreamFramesProcessedTest,
StreamFramesProcessed,
::testing::Values(
StreamFramesProcessedParams({Direction::Output, 8000, true}),
StreamFramesProcessedParams({Direction::Output, 44100, true}),
StreamFramesProcessedParams({Direction::Output, 96000, true}),
StreamFramesProcessedParams({Direction::Input, 8000, true}),
StreamFramesProcessedParams({Direction::Input, 44100, true}),
StreamFramesProcessedParams({Direction::Input, 96000, true}),
StreamFramesProcessedParams({Direction::Output, 8000, false}),
StreamFramesProcessedParams({Direction::Output, 44100, false}),
StreamFramesProcessedParams({Direction::Output, 96000, false}),
StreamFramesProcessedParams({Direction::Input, 8000, false}),
StreamFramesProcessedParams({Direction::Input, 44100, false}),
StreamFramesProcessedParams({Direction::Input, 96000, false})
)
);

746
externals/oboe/tests/testStreamOpen.cpp vendored Normal file
View file

@ -0,0 +1,746 @@
/*
* 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 <gtest/gtest.h>
#include <aaudio/AAudioExtensions.h>
#include <oboe/Oboe.h>
#include <android/api-level.h>
#ifndef __ANDROID_API_S__
#define __ANDROID_API_S__ 31
#endif
#ifndef __ANDROID_API_S_V2__
#define __ANDROID_API_S_V2__ 32
#endif
using namespace oboe;
class CallbackSizeMonitor : public AudioStreamCallback {
public:
DataCallbackResult onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override {
framesPerCallback = numFrames;
callbackCount++;
return DataCallbackResult::Continue;
}
// This is exposed publicly so that the number of frames per callback can be tested.
std::atomic<int32_t> framesPerCallback{0};
std::atomic<int32_t> callbackCount{0};
};
class StreamOpen : public ::testing::Test {
protected:
bool openStream() {
EXPECT_EQ(mStream, nullptr);
Result r = mBuilder.openStream(mStream);
EXPECT_EQ(r, Result::OK) << "Failed to open stream " << convertToText(r);
EXPECT_EQ(0, openCount) << "Should start with a fresh object every time.";
openCount++;
return (r == Result::OK);
}
bool closeStream() {
if (mStream){
Result r = mStream->close();
EXPECT_EQ(r, Result::OK) << "Failed to close stream. " << convertToText(r);
usleep(500 * 1000); // give previous stream time to settle
return (r == Result::OK);
} else {
return true;
}
}
void checkSampleRateConversionAdvancing(Direction direction) {
CallbackSizeMonitor callback;
mBuilder.setDirection(direction);
if (mBuilder.isAAudioRecommended()) {
mBuilder.setAudioApi(AudioApi::AAudio);
}
mBuilder.setCallback(&callback);
mBuilder.setPerformanceMode(PerformanceMode::LowLatency);
mBuilder.setSampleRate(44100);
mBuilder.setSampleRateConversionQuality(SampleRateConversionQuality::Medium);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->requestStart(), Result::OK);
int timeout = 20;
while (callback.framesPerCallback == 0 && timeout > 0) {
usleep(50 * 1000);
timeout--;
}
// Catch Issue #1166
mStream->getTimestamp(CLOCK_MONOTONIC); // should not crash
mStream->getTimestamp(CLOCK_MONOTONIC, nullptr, nullptr); // should not crash
ASSERT_GT(callback.callbackCount, 0);
ASSERT_GT(callback.framesPerCallback, 0);
ASSERT_EQ(mStream->requestStop(), Result::OK);
ASSERT_TRUE(closeStream());
}
AudioStreamBuilder mBuilder;
std::shared_ptr<AudioStream> mStream;
int32_t openCount = 0;
};
class StreamOpenOutput : public StreamOpen {};
class StreamOpenInput : public StreamOpen {};
TEST_F(StreamOpenOutput, ForOpenSLESDefaultSampleRateIsUsed){
DefaultStreamValues::SampleRate = 44100;
DefaultStreamValues::FramesPerBurst = 192;
mBuilder.setAudioApi(AudioApi::OpenSLES);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getSampleRate(), 44100);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, ForOpenSLESDefaultFramesPerBurstIsUsed){
DefaultStreamValues::SampleRate = 48000;
DefaultStreamValues::FramesPerBurst = 128; // used for low latency
mBuilder.setAudioApi(AudioApi::OpenSLES);
mBuilder.setPerformanceMode(PerformanceMode::LowLatency);
ASSERT_TRUE(openStream());
// Some devices like emulators may not support Low Latency
if (mStream->getPerformanceMode() == PerformanceMode::LowLatency) {
ASSERT_EQ(mStream->getFramesPerBurst(), 128);
}
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, ForOpenSLESDefaultChannelCountIsUsed){
DefaultStreamValues::ChannelCount = 1;
mBuilder.setAudioApi(AudioApi::OpenSLES);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getChannelCount(), 1);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, OutputForOpenSLESPerformanceModeShouldBeNone){
// We will not get a LowLatency stream if we request 16000 Hz.
mBuilder.setSampleRate(16000);
mBuilder.setSampleRateConversionQuality(SampleRateConversionQuality::None);
mBuilder.setPerformanceMode(PerformanceMode::LowLatency);
mBuilder.setDirection(Direction::Output);
mBuilder.setAudioApi(AudioApi::OpenSLES);
ASSERT_TRUE(openStream());
ASSERT_EQ((int)mStream->getPerformanceMode(), (int)PerformanceMode::None);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenInput, InputForOpenSLESPerformanceModeShouldBeNone){
// We will not get a LowLatency stream if we request 16000 Hz.
mBuilder.setSampleRate(16000);
mBuilder.setSampleRateConversionQuality(SampleRateConversionQuality::None);
mBuilder.setPerformanceMode(PerformanceMode::LowLatency);
mBuilder.setDirection(Direction::Input);
mBuilder.setAudioApi(AudioApi::OpenSLES);
ASSERT_TRUE(openStream());
ASSERT_EQ((int)mStream->getPerformanceMode(), (int)PerformanceMode::None);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, ForOpenSlesIllegalFormatRejectedOutput) {
mBuilder.setAudioApi(AudioApi::OpenSLES);
mBuilder.setPerformanceMode(PerformanceMode::LowLatency);
mBuilder.setFormat(static_cast<AudioFormat>(666));
Result r = mBuilder.openStream(mStream);
EXPECT_NE(r, Result::OK) << "Should not open stream " << convertToText(r);
if (mStream != nullptr) {
mStream->close(); // just in case it accidentally opened
}
}
TEST_F(StreamOpenInput, ForOpenSlesIllegalFormatRejectedInput) {
mBuilder.setAudioApi(AudioApi::OpenSLES);
mBuilder.setPerformanceMode(PerformanceMode::LowLatency);
mBuilder.setDirection(Direction::Input);
mBuilder.setFormat(static_cast<AudioFormat>(666));
Result r = mBuilder.openStream(mStream);
EXPECT_NE(r, Result::OK) << "Should not open stream " << convertToText(r);
if (mStream != nullptr) {
mStream->close(); // just in case it accidentally opened
}
}
// Make sure the callback is called with the requested FramesPerCallback
TEST_F(StreamOpenOutput, OpenSLESFramesPerCallback) {
const int kRequestedFramesPerCallback = 417;
CallbackSizeMonitor callback;
DefaultStreamValues::SampleRate = 48000;
DefaultStreamValues::ChannelCount = 2;
DefaultStreamValues::FramesPerBurst = 192;
mBuilder.setAudioApi(AudioApi::OpenSLES);
mBuilder.setFramesPerCallback(kRequestedFramesPerCallback);
mBuilder.setCallback(&callback);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->requestStart(), Result::OK);
int timeout = 20;
while (callback.framesPerCallback == 0 && timeout > 0) {
usleep(50 * 1000);
timeout--;
}
ASSERT_EQ(kRequestedFramesPerCallback, callback.framesPerCallback);
ASSERT_EQ(kRequestedFramesPerCallback, mStream->getFramesPerCallback());
ASSERT_EQ(mStream->requestStop(), Result::OK);
ASSERT_TRUE(closeStream());
}
// Make sure the LowLatency callback has the requested FramesPerCallback.
TEST_F(StreamOpen, AAudioFramesPerCallbackLowLatency) {
const int kRequestedFramesPerCallback = 192;
CallbackSizeMonitor callback;
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setFramesPerCallback(kRequestedFramesPerCallback);
mBuilder.setCallback(&callback);
mBuilder.setPerformanceMode(PerformanceMode::LowLatency);
ASSERT_TRUE(openStream());
ASSERT_EQ(kRequestedFramesPerCallback, mStream->getFramesPerCallback());
ASSERT_EQ(mStream->requestStart(), Result::OK);
int timeout = 20;
while (callback.framesPerCallback == 0 && timeout > 0) {
usleep(50 * 1000);
timeout--;
}
ASSERT_EQ(kRequestedFramesPerCallback, callback.framesPerCallback);
ASSERT_EQ(mStream->requestStop(), Result::OK);
ASSERT_TRUE(closeStream());
}
// Make sure the regular callback has the requested FramesPerCallback.
TEST_F(StreamOpen, AAudioFramesPerCallbackNone) {
const int kRequestedFramesPerCallback = 1024;
CallbackSizeMonitor callback;
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setFramesPerCallback(kRequestedFramesPerCallback);
mBuilder.setCallback(&callback);
mBuilder.setPerformanceMode(PerformanceMode::None);
ASSERT_TRUE(openStream());
ASSERT_EQ(kRequestedFramesPerCallback, mStream->getFramesPerCallback());
ASSERT_EQ(mStream->requestStart(), Result::OK);
int timeout = 20;
while (callback.framesPerCallback == 0 && timeout > 0) {
usleep(50 * 1000);
timeout--;
}
ASSERT_EQ(kRequestedFramesPerCallback, callback.framesPerCallback);
ASSERT_EQ(mStream->requestStop(), Result::OK);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenInput, RecordingFormatUnspecifiedReturnsI16BeforeMarshmallow){
if (getSdkVersion() < __ANDROID_API_M__){
mBuilder.setDirection(Direction::Input);
mBuilder.setFormat(AudioFormat::Unspecified);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::I16);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenInput, RecordingFormatUnspecifiedReturnsFloatOnMarshmallowAndLater){
if (getSdkVersion() >= __ANDROID_API_M__){
mBuilder.setDirection(Direction::Input);
mBuilder.setFormat(AudioFormat::Unspecified);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::Float);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenInput, RecordingFormatFloatReturnsErrorBeforeMarshmallow){
if (getSdkVersion() < __ANDROID_API_M__){
mBuilder.setDirection(Direction::Input);
mBuilder.setFormat(AudioFormat::Float);
Result r = mBuilder.openStream(mStream);
ASSERT_EQ(r, Result::ErrorInvalidFormat) << convertToText(r);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenInput, RecordingFormatFloatReturnsFloatOnMarshmallowAndLater){
if (getSdkVersion() >= __ANDROID_API_M__){
mBuilder.setDirection(Direction::Input);
mBuilder.setFormat(AudioFormat::Float);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::Float);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenInput, RecordingFormatI16ReturnsI16){
mBuilder.setDirection(Direction::Input);
mBuilder.setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::I16);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, PlaybackFormatUnspecifiedReturnsI16BeforeLollipop){
if (getSdkVersion() < __ANDROID_API_L__){
mBuilder.setDirection(Direction::Output);
mBuilder.setFormat(AudioFormat::Unspecified);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::I16);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenOutput, PlaybackFormatUnspecifiedReturnsFloatOnLollipopAndLater){
if (getSdkVersion() >= __ANDROID_API_L__){
mBuilder.setDirection(Direction::Output);
mBuilder.setFormat(AudioFormat::Unspecified);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::Float);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenOutput, PlaybackFormatFloatReturnsErrorBeforeLollipop){
if (getSdkVersion() < __ANDROID_API_L__){
mBuilder.setDirection(Direction::Output);
mBuilder.setFormat(AudioFormat::Float);
Result r = mBuilder.openStream(mStream);
ASSERT_EQ(r, Result::ErrorInvalidFormat);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenOutput, PlaybackFormatFloatReturnsFloatWithFormatConversionAllowed){
mBuilder.setDirection(Direction::Output);
mBuilder.setFormat(AudioFormat::Float);
mBuilder.setFormatConversionAllowed(true);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::Float);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, PlaybackFormatFloatReturnsFloatOnLollipopAndLater){
if (getSdkVersion() >= __ANDROID_API_L__){
mBuilder.setDirection(Direction::Output);
mBuilder.setFormat(AudioFormat::Float);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::Float);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenOutput, PlaybackFormatI16ReturnsI16) {
mBuilder.setDirection(Direction::Output);
mBuilder.setFormat(AudioFormat::I16);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getFormat(), AudioFormat::I16);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, OpenCloseLowLatencyStream){
mBuilder.setDirection(Direction::Output);
mBuilder.setPerformanceMode(PerformanceMode::LowLatency);
float *buf = new float[100];
ASSERT_TRUE(openStream());
delete[] buf;
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, LowLatencyStreamHasSmallBufferSize){
if (mBuilder.isAAudioRecommended()) {
mBuilder.setDirection(Direction::Output);
mBuilder.setPerformanceMode(PerformanceMode::LowLatency);
ASSERT_TRUE(openStream());
int32_t bufferSize = mStream->getBufferSizeInFrames();
int32_t burst = mStream->getFramesPerBurst();
ASSERT_TRUE(closeStream());
ASSERT_LE(bufferSize, burst * 3);
}
}
// Make sure the parameters get copied from the child stream.
TEST_F(StreamOpenOutput, AAudioOutputSampleRate44100FilterConfiguration) {
if (mBuilder.isAAudioRecommended()) {
mBuilder.setDirection(Direction::Output);
mBuilder.setPerformanceMode(PerformanceMode::LowLatency);
mBuilder.setSharingMode(SharingMode::Exclusive);
// Try to force the use of a FilterAudioStream by requesting conversion.
mBuilder.setSampleRate(44100);
mBuilder.setSampleRateConversionQuality(SampleRateConversionQuality::Medium);
ASSERT_TRUE(openStream());
if (getSdkVersion() >= __ANDROID_API_U__) {
ASSERT_LT(0, mStream->getHardwareSampleRate());
ASSERT_LT(0, mStream->getHardwareChannelCount());
ASSERT_LT(0, (int)mStream->getHardwareFormat());
}
// If MMAP is not supported then we cannot get an EXCLUSIVE mode stream.
if (!AAudioExtensions::getInstance().isMMapSupported()) {
ASSERT_NE(SharingMode::Exclusive, mStream->getSharingMode()); // IMPOSSIBLE
}
ASSERT_TRUE(closeStream());
}
}
// See if sample rate conversion by Oboe is calling the callback.
TEST_F(StreamOpenOutput, AAudioOutputSampleRate44100) {
checkSampleRateConversionAdvancing(Direction::Output);
}
// See if sample rate conversion by Oboe is calling the callback.
TEST_F(StreamOpenInput, AAudioInputSampleRate44100) {
checkSampleRateConversionAdvancing(Direction::Input);
}
TEST_F(StreamOpenOutput, AAudioOutputSetPackageName){
if (getSdkVersion() >= __ANDROID_API_S__){
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setPackageName("com.google.oboe.tests.unittestrunner");
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->requestStart(), Result::OK);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenInput, AAudioInputSetPackageName){
if (getSdkVersion() >= __ANDROID_API_S__){
mBuilder.setDirection(Direction::Input);
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setPackageName("com.google.oboe.tests.unittestrunner");
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->requestStart(), Result::OK);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenOutput, AAudioOutputSetAttributionTag){
if (getSdkVersion() >= __ANDROID_API_S__){
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setAttributionTag("TestSetOutputAttributionTag");
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->requestStart(), Result::OK);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenInput, AAudioInputSetAttributionTag){
if (getSdkVersion() >= __ANDROID_API_S__){
mBuilder.setDirection(Direction::Input);
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setAttributionTag("TestSetInputAttributionTag");
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->requestStart(), Result::OK);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenInput, AAudioInputSetSpatializationBehavior) {
mBuilder.setDirection(Direction::Input);
mBuilder.setSpatializationBehavior(SpatializationBehavior::Auto);
ASSERT_TRUE(openStream());
if (getSdkVersion() >= __ANDROID_API_S_V2__){
ASSERT_EQ(mStream->getSpatializationBehavior(), SpatializationBehavior::Auto);
} else {
ASSERT_EQ(mStream->getSpatializationBehavior(), SpatializationBehavior::Never);
}
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, AAudioOutputSetSpatializationBehavior) {
mBuilder.setDirection(Direction::Output);
mBuilder.setSpatializationBehavior(SpatializationBehavior::Never);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getSpatializationBehavior(), SpatializationBehavior::Never);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, OpenSLESOutputSetSpatializationBehavior) {
mBuilder.setDirection(Direction::Output);
mBuilder.setAudioApi(AudioApi::OpenSLES);
mBuilder.setSpatializationBehavior(SpatializationBehavior::Auto);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getSpatializationBehavior(), SpatializationBehavior::Never);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenInput, AAudioInputSetSpatializationBehaviorUnspecified) {
mBuilder.setDirection(Direction::Input);
mBuilder.setSpatializationBehavior(SpatializationBehavior::Unspecified);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getSpatializationBehavior(), SpatializationBehavior::Never);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, AAudioOutputSetSpatializationBehaviorUnspecified) {
mBuilder.setDirection(Direction::Output);
mBuilder.setSpatializationBehavior(SpatializationBehavior::Unspecified);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getSpatializationBehavior(), SpatializationBehavior::Never);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenInput, AAudioInputSetIsContentSpatialized) {
mBuilder.setDirection(Direction::Input);
mBuilder.setIsContentSpatialized(true);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->isContentSpatialized(), true);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, AAudioOutputSetIsContentSpatialized) {
mBuilder.setDirection(Direction::Output);
mBuilder.setIsContentSpatialized(true);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->isContentSpatialized(), true);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, OpenSLESOutputSetIsContentSpatialized) {
mBuilder.setDirection(Direction::Output);
mBuilder.setAudioApi(AudioApi::OpenSLES);
mBuilder.setIsContentSpatialized(true);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->isContentSpatialized(), true);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, AAudioOutputSetIsContentSpatializedFalse) {
mBuilder.setDirection(Direction::Output);
mBuilder.setIsContentSpatialized(false);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->isContentSpatialized(), false);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, AAudioOutputSetIsContentSpatializedUnspecified) {
mBuilder.setDirection(Direction::Output);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->isContentSpatialized(), false);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenInput, AAudioInputSetIsContentSpatializedUnspecified) {
mBuilder.setDirection(Direction::Input);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->isContentSpatialized(), false);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, OutputForOpenSLESPerformanceModeNoneGetBufferSizeInFrames){
mBuilder.setPerformanceMode(PerformanceMode::None);
mBuilder.setAudioApi(AudioApi::OpenSLES);
ASSERT_TRUE(openStream());
EXPECT_GT(mStream->getBufferSizeInFrames(), 0);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, OboeExtensions){
if (OboeExtensions::isMMapSupported()) {
ASSERT_EQ(OboeExtensions::setMMapEnabled(true), 0);
ASSERT_TRUE(OboeExtensions::isMMapEnabled());
ASSERT_EQ(OboeExtensions::setMMapEnabled(false), 0);
ASSERT_FALSE(OboeExtensions::isMMapEnabled());
ASSERT_TRUE(openStream());
EXPECT_FALSE(OboeExtensions::isMMapUsed(mStream.get()));
ASSERT_TRUE(closeStream());
ASSERT_EQ(OboeExtensions::setMMapEnabled(true), 0);
ASSERT_TRUE(OboeExtensions::isMMapEnabled());
}
}
TEST_F(StreamOpenInput, AAudioInputSetPrivacySensitiveModeUnspecifiedUnprocessed){
if (getSdkVersion() >= __ANDROID_API_R__){
mBuilder.setDirection(Direction::Input);
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setInputPreset(InputPreset::Unprocessed);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getPrivacySensitiveMode(), PrivacySensitiveMode::Disabled);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenInput, AAudioInputSetPrivacySensitiveModeUnspecifiedVoiceCommunication){
if (getSdkVersion() >= __ANDROID_API_R__){
mBuilder.setDirection(Direction::Input);
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setInputPreset(InputPreset::VoiceCommunication);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getPrivacySensitiveMode(), PrivacySensitiveMode::Enabled);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenInput, AAudioInputSetPrivacySensitiveModeVoiceDisabled){
if (getSdkVersion() >= __ANDROID_API_R__){
mBuilder.setDirection(Direction::Input);
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setInputPreset(InputPreset::VoiceCommunication);
mBuilder.setPrivacySensitiveMode(PrivacySensitiveMode::Disabled);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getPrivacySensitiveMode(), PrivacySensitiveMode::Disabled);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenInput, AAudioInputSetPrivacySensitiveModeUnprocessedEnabled){
if (getSdkVersion() >= __ANDROID_API_R__){
mBuilder.setDirection(Direction::Input);
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setInputPreset(InputPreset::Unprocessed);
mBuilder.setPrivacySensitiveMode(PrivacySensitiveMode::Enabled);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getPrivacySensitiveMode(), PrivacySensitiveMode::Enabled);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenOutput, AAudioOutputSetPrivacySensitiveModeGetsUnspecified){
if (getSdkVersion() >= __ANDROID_API_R__){
mBuilder.setDirection(Direction::Output);
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setPrivacySensitiveMode(PrivacySensitiveMode::Enabled);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getPrivacySensitiveMode(), PrivacySensitiveMode::Unspecified);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenInput, OpenSLESInputSetPrivacySensitiveModeDoesNotCrash){
mBuilder.setDirection(Direction::Input);
mBuilder.setAudioApi(AudioApi::OpenSLES);
mBuilder.setInputPreset(InputPreset::Unprocessed);
mBuilder.setPrivacySensitiveMode(PrivacySensitiveMode::Enabled);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getPrivacySensitiveMode(), PrivacySensitiveMode::Unspecified);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenInput, OldAndroidVersionInputSetPrivacySensitiveModeDoesNotCrash){
if (getSdkVersion() < __ANDROID_API_R__) {
mBuilder.setDirection(Direction::Input);
mBuilder.setInputPreset(InputPreset::Unprocessed);
mBuilder.setPrivacySensitiveMode(PrivacySensitiveMode::Enabled);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getPrivacySensitiveMode(), PrivacySensitiveMode::Unspecified);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenOutput, AAudioOutputSetAllowedCapturePolicyUnspecifiedGetsAll){
if (getSdkVersion() >= __ANDROID_API_Q__){
mBuilder.setDirection(Direction::Output);
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setAllowedCapturePolicy(AllowedCapturePolicy::Unspecified);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getAllowedCapturePolicy(), AllowedCapturePolicy::All);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenOutput, AAudioOutputSetAllowedCapturePolicyAll){
if (getSdkVersion() >= __ANDROID_API_Q__){
mBuilder.setDirection(Direction::Output);
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setAllowedCapturePolicy(AllowedCapturePolicy::All);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getAllowedCapturePolicy(), AllowedCapturePolicy::All);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenOutput, AAudioOutputSetAllowedCapturePolicySystem){
if (getSdkVersion() >= __ANDROID_API_Q__){
mBuilder.setDirection(Direction::Output);
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setAllowedCapturePolicy(AllowedCapturePolicy::System);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getAllowedCapturePolicy(), AllowedCapturePolicy::System);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenOutput, AAudioOutputSetAllowedCapturePolicyNone){
if (getSdkVersion() >= __ANDROID_API_Q__){
mBuilder.setDirection(Direction::Output);
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setAllowedCapturePolicy(AllowedCapturePolicy::None);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getAllowedCapturePolicy(), AllowedCapturePolicy::None);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenOutput, AAudioOutputDoNotSetAllowedCapturePolicy){
mBuilder.setDirection(Direction::Output);
mBuilder.setAudioApi(AudioApi::AAudio);
ASSERT_TRUE(openStream());
if (getSdkVersion() >= __ANDROID_API_Q__){
ASSERT_EQ(mStream->getAllowedCapturePolicy(), AllowedCapturePolicy::All);
} else {
ASSERT_EQ(mStream->getAllowedCapturePolicy(), AllowedCapturePolicy::Unspecified);
}
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, OpenSLESOutputSetAllowedCapturePolicyAllGetsUnspecified){
mBuilder.setDirection(Direction::Output);
mBuilder.setAudioApi(AudioApi::OpenSLES);
mBuilder.setAllowedCapturePolicy(AllowedCapturePolicy::All);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getAllowedCapturePolicy(), AllowedCapturePolicy::Unspecified);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamOpenOutput, AAudioBeforeQOutputSetAllowedCapturePolicyAllGetsUnspecified){
if (getSdkVersion() < __ANDROID_API_Q__){
mBuilder.setDirection(Direction::Output);
mBuilder.setAudioApi(AudioApi::AAudio);
mBuilder.setAllowedCapturePolicy(AllowedCapturePolicy::All);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getAllowedCapturePolicy(), AllowedCapturePolicy::Unspecified);
ASSERT_TRUE(closeStream());
}
}
TEST_F(StreamOpenInput, AAudioInputSetAllowedCapturePolicyAllGetsUnspecified){
mBuilder.setDirection(Direction::Input);
mBuilder.setAllowedCapturePolicy(AllowedCapturePolicy::All);
ASSERT_TRUE(openStream());
ASSERT_EQ(mStream->getAllowedCapturePolicy(), AllowedCapturePolicy::Unspecified);
ASSERT_TRUE(closeStream());
}

View file

@ -0,0 +1,305 @@
/*
* 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 <gtest/gtest.h>
#include <oboe/Oboe.h>
using namespace oboe;
// Sleep between close and open to avoid a race condition inside Android Audio.
// On a Pixel 2 emulator on a fast Linux host, the minimum value is around 16 msec.
constexpr int kOboeOpenCloseSleepMSec = 100;
class StreamStates : public ::testing::Test {
protected:
void SetUp(){
mBuilder.setPerformanceMode(PerformanceMode::None);
mBuilder.setDirection(Direction::Output);
}
bool openStream(Direction direction) {
usleep(100 * 1000);
mBuilder.setDirection(direction);
Result r = mBuilder.openStream(mStream);
EXPECT_EQ(r, Result::OK) << "Failed to open stream " << convertToText(r);
if (r != Result::OK)
return false;
Direction d = mStream->getDirection();
EXPECT_EQ(d, direction) << convertToText(mStream->getDirection());
return (d == direction);
}
bool openStream() {
return openStream(Direction::Output);
}
bool openInputStream() {
return openStream(Direction::Input);
}
bool closeStream() {
Result r = mStream->close();
EXPECT_EQ(r, Result::OK) << "Failed to close stream. " << convertToText(r);
return (r == Result::OK);
}
void checkStreamStateIsStartedAfterStartingTwice(Direction direction) {
ASSERT_TRUE(openStream(direction));
StreamState next = StreamState::Unknown;
auto r = mStream->requestStart();
EXPECT_EQ(r, Result::OK) << "requestStart returned: " << convertToText(r);
r = mStream->waitForStateChange(StreamState::Starting, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK);
EXPECT_EQ(next, StreamState::Started);
next = StreamState::Unknown;
r = mStream->requestStart();
// TODO On P, AAudio is returning ErrorInvalidState for Output and OK for Input
// EXPECT_EQ(r, Result::OK) << "requestStart returned: " << convertToText(r);
r = mStream->waitForStateChange(StreamState::Starting, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK);
ASSERT_EQ(next, StreamState::Started);
ASSERT_TRUE(closeStream());
}
void checkStreamStateIsStoppedAfterStoppingTwice(Direction direction) {
ASSERT_TRUE(openStream(direction));
StreamState next = StreamState::Unknown;
auto r = mStream->requestStart();
EXPECT_EQ(r, Result::OK);
r = mStream->requestStop();
EXPECT_EQ(r, Result::OK);
r = mStream->waitForStateChange(StreamState::Stopping, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK);
EXPECT_EQ(next, StreamState::Stopped);
r = mStream->requestStop();
EXPECT_EQ(r, Result::OK);
next = StreamState::Unknown;
r = mStream->waitForStateChange(StreamState::Stopping, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK);
ASSERT_EQ(next, StreamState::Stopped);
ASSERT_TRUE(closeStream());
}
// TODO: This seems to fail intermittently on Pixel OC_MR1 !
void checkStreamLeftRunningShouldNotInterfereWithNextOpen(Direction direction) {
ASSERT_TRUE(openStream(direction));
auto r = mStream->requestStart();
EXPECT_EQ(r, Result::OK);
// It should be safe to close without stopping.
// The underlying API should stop the stream.
ASSERT_TRUE(closeStream());
usleep(kOboeOpenCloseSleepMSec * 1000); // avoid race condition in emulator
ASSERT_TRUE(openInputStream());
r = mStream->requestStart();
ASSERT_EQ(r, Result::OK) << "requestStart returned: " << convertToText(r);
r = mStream->requestStop();
EXPECT_EQ(r, Result::OK) << "requestStop returned: " << convertToText(r);
ASSERT_TRUE(closeStream());
}
AudioStreamBuilder mBuilder;
std::shared_ptr<AudioStream> mStream;
static constexpr int kTimeoutInNanos = 500 * kNanosPerMillisecond;
};
TEST_F(StreamStates, OutputStreamStateIsOpenAfterOpening){
ASSERT_TRUE(openStream());
StreamState next = StreamState::Unknown;
Result r = mStream->waitForStateChange(StreamState::Uninitialized, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK) << convertToText(r);
ASSERT_EQ(next, StreamState::Open) << convertToText(next);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamStates, OutputStreamStateIsStartedAfterStarting){
ASSERT_TRUE(openStream());
StreamState next = StreamState::Unknown;
auto r = mStream->requestStart();
EXPECT_EQ(r, Result::OK);
r = mStream->waitForStateChange(StreamState::Starting, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK);
ASSERT_EQ(next, StreamState::Started);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamStates, OutputStreamStateIsPausedAfterPausing){
ASSERT_TRUE(openStream());
StreamState next = StreamState::Unknown;
auto r = mStream->requestStart();
EXPECT_EQ(r, Result::OK);
r = mStream->requestPause();
EXPECT_EQ(r, Result::OK);
r = mStream->waitForStateChange(StreamState::Pausing, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK);
ASSERT_EQ(next, StreamState::Paused);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamStates, OutputStreamStateIsStoppedAfterStopping){
ASSERT_TRUE(openStream());
StreamState next = StreamState::Unknown;
auto r = mStream->requestStart();
EXPECT_EQ(r, Result::OK);
r = mStream->requestStop();
r = mStream->waitForStateChange(StreamState::Stopping, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK);
ASSERT_EQ(next, StreamState::Stopped);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamStates, InputStreamStateIsOpenAfterOpening){
ASSERT_TRUE(openInputStream());
StreamState next = StreamState::Unknown;
Result r = mStream->waitForStateChange(StreamState::Uninitialized, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK) << convertToText(r);
ASSERT_EQ(next, StreamState::Open) << convertToText(next);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamStates, InputStreamStateIsStartedAfterStarting){
ASSERT_TRUE(openInputStream());
StreamState next = StreamState::Unknown;
auto r = mStream->requestStart();
EXPECT_EQ(r, Result::OK);
r = mStream->waitForStateChange(StreamState::Starting, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK);
ASSERT_EQ(next, StreamState::Started);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamStates, OutputStreamStateIsStartedAfterStartingTwice){
checkStreamStateIsStartedAfterStartingTwice(Direction::Output);
}
TEST_F(StreamStates, InputStreamStateIsStartedAfterStartingTwice){
checkStreamStateIsStartedAfterStartingTwice(Direction::Input);
}
TEST_F(StreamStates, OutputStreamStateIsStoppedAfterStoppingTwice){
checkStreamStateIsStoppedAfterStoppingTwice(Direction::Output);
}
TEST_F(StreamStates, InputStreamStateIsStoppedAfterStoppingTwice){
checkStreamStateIsStoppedAfterStoppingTwice(Direction::Input);
}
TEST_F(StreamStates, OutputStreamStateIsPausedAfterPausingTwice){
ASSERT_TRUE(openStream());
StreamState next = StreamState::Unknown;
auto r = mStream->requestStart();
EXPECT_EQ(r, Result::OK);
r = mStream->requestPause();
EXPECT_EQ(r, Result::OK);
r = mStream->waitForStateChange(StreamState::Pausing, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK);
EXPECT_EQ(next, StreamState::Paused);
// requestPause() while already paused could leave us in Pausing in AAudio O_MR1.
r = mStream->requestPause();
EXPECT_EQ(r, Result::OK);
next = StreamState::Unknown;
r = mStream->waitForStateChange(StreamState::Pausing, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK);
ASSERT_EQ(next, StreamState::Paused);
ASSERT_TRUE(closeStream());
}
TEST_F(StreamStates, InputStreamDoesNotSupportPause){
ASSERT_TRUE(openInputStream());
auto r = mStream->requestStart();
EXPECT_EQ(r, Result::OK);
r = mStream->requestPause();
ASSERT_EQ(r, Result::ErrorUnimplemented) << convertToText(r);
mStream->requestStop();
ASSERT_TRUE(closeStream());
}
TEST_F(StreamStates, OutputStreamLeftRunningShouldNotInterfereWithNextOpen) {
checkStreamLeftRunningShouldNotInterfereWithNextOpen(Direction::Output);
}
TEST_F(StreamStates, InputStreamLeftRunningShouldNotInterfereWithNextOpen) {
checkStreamLeftRunningShouldNotInterfereWithNextOpen(Direction::Input);
}
TEST_F(StreamStates, OutputLowLatencyStreamLeftRunningShouldNotInterfereWithNextOpen) {
mBuilder.setPerformanceMode(PerformanceMode::LowLatency);
checkStreamLeftRunningShouldNotInterfereWithNextOpen(Direction::Output);
}
TEST_F(StreamStates, InputLowLatencyStreamLeftRunningShouldNotInterfereWithNextOpen) {
mBuilder.setPerformanceMode(PerformanceMode::LowLatency);
checkStreamLeftRunningShouldNotInterfereWithNextOpen(Direction::Input);
}
TEST_F(StreamStates, InputStreamStateIsStoppedAfterStopping){
ASSERT_TRUE(openInputStream());
StreamState next = StreamState::Unknown;
auto r = mStream->requestStart();
EXPECT_EQ(r, Result::OK) << "requestStart returned: " << convertToText(r);
r = mStream->requestStop();
EXPECT_EQ(r, Result::OK) << "requestStop returned: " << convertToText(r);
r = mStream->waitForStateChange(StreamState::Stopping, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK) << "waitForStateChange returned: " << convertToText(r);
ASSERT_EQ(next, StreamState::Stopped);
ASSERT_TRUE(closeStream());
}

130
externals/oboe/tests/testStreamStop.cpp vendored Normal file
View file

@ -0,0 +1,130 @@
/*
* Copyright 2022 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 <thread>
#include <gtest/gtest.h>
#include <oboe/Oboe.h>
using namespace oboe;
using TestStreamStopParams = std::tuple<Direction, AudioApi, PerformanceMode>;
class TestStreamStop : public ::testing::Test,
public ::testing::WithParamInterface<TestStreamStopParams> {
protected:
void SetUp(){
mBuilder.setPerformanceMode(PerformanceMode::None);
mBuilder.setDirection(Direction::Output);
}
bool openStream(Direction direction, AudioApi audioApi, PerformanceMode perfMode) {
mBuilder.setDirection(direction);
if (mBuilder.isAAudioRecommended()) {
mBuilder.setAudioApi(audioApi);
}
mBuilder.setPerformanceMode(perfMode);
mBuilder.setChannelCount(1);
mBuilder.setFormat(AudioFormat::I16);
Result r = mBuilder.openStream(mStream);
EXPECT_EQ(r, Result::OK) << "Failed to open stream " << convertToText(r);
if (r != Result::OK)
return false;
Direction d = mStream->getDirection();
EXPECT_EQ(d, direction) << convertToText(mStream->getDirection());
return (d == direction);
}
bool openStream(AudioStreamBuilder &builder) {
Result r = builder.openStream(mStream);
EXPECT_EQ(r, Result::OK) << "Failed to open stream " << convertToText(r);
return (r == Result::OK);
}
void stopWhileUsingLargeBuffer() {
StreamState next = StreamState::Unknown;
auto r = mStream->requestStart();
EXPECT_EQ(r, Result::OK);
r = mStream->waitForStateChange(StreamState::Starting, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK);
EXPECT_EQ(next, StreamState::Started) << "next = " << convertToText(next);
std::shared_ptr<AudioStream> str = mStream;
int16_t buffer[kFramesToWrite] = {};
std::thread stopper([str] {
int64_t estimatedCompletionTimeUs = kMicroSecondsPerSecond * kFramesToWrite / str->getSampleRate();
usleep(estimatedCompletionTimeUs / 2); // Stop halfway during the read/write
EXPECT_EQ(str->close(), Result::OK);
});
if (mBuilder.getDirection() == Direction::Output) {
r = mStream->write(&buffer, kFramesToWrite, kTimeoutInNanos);
} else {
r = mStream->read(&buffer, kFramesToWrite, kTimeoutInNanos);
}
if (r != Result::OK) {
FAIL() << "Could not read/write to audio stream: " << static_cast<int>(r);
}
stopper.join();
r = mStream->waitForStateChange(StreamState::Started, &next,
1000 * kNanosPerMillisecond);
if ((r != Result::ErrorClosed) && (r != Result::OK)) {
FAIL() << "Wrong closed result type: " << static_cast<int>(r);
}
}
AudioStreamBuilder mBuilder;
std::shared_ptr<AudioStream> mStream;
static constexpr int kTimeoutInNanos = 1000 * kNanosPerMillisecond;
static constexpr int64_t kMicroSecondsPerSecond = 1000000;
static constexpr int kFramesToWrite = 10000;
};
TEST_P(TestStreamStop, VerifyTestStreamStop) {
const Direction direction = std::get<0>(GetParam());
const AudioApi audioApi = std::get<1>(GetParam());
const PerformanceMode performanceMode = std::get<2>(GetParam());
ASSERT_TRUE(openStream(direction, audioApi, performanceMode));
stopWhileUsingLargeBuffer();
}
INSTANTIATE_TEST_SUITE_P(
TestStreamStopTest,
TestStreamStop,
::testing::Values(
TestStreamStopParams({Direction::Output, AudioApi::AAudio, PerformanceMode::LowLatency}),
TestStreamStopParams({Direction::Output, AudioApi::AAudio, PerformanceMode::None}),
TestStreamStopParams({Direction::Output, AudioApi::AAudio, PerformanceMode::PowerSaving}),
TestStreamStopParams({Direction::Output, AudioApi::OpenSLES, PerformanceMode::LowLatency}),
TestStreamStopParams({Direction::Output, AudioApi::OpenSLES, PerformanceMode::None}),
TestStreamStopParams({Direction::Output, AudioApi::OpenSLES, PerformanceMode::PowerSaving}),
TestStreamStopParams({Direction::Input, AudioApi::AAudio, PerformanceMode::LowLatency}),
TestStreamStopParams({Direction::Input, AudioApi::AAudio, PerformanceMode::None}),
TestStreamStopParams({Direction::Input, AudioApi::AAudio, PerformanceMode::PowerSaving}),
TestStreamStopParams({Direction::Input, AudioApi::OpenSLES, PerformanceMode::LowLatency}),
TestStreamStopParams({Direction::Input, AudioApi::OpenSLES, PerformanceMode::None}),
TestStreamStopParams({Direction::Input, AudioApi::OpenSLES, PerformanceMode::PowerSaving})
)
);

View file

@ -0,0 +1,251 @@
/*
* Copyright 2019 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 <thread>
#include <gtest/gtest.h>
#include <oboe/Oboe.h>
using namespace oboe;
class TestStreamWaitState : public ::testing::Test {
protected:
void SetUp(){
mBuilder.setPerformanceMode(PerformanceMode::None);
mBuilder.setDirection(Direction::Output);
}
bool openStream(Direction direction, PerformanceMode perfMode) {
mBuilder.setDirection(direction);
mBuilder.setPerformanceMode(perfMode);
Result r = mBuilder.openStream(mStream);
EXPECT_EQ(r, Result::OK) << "Failed to open stream " << convertToText(r);
if (r != Result::OK)
return false;
Direction d = mStream->getDirection();
EXPECT_EQ(d, direction) << convertToText(mStream->getDirection());
return (d == direction);
}
bool openStream(AudioStreamBuilder &builder) {
Result r = builder.openStream(mStream);
EXPECT_EQ(r, Result::OK) << "Failed to open stream " << convertToText(r);
return (r == Result::OK);
}
bool closeStream() {
Result r = mStream->close();
EXPECT_TRUE(r == Result::OK || r == Result::ErrorClosed) <<
"Failed to close stream. " << convertToText(r);
return (r == Result::OK || r == Result::ErrorClosed);
}
// if zero then don't wait for a state change
void checkWaitForStateChangeTimeout(int64_t timeout = kTimeoutInNanos) {
StreamState next = StreamState::Unknown;
Result result = mStream->waitForStateChange(mStream->getState(), &next, timeout);
EXPECT_EQ(Result::ErrorTimeout, result);
}
void checkStopWhileWaiting() {
StreamState next = StreamState::Unknown;
auto r = mStream->requestStart();
EXPECT_EQ(r, Result::OK);
r = mStream->waitForStateChange(StreamState::Starting, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK);
EXPECT_EQ(next, StreamState::Started) << "next = " << convertToText(next);
std::shared_ptr<AudioStream> str = mStream;
std::thread stopper([str] {
usleep(200 * 1000);
str->requestStop();
});
r = mStream->waitForStateChange(StreamState::Started, &next, 1000 * kNanosPerMillisecond);
stopper.join();
EXPECT_EQ(r, Result::OK);
// May have caught in stopping transition. Wait for full stop.
if (next == StreamState::Stopping) {
r = mStream->waitForStateChange(StreamState::Stopping, &next, 1000 * kNanosPerMillisecond);
EXPECT_EQ(r, Result::OK);
}
ASSERT_EQ(next, StreamState::Stopped) << "next = " << convertToText(next);
}
void checkCloseWhileWaiting() {
StreamState next = StreamState::Unknown;
auto r = mStream->requestStart();
EXPECT_EQ(r, Result::OK);
r = mStream->waitForStateChange(StreamState::Starting, &next, kTimeoutInNanos);
EXPECT_EQ(r, Result::OK);
EXPECT_EQ(next, StreamState::Started) << "next = " << convertToText(next);
std::shared_ptr<AudioStream> str = mStream;
std::thread closer([str] {
usleep(200 * 1000);
str->close();
});
r = mStream->waitForStateChange(StreamState::Started, &next, 1000 * kNanosPerMillisecond);
closer.join();
// You might catch this at any point in stopping or closing.
EXPECT_TRUE(r == Result::OK || r == Result::ErrorClosed) << "r = " << convertToText(r);
ASSERT_TRUE(next == StreamState::Stopping
|| next == StreamState::Stopped
|| next == StreamState::Pausing
|| next == StreamState::Paused
|| next == StreamState::Closed) << "next = " << convertToText(next);
}
AudioStreamBuilder mBuilder;
std::shared_ptr<AudioStream> mStream;
static constexpr int kTimeoutInNanos = 100 * kNanosPerMillisecond;
};
// Test return of error timeout when zero passed as the timeoutNanos.
TEST_F(TestStreamWaitState, OutputLowWaitZero) {
ASSERT_TRUE(openStream(Direction::Output, PerformanceMode::LowLatency));
checkWaitForStateChangeTimeout(0);
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, OutputNoneWaitZero) {
ASSERT_TRUE(openStream(Direction::Output, PerformanceMode::None));
checkWaitForStateChangeTimeout(0);
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, OutputLowWaitZeroSLES) {
AudioStreamBuilder builder;
builder.setPerformanceMode(PerformanceMode::LowLatency);
builder.setAudioApi(AudioApi::OpenSLES);
ASSERT_TRUE(openStream(builder));
checkWaitForStateChangeTimeout(0);
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, OutputNoneWaitZeroSLES) {
AudioStreamBuilder builder;
builder.setPerformanceMode(PerformanceMode::None);
builder.setAudioApi(AudioApi::OpenSLES);
ASSERT_TRUE(openStream(builder));
checkWaitForStateChangeTimeout(0);
ASSERT_TRUE(closeStream());
}
// Test actual timeout.
TEST_F(TestStreamWaitState, OutputLowWaitNonZero) {
ASSERT_TRUE(openStream(Direction::Output, PerformanceMode::LowLatency));
checkWaitForStateChangeTimeout();
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, OutputNoneWaitNonZero) {
ASSERT_TRUE(openStream(Direction::Output, PerformanceMode::None));
checkWaitForStateChangeTimeout();
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, OutputLowWaitNonZeroSLES) {
AudioStreamBuilder builder;
builder.setPerformanceMode(PerformanceMode::LowLatency);
builder.setAudioApi(AudioApi::OpenSLES);
ASSERT_TRUE(openStream(builder));
checkWaitForStateChangeTimeout();
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, OutputNoneWaitNonZeroSLES) {
AudioStreamBuilder builder;
builder.setPerformanceMode(PerformanceMode::None);
builder.setAudioApi(AudioApi::OpenSLES);
ASSERT_TRUE(openStream(builder));
checkWaitForStateChangeTimeout();
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, OutputLowStopWhileWaiting) {
ASSERT_TRUE(openStream(Direction::Output, PerformanceMode::LowLatency));
checkStopWhileWaiting();
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, OutputNoneStopWhileWaiting) {
ASSERT_TRUE(openStream(Direction::Output, PerformanceMode::LowLatency));
checkStopWhileWaiting();
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, OutputLowStopWhileWaitingSLES) {
AudioStreamBuilder builder;
builder.setPerformanceMode(PerformanceMode::LowLatency);
builder.setAudioApi(AudioApi::OpenSLES);
ASSERT_TRUE(openStream(builder));
checkStopWhileWaiting();
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, OutputLowCloseWhileWaiting) {
ASSERT_TRUE(openStream(Direction::Output, PerformanceMode::LowLatency));
checkCloseWhileWaiting();
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, OutputNoneCloseWhileWaiting) {
ASSERT_TRUE(openStream(Direction::Output, PerformanceMode::None));
checkCloseWhileWaiting();
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, InputLowCloseWhileWaiting) {
ASSERT_TRUE(openStream(Direction::Input, PerformanceMode::LowLatency));
checkCloseWhileWaiting();
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, InputNoneCloseWhileWaiting) {
ASSERT_TRUE(openStream(Direction::Input, PerformanceMode::None));
checkCloseWhileWaiting();
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, OutputNoneCloseWhileWaitingSLES) {
AudioStreamBuilder builder;
builder.setPerformanceMode(PerformanceMode::None);
builder.setAudioApi(AudioApi::OpenSLES);
ASSERT_TRUE(openStream(builder));
checkCloseWhileWaiting();
ASSERT_TRUE(closeStream());
}
TEST_F(TestStreamWaitState, OutputLowCloseWhileWaitingSLES) {
AudioStreamBuilder builder;
builder.setPerformanceMode(PerformanceMode::LowLatency);
builder.setAudioApi(AudioApi::OpenSLES);
ASSERT_TRUE(openStream(builder));
checkCloseWhileWaiting();
ASSERT_TRUE(closeStream());
}

40
externals/oboe/tests/testUtilities.cpp vendored Normal file
View file

@ -0,0 +1,40 @@
/*
* 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 <gtest/gtest.h>
#include <oboe/Definitions.h>
#include <oboe/Utilities.h>
/**
* Tests needing to be written:
*
* - oboe::convertFormatToSizeInBytes()
*/
using namespace oboe;
class UtilityFunctions : public ::testing::Test {
};
TEST_F(UtilityFunctions, Converts16BitIntegerToSizeOf2Bytes){
int32_t sizeInBytes = oboe::convertFormatToSizeInBytes(AudioFormat::I16);
ASSERT_EQ(sizeInBytes, 2);
}
TEST_F(UtilityFunctions, ConvertsFloatToSizeOf4Bytes){
int32_t sizeInBytes = oboe::convertFormatToSizeInBytes(AudioFormat::Float);
ASSERT_EQ(sizeInBytes, 4);
}

View file

@ -0,0 +1,81 @@
/*
* 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 <gtest/gtest.h>
#include <oboe/Oboe.h>
using namespace oboe;
class MyCallback : public AudioStreamCallback {
public:
DataCallbackResult onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override {
return DataCallbackResult::Continue;
}
};
class XRunBehaviour : public ::testing::Test {
protected:
bool openStream() {
Result r = mBuilder.openStream(mStream);
EXPECT_EQ(r, Result::OK) << "Failed to open stream " << convertToText(r);
return (r == Result::OK);
}
bool closeStream() {
Result r = mStream->close();
EXPECT_EQ(r, Result::OK) << "Failed to close stream. " << convertToText(r);
return (r == Result::OK);
}
AudioStreamBuilder mBuilder;
std::shared_ptr<AudioStream> mStream;
};
// TODO figure out this behaviour - On OpenSLES xRuns are supported within AudioStreamBuffered,
// however, these aren't the same as the actual stream underruns
TEST_F(XRunBehaviour, SupportedWhenStreamIsUsingAAudio){
ASSERT_TRUE(openStream());
if (mStream->getAudioApi() == AudioApi::AAudio){
ASSERT_TRUE(mStream->isXRunCountSupported());
}
ASSERT_TRUE(closeStream());
}
TEST_F(XRunBehaviour, NotSupportedOnOpenSLESWhenStreamIsUsingCallback){
MyCallback callback;
mBuilder.setCallback(&callback);
ASSERT_TRUE(openStream());
if (mStream->getAudioApi() == AudioApi::OpenSLES){
ASSERT_FALSE(mStream->isXRunCountSupported());
}
ASSERT_TRUE(closeStream());
}
TEST_F(XRunBehaviour, SupportedOnOpenSLESWhenStreamIsUsingBlockingIO){
ASSERT_TRUE(openStream());
if (mStream->getAudioApi() == AudioApi::OpenSLES){
ASSERT_TRUE(mStream->isXRunCountSupported());
}
ASSERT_TRUE(closeStream());
}