Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d2eef15d3 | ||
|
|
44d725a358 | ||
|
|
9d05e29492 |
131
.ci_tools/build_samples.sh
Executable file
@@ -0,0 +1,131 @@
|
||||
#!/bin/bash
|
||||
|
||||
# - Configure SDK/NDK locations so we do not depends on local.properties, e.g
|
||||
# - export ANDROID_HOME=$HOME/dev/sdk
|
||||
# - export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle
|
||||
# - executing in repo root directory
|
||||
|
||||
# Configurations:
|
||||
# temp file name to hold build result
|
||||
BUILD_RESULT_FILE=build_result.txt
|
||||
|
||||
# Repo root directory
|
||||
NDK_SAMPLE_REPO=.
|
||||
|
||||
|
||||
declare projects=(
|
||||
audio-echo
|
||||
bitmap-plasma
|
||||
camera
|
||||
endless-tunnel
|
||||
gles3jni
|
||||
hello-gl2
|
||||
hello-jni
|
||||
hello-jniCallback
|
||||
hello-libs
|
||||
hello-neon
|
||||
native-activity
|
||||
native-audio
|
||||
native-codec
|
||||
native-media
|
||||
native-plasma
|
||||
nn-samples
|
||||
prefab/curl-ssl
|
||||
prefab/prefab-publishing
|
||||
san-angeles
|
||||
sensor-graph
|
||||
# webp
|
||||
teapots
|
||||
## ndk-build samples
|
||||
other-builds/ndkbuild/bitmap-plasma
|
||||
other-builds/ndkbuild/gles3jni
|
||||
other-builds/ndkbuild/hello-gl2
|
||||
other-builds/ndkbuild/hello-jni
|
||||
other-builds/ndkbuild/hello-libs
|
||||
other-builds/ndkbuild/hello-neon
|
||||
other-builds/ndkbuild/native-activity
|
||||
other-builds/ndkbuild/native-audio
|
||||
other-builds/ndkbuild/native-codec
|
||||
other-builds/ndkbuild/native-media
|
||||
other-builds/ndkbuild/native-plasma
|
||||
other-builds/ndkbuild/nn-samples
|
||||
other-builds/ndkbuild/san-angeles
|
||||
other-builds/ndkbuild/teapots
|
||||
)
|
||||
|
||||
for d in "${projects[@]}"; do
|
||||
pushd ${NDK_SAMPLE_REPO}/${d} >/dev/null
|
||||
TERM=dumb ./gradlew -q clean assembleDebug
|
||||
popd >/dev/null
|
||||
done
|
||||
|
||||
|
||||
# Check the apks that all get built fine
|
||||
declare apks=(
|
||||
audio-echo/app/build/outputs/apk/debug/app-debug.apk
|
||||
bitmap-plasma/app/build/outputs/apk/debug/app-debug.apk
|
||||
camera/basic/build/outputs/apk/debug/basic-debug.apk
|
||||
camera/texture-view/build/outputs/apk/debug/texture-view-debug.apk
|
||||
endless-tunnel/app/build/outputs/apk/debug/app-debug.apk
|
||||
gles3jni/app/build/outputs/apk/debug/app-debug.apk
|
||||
hello-gl2/app/build/outputs/apk/debug/app-debug.apk
|
||||
hello-jni/app/build/outputs/apk/arm8/debug/app-arm8-debug.apk
|
||||
hello-jniCallback/app/build/outputs/apk/debug/app-debug.apk
|
||||
hello-libs/app/build/outputs/apk/debug/app-debug.apk
|
||||
hello-neon/app/build/outputs/apk/debug/app-debug.apk
|
||||
native-activity/app/build/outputs/apk/debug/app-debug.apk
|
||||
native-audio/app/build/outputs/apk/debug/app-debug.apk
|
||||
native-codec/app/build/outputs/apk/debug/app-debug.apk
|
||||
native-media/app/build/outputs/apk/debug/app-debug.apk
|
||||
native-plasma/app/build/outputs/apk/debug/app-debug.apk
|
||||
nn-samples/basic/build/outputs/apk/debug/basic-debug.apk
|
||||
nn-samples/sequence/build/outputs/apk/debug/sequence-debug.apk
|
||||
prefab/curl-ssl/app/build/outputs/apk/debug/app-debug.apk
|
||||
prefab/prefab-publishing/mylibrary/build/outputs/aar/mylibrary-debug.aar
|
||||
sensor-graph/accelerometer/build/outputs/apk/debug/accelerometer-debug.apk
|
||||
san-angeles/app/build/outputs/apk/debug/app-armeabi-v7a-debug.apk
|
||||
san-angeles/app/build/outputs/apk/debug/app-arm64-v8a-debug.apk
|
||||
san-angeles/app/build/outputs/apk/debug/app-x86-debug.apk
|
||||
teapots/classic-teapot/build/outputs/apk/debug/classic-teapot-debug.apk
|
||||
teapots/more-teapots/build/outputs/apk/debug/more-teapots-debug.apk
|
||||
teapots/choreographer-30fps/build/outputs/apk/debug/choreographer-30fps-debug.apk
|
||||
teapots/image-decoder/build/outputs/apk/debug/image-decoder-debug.apk
|
||||
# webp/view/build/outputs/apk/debug/view-arm7-debug.apk
|
||||
|
||||
## other-builds
|
||||
other-builds/ndkbuild/bitmap-plasma/app/build/outputs/apk/debug/app-debug.apk
|
||||
other-builds/ndkbuild/gles3jni/app/build/outputs/apk/debug/app-debug.apk
|
||||
other-builds/ndkbuild/hello-gl2/app/build/outputs/apk/debug/app-debug.apk
|
||||
other-builds/ndkbuild/hello-jni/app/build/outputs/apk/debug/app-debug.apk
|
||||
other-builds/ndkbuild/hello-libs/app/build/outputs/apk/debug/app-debug.apk
|
||||
other-builds/ndkbuild/hello-neon/app/build/outputs/apk/arm7/debug/app-arm7-debug.apk
|
||||
other-builds/ndkbuild/native-activity/app/build/outputs/apk/debug/app-debug.apk
|
||||
other-builds/ndkbuild/native-audio/app/build/outputs/apk/debug/app-debug.apk
|
||||
other-builds/ndkbuild/native-codec/app/build/outputs/apk/debug/app-debug.apk
|
||||
other-builds/ndkbuild/native-media/app/build/outputs/apk/debug/app-debug.apk
|
||||
other-builds/ndkbuild/native-plasma/app/build/outputs/apk/debug/app-debug.apk
|
||||
other-builds/ndkbuild/nn-samples/basic/build/outputs/apk/debug/basic-debug.apk
|
||||
other-builds/ndkbuild/san-angeles/app/build/outputs/apk/debug/app-armeabi-v7a-debug.apk
|
||||
other-builds/ndkbuild/san-angeles/app/build/outputs/apk/debug/app-arm64-v8a-debug.apk
|
||||
other-builds/ndkbuild/san-angeles/app/build/outputs/apk/debug/app-x86-debug.apk
|
||||
other-builds/ndkbuild/teapots/more-teapots/build/outputs/apk/debug/more-teapots-debug.apk
|
||||
other-builds/ndkbuild/teapots/classic-teapot/build/outputs/apk/debug/classic-teapot-debug.apk
|
||||
)
|
||||
|
||||
rm -fr ${BUILD_RESULT_FILE}
|
||||
for apk in "${apks[@]}"; do
|
||||
if [ ! -f ${NDK_SAMPLE_REPO}/${apk} ]; then
|
||||
export SAMPLE_CI_RESULT=1
|
||||
echo ${apk} does not build >> ${BUILD_RESULT_FILE}
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -f ${BUILD_RESULT_FILE} ]; then
|
||||
echo "******* Failed Builds ********:"
|
||||
cat ${BUILD_RESULT_FILE}
|
||||
else
|
||||
echo "======= BUILD SUCCESS ======="
|
||||
fi
|
||||
|
||||
rm -fr ${BUILD_RESULT_FILE}
|
||||
|
||||
31
.ci_tools/misc_ci.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
set +e
|
||||
|
||||
MISC_STATUS=0
|
||||
# check that all Support section of the README are the same.
|
||||
for f in */README.md; do
|
||||
sed -n '/Support/,/License/p' $f > /tmp/$(dirname $f).readme;
|
||||
done && diff -u --from-file=/tmp/hello-jni.readme /tmp/*.readme
|
||||
MISC_STATUS=$(($MISC_STATUS + $?))
|
||||
|
||||
# check that all targetSdkVersion are 26+
|
||||
# test "$(grep -H targetSdkVersion */app/build.gradle | tee /dev/stderr | cut -d= -f 2 | xargs -n1 echo | sort | uniq | wc -l)" = "2"
|
||||
# check that there is no tabs in AndroidManifest
|
||||
(! grep -n $'\t' */*/src/main/AndroidManifest.xml) | cat -t;
|
||||
MISC_STATUS=$(($MISC_STATUS + ${PIPESTATUS[0]}))
|
||||
|
||||
# check that there is no trailing spaces in AndroidManifest
|
||||
(! grep -E '\s+$' */*/src/main/AndroidManifest.xml) | cat -e;
|
||||
MISC_STATUS=$(($MISC_STATUS + ${PIPESTATUS[0]}))
|
||||
|
||||
## Fix the builder then enable it [TBD]
|
||||
#(cd builder && ./gradlew test)
|
||||
# print build failure summary
|
||||
# pandoc builder/build/reports/tests/index.html -t plain | sed -n '/^Failed tests/,/default-package/p'
|
||||
# print lint results details
|
||||
# for f in */app/build/outputs/lint-results.html; do pandoc $f -t plain; done
|
||||
|
||||
# populate the error to final status
|
||||
if [[ "$MISC_STATUS" -ne 0 ]]; then
|
||||
SAMPLE_CI_RESULT=$(($SAMPLE_CI_RESULT + 1))
|
||||
fi
|
||||
4
.ci_tools/run_samples.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
# TBD: load apks on emulator
|
||||
#
|
||||
100
.ci_tools/setup_env.sh
Executable file
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
|
||||
# assumption:
|
||||
# - pwd must be inside the repo's homed directory (android-ndk)
|
||||
# - upon completion, we are still in the same directory ( no change )
|
||||
|
||||
|
||||
# parse all build.gradle to find the specified tokens' version
|
||||
# usage:
|
||||
# retrive_versions token version_file
|
||||
# where
|
||||
# token: the token to search for inside build.grade
|
||||
# version string is right after the token string
|
||||
# version_file: file to hold the given versions
|
||||
# one version at a line
|
||||
retrieve_versions() {
|
||||
# $1: token; $2 version_file
|
||||
if [[ -z $1 ]] || [[ -z $2 ]]; then
|
||||
echo "input string(s) may be empty: token: $1; version_file: $2"
|
||||
return 1
|
||||
fi
|
||||
|
||||
find . -type f -name 'build.gradle' -exec grep $1 {} + | \
|
||||
sed "s/^.*$1//" | sed 's/[=+]//g' | \
|
||||
sed 's/"//g' | sed "s/'//g" | \
|
||||
sed 's/[[:space:]]//g' | \
|
||||
awk '!seen[$0]++' > $2
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# helper function for src_str > target_str
|
||||
# Usage
|
||||
# comp_ver_string src_str target_str
|
||||
# return:
|
||||
# 0: src_str <= target_str
|
||||
# 1: otherwise
|
||||
comp_ver_string () {
|
||||
# $1: src_str, $2: target_str
|
||||
if [[ $1 == $2 ]]
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
local IFS=.
|
||||
local i ver1=($1) ver2=($2)
|
||||
# fill empty fields in ver1 with zeros
|
||||
for ((i=${#ver1[@]}; i<${#ver2[@]}; i++))
|
||||
do
|
||||
ver1[i]=0
|
||||
done
|
||||
for ((i=0; i<${#ver1[@]}; i++))
|
||||
do
|
||||
if [[ -z ${ver2[i]} ]]
|
||||
then
|
||||
# fill empty fields in ver2 with zeros
|
||||
ver2[i]=0
|
||||
fi
|
||||
if ((10#${ver1[i]} < 10#${ver2[i]}))
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ((10#${ver1[i]} > 10#${ver2[i]}))
|
||||
then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
# prepare to install necessary packages
|
||||
if [ -f ~/.android/repositories.cfg ]; then
|
||||
touch ~/.android/repositories.cfg
|
||||
fi
|
||||
|
||||
TMP_SETUP_FILENAME=versions_.txt
|
||||
|
||||
## Retrieve all necessary Android Platforms and install them all
|
||||
retrieve_versions compileSdkVersion $TMP_SETUP_FILENAME
|
||||
|
||||
# Install platforms
|
||||
while read -r version_; do
|
||||
version_=${version_//android-/}
|
||||
echo y | $ANDROID_HOME/tools/bin/sdkmanager "platforms;android-$version_";
|
||||
done < $TMP_SETUP_FILENAME
|
||||
# echo "Android platforms:"; cat $TMP_SETUP_FILENAME
|
||||
|
||||
# Install side by side ndks
|
||||
retrieve_versions ndkVersion $TMP_SETUP_FILENAME
|
||||
while read -r version_; do
|
||||
echo y | $ANDROID_HOME/tools/bin/sdkmanager "ndk;$version_" --channel=3;
|
||||
done < $TMP_SETUP_FILENAME
|
||||
# echo "NDK versions:"; cat $TMP_SETUP_FILENAME
|
||||
|
||||
# add customized cmake installation
|
||||
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "cmake;3.18.1"
|
||||
|
||||
rm -f $TMP_SETUP_FILENAME
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
BasedOnStyle: Google
|
||||
DerivePointerAlignment: false
|
||||
23
.github/workflows/build.yml
vendored
@@ -1,9 +1,10 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ main develop ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -12,18 +13,18 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: set up JDK 21
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 21
|
||||
|
||||
- uses: pre-commit/action@v3.0.0
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
with:
|
||||
packages: "cmake;4.1.0"
|
||||
java-version: 1.8
|
||||
|
||||
- name: setup env
|
||||
run:
|
||||
source .ci_tools/setup_env.sh
|
||||
- name: build samples
|
||||
run: |
|
||||
./gradlew build
|
||||
export SAMPLE_CI_RESULT=0
|
||||
source .ci_tools/build_samples.sh
|
||||
source .ci_tools/run_samples.sh
|
||||
eval "[[ $SAMPLE_CI_RESULT == 0 ]]"
|
||||
|
||||
|
||||
31
.github/workflows/copy-branch.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# Duplicates default main branch to the old master branch
|
||||
|
||||
name: Duplicates main to old master branch
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the main branch
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "copy-branch"
|
||||
copy-branch:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it,
|
||||
# but specifies master branch (old default).
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: master
|
||||
|
||||
- run: |
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
git merge origin/main
|
||||
git push
|
||||
@@ -1,13 +0,0 @@
|
||||
status: PUBLISHED
|
||||
technologies:
|
||||
- Android
|
||||
- NDK
|
||||
- Platform
|
||||
categories:
|
||||
- NDK
|
||||
languages:
|
||||
- C++
|
||||
solutions:
|
||||
- Mobile
|
||||
github: android/ndk-samples
|
||||
license: apache2
|
||||
@@ -1,6 +0,0 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-xml
|
||||
- id: check-yaml
|
||||
@@ -1,96 +0,0 @@
|
||||
# Architecture
|
||||
|
||||
This document describes the layout of the samples repository.
|
||||
|
||||
The top level directory is a Gradle project. This directory can be opened in
|
||||
Android Studio (and that will be the easiest way to work with the samples).
|
||||
|
||||
## Directory structure
|
||||
|
||||
### Samples
|
||||
|
||||
Most subdirectories are the individual sample apps. These can't be opened in
|
||||
Android Studio individually, as they rely on common code from the top level
|
||||
project. These subdirectories have their own `build.gradle` (the Groovy DSL) or
|
||||
`build.gradle.kts` (the Kotlin DSL) files. In Gradle terms these are "projects".
|
||||
Android Studio calls them "modules". The documentation in this repository will
|
||||
typically call them "modules".
|
||||
|
||||
Each sample has its own README.md that explains what features it demonstrates,
|
||||
as well as any unique requirements.
|
||||
|
||||
To run a sample, use the configuration selector in the top panel of Android
|
||||
Studio to select the sample and then click run. For example, to run the
|
||||
endless-tunnel sample game:
|
||||
|
||||
![Select and run a sample][docs/run-sample.png]
|
||||
|
||||
#### Build types
|
||||
|
||||
Android Gradle modules have multiple "build types", sometimes called "variants".
|
||||
For most of the samples, there are only two build types: debug and release. A
|
||||
few samples, notably the sanitizers sample, have more. To view the build types
|
||||
for the modules in this repository, in the Android Studio application menu,
|
||||
select View -> Tool Windows -> Build Variants. A window will open that lets you
|
||||
select the active variant for each sample.
|
||||
|
||||
### build-logic
|
||||
|
||||
The `build-logic` directory contains Gradle convention plugins used by this
|
||||
repository. This is where Gradle policy decisions that apply to the whole
|
||||
repository are made.
|
||||
|
||||
See the README.md in that directory for more details.
|
||||
|
||||
### docs
|
||||
|
||||
Documentation and supporting artifacts for this repository. Yes, for now it's
|
||||
just images for the READMEs and this doc.
|
||||
|
||||
### gradle/libs.versions.toml
|
||||
|
||||
This is a Gradle [version catalog]. It's the central location that defines the
|
||||
library and plugin dependencies for each sample.
|
||||
|
||||
The Android Gradle Plugin does not support evaluating version catalog fields in
|
||||
its DSL, so versions for SDK and NDK tools (`ndkVersion`, `compileSdkVersion`,
|
||||
`targetSdkVersion`, etc) are all defined by the convention plugins in
|
||||
[build-logic](#build-logic).
|
||||
|
||||
[version catalog]: https://docs.gradle.org/current/userguide/platforms.html
|
||||
|
||||
### Gradle wrapper
|
||||
|
||||
The `gradlew` and `gradlew.bat` scripts are the Gradle wrappers, macOS/Linux-
|
||||
and Windows batch-compatible respectively. They will manage the Gradle
|
||||
installation used by this repository for you, ensuring that the correct versions
|
||||
and environment are used. These should be used instead of running `gradle`
|
||||
directly.
|
||||
|
||||
`gradle/wrapper/gradle-wrapper.properties` defines which version of Gradle will
|
||||
be used. This should rarely be modified manually. Android Studio's Upgrade
|
||||
Assistant will manage that file and upgrade to whatever version of Gradle it
|
||||
prefers for that version of Android Studio and Android Gradle.
|
||||
|
||||
### build.gradle and settings.gradle
|
||||
|
||||
The build.gradle (or build.gradle.kts) file is the top level build file. It
|
||||
doesn't do anything interesting but declares which plugins will be used by the
|
||||
child modules. Each sample's module has its own build.gradle file that defines
|
||||
properties of the app.
|
||||
|
||||
The settings.gradle (or settings.gradle.kts) file configures repositories for
|
||||
fetching dependencies, and declares each app module.
|
||||
|
||||
### Metadata directories
|
||||
|
||||
#### .github
|
||||
|
||||
This directory contains GitHub metadata files, such as GitHub Action workflows
|
||||
and Issue templates.
|
||||
|
||||
#### .google
|
||||
|
||||
The `packaging.yaml` file in this directory is used by Android Studio to enable
|
||||
the File -> New -> Import Sample feature. As this repository is a monolithic
|
||||
sample project, it should not need to be changed.
|
||||
@@ -2,18 +2,18 @@
|
||||
|
||||
## Contributor License Agreements
|
||||
|
||||
We'd love to accept your sample apps and patches! Before we can take them, we
|
||||
We'd love to accept your sample apps and patches! Before we can take them, we
|
||||
have to jump a couple of legal hurdles.
|
||||
|
||||
Please fill out either the individual or corporate Contributor License Agreement
|
||||
(CLA).
|
||||
|
||||
- If you are an individual writing original source code and you're sure you own
|
||||
the intellectual property, then you'll need to sign an
|
||||
[individual CLA](http://code.google.com/legal/individual-cla-v1.0.html).
|
||||
- If you work for a company that wants to allow you to contribute your work,
|
||||
then you'll need to sign a
|
||||
[corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html).
|
||||
* If you are an individual writing original source code and you're sure you
|
||||
own the intellectual property, then you'll need to sign an [individual CLA]
|
||||
(http://code.google.com/legal/individual-cla-v1.0.html).
|
||||
* If you work for a company that wants to allow you to contribute your work,
|
||||
then you'll need to sign a [corporate CLA]
|
||||
(http://code.google.com/legal/corporate-cla-v1.0.html).
|
||||
|
||||
Follow either of the two links above to access the appropriate CLA and
|
||||
instructions for how to sign and return it. Once we receive it, we'll be able to
|
||||
@@ -24,9 +24,9 @@ accept your pull requests.
|
||||
1. Sign a Contributor License Agreement, if you have not yet done so (see
|
||||
details above).
|
||||
1. Create your change to the repo in question.
|
||||
- Fork the desired repo, develop and test your code changes.
|
||||
- Ensure that your code is clear and comprehensible.
|
||||
- Ensure that your code has an appropriate set of unit tests which all pass.
|
||||
* Fork the desired repo, develop and test your code changes.
|
||||
* Ensure that your code is clear and comprehensible.
|
||||
* Ensure that your code has an appropriate set of unit tests which all pass.
|
||||
1. Submit a pull request.
|
||||
1. The repo owner will review your request. If it is approved, the change will
|
||||
be merged. If it needs additional work, the repo owner will respond with
|
||||
|
||||
174
README.md
@@ -1,142 +1,66 @@
|
||||
# Android NDK Samples
|
||||
NDK Samples [](https://github.com/android/ndk-samples/actions)
|
||||
===========
|
||||
|
||||
This repository contains sample apps that use the [Android NDK].
|
||||
This repository contains [Android NDK][0] samples with Android Studio [C++ integration](https://www.youtube.com/watch?v=f7ihSQ44WO0&feature=youtu.be).
|
||||
|
||||
For an explanation of the layout of this repository, see
|
||||
[ARCHITECTURE.md](ARCHITECTURE.md).
|
||||
These samples use the new [CMake Android plugin](https://developer.android.com/studio/projects/add-native-code.html) with C++ support.
|
||||
|
||||
## Build and run
|
||||
|
||||
[](https://github.com/android/ndk-samples/actions)
|
||||
|
||||
1. Clone the repository
|
||||
2. Open the whole project in Android Studio
|
||||
3. Install CMake 4.1.0 via the SDK Manager (must be done manually until
|
||||
https://issuetracker.google.com/443137057 is fixed).
|
||||
4. Select the sample you want to run in the top bar (you may need to sync gradle
|
||||
first)
|
||||
5. Click the play button to run the sample
|
||||
|
||||
You can also build the samples from the command line if you prefer. Use
|
||||
`./gradlew build` to build everything (if you're on Windows, use `.\gradlew.bat`
|
||||
instead of `./gradlew`). For individual tasks, see `./gradlew tasks`. To see the
|
||||
tasks for an individual sample, run the `tasks` task for that directory. For
|
||||
example, `./gradlew :camera:basic:tasks` will show the tasks for the
|
||||
`camera/basic` app.
|
||||
|
||||
## I just want something to copy from as a starting point
|
||||
|
||||
The samples in this repository are generally not a good starting point for a
|
||||
production quality app. They aim to demonstrate individual NDK APIs, but often
|
||||
make sacrifices to be succinct that make them unsuitable for a production app.
|
||||
This is gradually changing, but for now you should not do this.
|
||||
|
||||
[Now in Android](https://github.com/android/nowinandroid/) is an excellent
|
||||
resource for production quality apps in general, but does not touch on NDK-
|
||||
specific issues. https://github.com/DanAlbert/ndk-app-template can help some
|
||||
with that until this repository is able to.
|
||||
|
||||
You're most likely best served by using the New Project wizard in Android Studio
|
||||
to create a new app, then using those resources and the samples here as a
|
||||
reference. Android Studio's "Native C++" template is a good starting point for
|
||||
typical applications that need to use some C++ via JNI. The "Game Activity"
|
||||
template is a good starting point for game-like apps (that is, apps that do not
|
||||
use the Android UI, but instead render their own UI using OpenGL or Vulkan).
|
||||
|
||||
## Best practices shown here
|
||||
|
||||
There are a few best practices shown throughout this repository that should be
|
||||
explained, but there's no central place to discuss those in the code, so we'll
|
||||
discuss those here.
|
||||
|
||||
### `RegisterNatives`
|
||||
|
||||
We prefer using `RegisterNatives()` via `JNI_OnLoad()` over the name-based
|
||||
matching that Studio's New Project Wizard will typically create, e.g. JNI
|
||||
functions that follow the pattern `JNIEXPORT void JNICALL
|
||||
Java_com_etc_ClassName_methodName()`. That approach to matching C/C++ functions
|
||||
to their Java `native` function (or Kotlin `external fun`) makes for a shorter
|
||||
demo when there are only a small number of functions, but it has a number of
|
||||
disadvantages. See the [JNI tips] guide for details.
|
||||
|
||||
[JNI tips]: https://developer.android.com/ndk/guides/jni-tips#native-libraries
|
||||
|
||||
### Version scripts
|
||||
|
||||
All of the app libraries shown here are built using a version script. This is a
|
||||
file that explicitly lists which symbols should be exported from the library,
|
||||
and hides all the others. Version scripts function similarly to
|
||||
`-fvisibility=hidden`, but can go a step further and are capable of hiding
|
||||
symbols in static libraries that are used by your app. Hiding as many symbols as
|
||||
possible results in smaller binaries that load faster, as there are fewer
|
||||
relocations required and LTO can do a better job. They also run faster as
|
||||
same-library function calls do not need to be made through the PLT. There are no
|
||||
good reasons to not use a version script for your NDK code. See the NDK
|
||||
documentation on [controlling symbol visibility] for more information.
|
||||
|
||||
You can find these in each sample as the `lib<name>.map.txt` file (where
|
||||
`<name>` is the name of the library passed to `add_app_library()` in the
|
||||
`CMakeLists.txt` file). The build plumbing that uses the version scripts is in
|
||||
the definition of `add_app_library()` in `cmake/AppLibrary.cmake`.
|
||||
|
||||
[controlling symbol visibility]: https://developer.android.com/ndk/guides/symbol-visibility
|
||||
|
||||
## Additional documentation
|
||||
|
||||
- [Add Native Code to Your Project](https://developer.android.com/studio/projects/add-native-code.html)
|
||||
- [Configure NDK for Android Studio/Gradle Plugin](https://developer.android.com/studio/projects/configure-agp-ndk)
|
||||
- [CMake for NDK](https://developer.android.com/ndk/guides/cmake.html)
|
||||
|
||||
## Support
|
||||
|
||||
If you've found an issue with a sample and you know how to fix it, please
|
||||
[send us a PR!](CONTRIBUTING.md).
|
||||
|
||||
If you need to report a bug, where it needs to be filed depends on the type of
|
||||
issue:
|
||||
|
||||
- Problems with the samples themselves:
|
||||
https://github.com/googlesamples/android-ndk/issues
|
||||
- Problems with the OS APIs: http://b.android.com (usually the Framework
|
||||
component)
|
||||
- Problems with NDK (that is, the compiler):
|
||||
https://github.com/android/ndk/issues
|
||||
|
||||
For questions about using the NDK or the platform APIs, you can ask on:
|
||||
|
||||
- [The NDK mailing list](https://groups.google.com/g/android-ndk) (best if
|
||||
you're not sure where else to ask)
|
||||
- The [Discussions](https://github.com/android/ndk-samples/discussions) tab of
|
||||
this repo (best for questions about the samples themselves)
|
||||
- The NDK's [Discussions](https://github.com/android/ndk/discussions) (best for
|
||||
questions about the NDK compilers and build systems)
|
||||
- [Stack Overflow](https://stackoverflow.com/questions/tagged/android)
|
||||
|
||||
## Additional NDK samples:
|
||||
Samples could also be built with other build systems:
|
||||
- for ndk-build with Android Studio, refer to directory [other-builds/ndkbuild](https://github.com/googlesamples/android-ndk/tree/master/other-builds/ndkbuild)
|
||||
- for gradle-experimental plugin, refer to directory other-builds/experimental. Note that gradle-experimental does not work with unified headers yet: use NDK version up to r15 and Android Studio up to version 2.3. When starting new project, please use CMake or ndk-build plugin.
|
||||
|
||||
Additional Android Studio samples:
|
||||
- [Google Play Game Samples with Android Studio](https://github.com/playgameservices/cpp-android-basic-samples)
|
||||
- [Google Android Vulkan Tutorials](https://github.com/googlesamples/android-vulkan-tutorials)
|
||||
- [Android Vulkan API Basic Samples](https://github.com/googlesamples/vulkan-basic-samples)
|
||||
- [Android High Performance Audio](https://github.com/googlesamples/android-audio-high-performance)
|
||||
- [Android High Performance Audio](https://github.com/googlesamples/android-audio-high-performance)
|
||||
|
||||
## License
|
||||
Documentation
|
||||
- [Add Native Code to Your Project](https://developer.android.com/studio/projects/add-native-code.html)
|
||||
- [Configure NDK for Android Studio/Gradle Plugin](https://github.com/android/ndk-samples/wiki/Configure-NDK-Path)
|
||||
- [CMake for NDK](https://developer.android.com/ndk/guides/cmake.html)
|
||||
|
||||
Copyright 2015 The Android Open Source Project, Inc.
|
||||
Known Issues
|
||||
- For Studio related issues, refer to [Android Studio known issues](http://tools.android.com/knownissues) page
|
||||
- For NDK issues, refer to [ndk issues](https://github.com/android/ndk/issues)
|
||||
|
||||
For samples using `Android.mk` build system with `ndk-build` see the [android-mk](https://github.com/googlesamples/android-ndk/tree/android-mk) branch.
|
||||
|
||||
Build Steps
|
||||
----------
|
||||
- With Android Studio: "Open An Existing Android Studio Project" or "File" > "Open", then navigate to & select project's build.gradle file.
|
||||
- On Command Line: set up ANDROID_HOME and ANDROID_NDK_HOME to your SDK and NDK path, cd to individual sample dir, and do "gradlew assembleDebug"
|
||||
|
||||
Support
|
||||
-------
|
||||
|
||||
For any issues you found in these samples, please
|
||||
- submit patches with pull requests, see [CONTRIBUTING.md](CONTRIBUTING.md) for more details, or
|
||||
- [create bugs](https://github.com/googlesamples/android-ndk/issues/new) here.
|
||||
|
||||
For Android NDK generic questions, please ask on [Stack Overflow](https://stackoverflow.com/questions/tagged/android), Android teams are periodically monitoring questions there.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Copyright 2018 The Android Open Source Project, Inc.
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||
license agreements. See the NOTICE file distributed with this work for
|
||||
additional information regarding copyright ownership. The ASF licenses this file
|
||||
to you 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
|
||||
license agreements. See the NOTICE file distributed with this work for
|
||||
additional information regarding copyright ownership. The ASF licenses this
|
||||
file to you 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
|
||||
|
||||
https://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.
|
||||
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.
|
||||
|
||||
[LICENSE](LICENSE)
|
||||
|
||||
[Android NDK]: https://developer.android.com/ndk
|
||||
[0]: https://developer.android.com/ndk
|
||||
|
||||
15
REFERENCE.md
@@ -1,8 +1,11 @@
|
||||
Android Studio/Gradle DSL References
|
||||
Android Studio/Gradle DSL References
|
||||
|
||||
| Name |Function | Type | Options | Default |
|
||||
|---------------|-----------|:-------------:|----------|---------|
|
||||
| debuggable | Debugging Java code | bool | true / false ||
|
||||
| ndk.debuggable| Debugging JNI code | bool | true / false ||
|
||||
|
||||
Notation:
|
||||
dot(".") notation is same as closure ("{}") notation
|
||||
|
||||
| Name | Function | Type | Options | Default |
|
||||
| -------------- | ------------------- | :--: | ------------ | ------- |
|
||||
| debuggable | Debugging Java code | bool | true / false | |
|
||||
| ndk.debuggable | Debugging JNI code | bool | true / false | |
|
||||
|
||||
Notation: dot(".") notation is same as closure ("{}") notation
|
||||
|
||||
7
audio-echo/.google/packaging.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
status: PUBLISHED
|
||||
technologies: [Android, NDK]
|
||||
categories: [NDK]
|
||||
languages: [C++, Java]
|
||||
solutions: [Mobile]
|
||||
github: googlesamples/android-ndk
|
||||
license: apache2
|
||||
@@ -1,6 +1,93 @@
|
||||
# Sample removed
|
||||
Audio-Echo
|
||||
==========
|
||||
The sample demos how to use OpenSL ES to create a player and recorder in Android Fast Audio Path, and connect them to loopback audio. On most android devices, there is a optimized audio path that is tuned up for low latency purpose. The sample creates player/recorder to work in this highly optimized audio path(sometimes called native audio path, [low latency path](http://stackoverflow.com/questions/14842803/low-latency-audio-playback-on-android?rq=1), or fast audio path). The application is validated against the following configurations:
|
||||
* Android L AndroidOne
|
||||
* Android M Nexus 5, Nexus 9
|
||||
|
||||
This sample has been removed because the API it demonstrated (OpenSLES) is
|
||||
deprecated. New apps should instead use [Oboe], which has its own samples.
|
||||
This sample uses the new Android Studio with CMake support, and shows how to use shared stl lib with android studio version 2.2.0, see CMakeLists.txt for details
|
||||
|
||||
[Oboe]: https://github.com/google/oboe
|
||||
***Note that OpenSL ES is [deprecated from Android 11](https://developer.android.com/preview/features#deprecate-opensl), developers are recommended to use [Oboe](https://github.com/google/oboe) library instead.***
|
||||
|
||||
Pre-requisites
|
||||
--------------
|
||||
- Android Studio 2.2+ with [NDK](https://developer.android.com/ndk/) bundle.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
1. [Download Android Studio](http://developer.android.com/sdk/index.html)
|
||||
1. Launch Android Studio.
|
||||
1. Open the sample directory.
|
||||
1. Open *File/Project Structure...*
|
||||
- Click *Download* or *Select NDK location*.
|
||||
1. Click *Tools/Android/Sync Project with Gradle Files*.
|
||||
1. Click *Run/Run 'app'*.
|
||||
|
||||
Usage
|
||||
-----
|
||||
App will capture audio from android devices and playback on the same device; the playback on speaker will be captured immediately and played back...! So to verify it, it is recommended to "mute" the playback audio with a earspeaker/earphone/earbug so it does not get looped back. Some device like Nexus 9, once you plug in an external headphone/headspeaker, it stops to use onboard microphone AND speaker anymore -- in this case, you need turn on the microphone coming with your headphone. Another point, when switching between external headphone and internal one, the volume is sometimes very low/muted; recommend to increase the playback volume with volume buttons on the phone/pad after plugging external headphone.
|
||||
|
||||
Low Latency Verification
|
||||
------------------------
|
||||
|
||||
1. execute "adb shell dumpsys media.audio_flinger". Find a list of the running processes
|
||||
|
||||
Name Active Client Type Fmt Chn mask Session fCount S F SRate L dB R dB Server Main buf Aux Buf Flags UndFrmCnt
|
||||
F 2 no 704 1 00000001 00000003 562 13248 S 1 48000 -inf -inf 000033C0 0xabab8480 0x0 0x600 0
|
||||
F 6 yes 9345 3 00000001 00000001 576 128 A 1 48000 0 0 0376AA00 0xabab8480 0x0 0x400 256
|
||||
|
||||
1. execute adb shell ps | grep echo
|
||||
|
||||
* find the sample app pid
|
||||
* check with result on step 1.
|
||||
if there is one "F" in the front of your echo pid, **player** is on fast audio path
|
||||
For fast audio capture [it is totally different story], if you do **NOT** see
|
||||
com.example.nativeaudio W/AudioRecord﹕ AUDIO_INPUT_FLAG_FAST denied by client
|
||||
in your logcat output when you are creating audio recorder, you could "assume" you are on the fast path.
|
||||
If your system image was built with muted ALOGW, you will not be able to see the above warning message.
|
||||
|
||||
Tune-ups
|
||||
--------
|
||||
A couple of knobs in the code for lower latency purpose:
|
||||
* audio buffer size
|
||||
* number of audio buffers cached before kicking start player
|
||||
The lower you go with them, the lower latency you get and also the lower budget for audio processing. All audio processing has to be completed in the time period they are captured / played back, plus extra time needed for:
|
||||
* audio driver
|
||||
* audio flinger framework,
|
||||
* bufferqueue callbacks etc
|
||||
Besides those, the irregularity of the buffer queue player/capture callback time is another factor. The callback from openSL may not as regular as you assumed, the more irregularity it is, the more likely have choopy audio. To fight that, more buffering is needed, which defeats the low-latency purpose! The low latency path is highly tuned up so you have better chance to get more regular callbacks. You may experiment with your platform to find the best parameters for lower latency and continuously playback audio experience.
|
||||
The app capture and playback on the same device [most of times the same chip], capture and playback clocks are assumed synchronized naturally [so we are not dealing with it]
|
||||
|
||||
Credits
|
||||
-------
|
||||
* The sample is greatly inspired by native-audio sample
|
||||
* Don Turner @ Google for the helping of low latency path
|
||||
* Ian Ni-Lewis @ Google for producer/consumer queue and many others
|
||||
|
||||
Support
|
||||
-------
|
||||
If you've found an error in these samples, please [file an issue](https://github.com/googlesamples/android-ndk/issues/new).
|
||||
|
||||
Patches are encouraged, and may be submitted by [forking this project](https://github.com/googlesamples/android-ndk/fork) and
|
||||
submitting a pull request through GitHub. Please see [CONTRIBUTING.md](../CONTRIBUTING.md) for more details.
|
||||
|
||||
- [Stack Overflow](http://stackoverflow.com/questions/tagged/android-ndk)
|
||||
- [Android Tools Feedbacks](http://tools.android.com/feedback)
|
||||
|
||||
License
|
||||
-------
|
||||
Copyright 2015 Google, Inc.
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||
license agreements. See the NOTICE file distributed with this work for
|
||||
additional information regarding copyright ownership. The ASF licenses this
|
||||
file to you 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.
|
||||
|
||||
42
audio-echo/app/build.gradle
Normal file
@@ -0,0 +1,42 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
ndkVersion '21.2.6472646'
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.google.sample.echo'
|
||||
/*
|
||||
To run on earlier version of Android than android-21, do:
|
||||
*) set this minSDKVersion and cmake's ANDROID_PLATFORM to your version
|
||||
*) set ANDROID_STL to c++_static for some very earlier version android.
|
||||
*/
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName '1.0'
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments '-DANDROID_STL=c++_static'
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'),
|
||||
'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version '3.18.1'
|
||||
path 'src/main/cpp/CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
}
|
||||
17
audio-echo/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /Users/gfan/dev/android-sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# 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 *;
|
||||
#}
|
||||
24
audio-echo/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.sample.echo" >
|
||||
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission>
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"></uses-permission>
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
android:supportsRtl="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
23
audio-echo/app/src/main/cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
project(echo LANGUAGES C CXX)
|
||||
|
||||
add_library(echo
|
||||
SHARED
|
||||
audio_main.cpp
|
||||
audio_player.cpp
|
||||
audio_recorder.cpp
|
||||
audio_effect.cpp
|
||||
audio_common.cpp
|
||||
debug_utils.cpp)
|
||||
|
||||
#include libraries needed for echo lib
|
||||
target_link_libraries(echo
|
||||
PRIVATE
|
||||
OpenSLES
|
||||
android
|
||||
log
|
||||
atomic)
|
||||
|
||||
target_compile_options(echo
|
||||
PRIVATE
|
||||
-Wall -Werror)
|
||||
47
audio-echo/app/src/main/cpp/android_debug.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
#ifndef NATIVE_AUDIO_ANDROID_DEBUG_H_H
|
||||
#define NATIVE_AUDIO_ANDROID_DEBUG_H_H
|
||||
#include <android/log.h>
|
||||
|
||||
#if 1
|
||||
|
||||
#define MODULE_NAME "AUDIO-ECHO"
|
||||
#define LOGV(...) \
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, MODULE_NAME, __VA_ARGS__)
|
||||
#define LOGD(...) \
|
||||
__android_log_print(ANDROID_LOG_DEBUG, MODULE_NAME, __VA_ARGS__)
|
||||
#define LOGI(...) \
|
||||
__android_log_print(ANDROID_LOG_INFO, MODULE_NAME, __VA_ARGS__)
|
||||
#define LOGW(...) \
|
||||
__android_log_print(ANDROID_LOG_WARN, MODULE_NAME, __VA_ARGS__)
|
||||
#define LOGE(...) \
|
||||
__android_log_print(ANDROID_LOG_ERROR, MODULE_NAME, __VA_ARGS__)
|
||||
#define LOGF(...) \
|
||||
__android_log_print(ANDROID_LOG_FATAL, MODULE_NAME, __VA_ARGS__)
|
||||
|
||||
#else
|
||||
|
||||
#define LOGV(...)
|
||||
#define LOGD(...)
|
||||
#define LOGI(...)
|
||||
#define LOGW(...)
|
||||
#define LOGE(...)
|
||||
#define LOGF(...)
|
||||
#endif
|
||||
|
||||
#endif // NATIVE_AUDIO_ANDROID_DEBUG_H_H
|
||||
66
audio-echo/app/src/main/cpp/audio_common.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2015 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 "audio_common.h"
|
||||
|
||||
void ConvertToSLSampleFormat(SLAndroidDataFormat_PCM_EX* pFormat,
|
||||
SampleFormat* pSampleInfo_) {
|
||||
assert(pFormat);
|
||||
memset(pFormat, 0, sizeof(*pFormat));
|
||||
|
||||
pFormat->formatType = SL_DATAFORMAT_PCM;
|
||||
// Only support 2 channels
|
||||
// For channelMask, refer to wilhelm/src/android/channels.c for details
|
||||
if (pSampleInfo_->channels_ <= 1) {
|
||||
pFormat->numChannels = 1;
|
||||
pFormat->channelMask = SL_SPEAKER_FRONT_LEFT;
|
||||
} else {
|
||||
pFormat->numChannels = 2;
|
||||
pFormat->channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
}
|
||||
pFormat->sampleRate = pSampleInfo_->sampleRate_;
|
||||
|
||||
pFormat->endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
pFormat->bitsPerSample = pSampleInfo_->pcmFormat_;
|
||||
pFormat->containerSize = pSampleInfo_->pcmFormat_;
|
||||
|
||||
/*
|
||||
* fixup for android extended representations...
|
||||
*/
|
||||
pFormat->representation = pSampleInfo_->representation_;
|
||||
switch (pFormat->representation) {
|
||||
case SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT:
|
||||
pFormat->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8;
|
||||
pFormat->containerSize = SL_PCMSAMPLEFORMAT_FIXED_8;
|
||||
pFormat->formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
break;
|
||||
case SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT:
|
||||
pFormat->bitsPerSample =
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16; // supports 16, 24, and 32
|
||||
pFormat->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
|
||||
pFormat->formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
break;
|
||||
case SL_ANDROID_PCM_REPRESENTATION_FLOAT:
|
||||
pFormat->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
|
||||
pFormat->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
|
||||
pFormat->formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
break;
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
79
audio-echo/app/src/main/cpp/audio_common.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef NATIVE_AUDIO_AUDIO_COMMON_H
|
||||
#define NATIVE_AUDIO_AUDIO_COMMON_H
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
|
||||
#include "android_debug.h"
|
||||
#include "debug_utils.h"
|
||||
#include "buf_manager.h"
|
||||
|
||||
/*
|
||||
* Audio Sample Controls...
|
||||
*/
|
||||
#define AUDIO_SAMPLE_CHANNELS 1
|
||||
|
||||
/*
|
||||
* Sample Buffer Controls...
|
||||
*/
|
||||
#define RECORD_DEVICE_KICKSTART_BUF_COUNT 2
|
||||
#define PLAY_KICKSTART_BUFFER_COUNT 3
|
||||
#define DEVICE_SHADOW_BUFFER_QUEUE_LEN 4
|
||||
#define BUF_COUNT 16
|
||||
|
||||
struct SampleFormat {
|
||||
uint32_t sampleRate_;
|
||||
uint32_t framesPerBuf_;
|
||||
uint16_t channels_;
|
||||
uint16_t pcmFormat_; // 8 bit, 16 bit, 24 bit ...
|
||||
uint32_t representation_; // android extensions
|
||||
};
|
||||
extern void ConvertToSLSampleFormat(SLAndroidDataFormat_PCM_EX* pFormat,
|
||||
SampleFormat* format);
|
||||
|
||||
/*
|
||||
* GetSystemTicks(void): return the time in micro sec
|
||||
*/
|
||||
__inline__ uint64_t GetSystemTicks(void) {
|
||||
struct timeval Time;
|
||||
gettimeofday(&Time, NULL);
|
||||
|
||||
return (static_cast<uint64_t>(1000000) * Time.tv_sec + Time.tv_usec);
|
||||
}
|
||||
|
||||
#define SLASSERT(x) \
|
||||
do { \
|
||||
assert(SL_RESULT_SUCCESS == (x)); \
|
||||
(void)(x); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Interface for player and recorder to communicate with engine
|
||||
*/
|
||||
#define ENGINE_SERVICE_MSG_KICKSTART_PLAYER 1
|
||||
#define ENGINE_SERVICE_MSG_RETRIEVE_DUMP_BUFS 2
|
||||
#define ENGINE_SERVICE_MSG_RECORDED_AUDIO_AVAILABLE 3
|
||||
typedef bool (*ENGINE_CALLBACK)(void* pCTX, uint32_t msg, void* pData);
|
||||
|
||||
/*
|
||||
* flag to enable file dumping
|
||||
*/
|
||||
// #define ENABLE_LOG 1
|
||||
|
||||
#endif // NATIVE_AUDIO_AUDIO_COMMON_H
|
||||
170
audio-echo/app/src/main/cpp/audio_effect.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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 "audio_effect.h"
|
||||
#include "audio_common.h"
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
|
||||
/*
|
||||
* Mixing Audio in integer domain to avoid FP calculation
|
||||
* (FG * ( MixFactor * 16 ) + BG * ( (1.0f-MixFactor) * 16 )) / 16
|
||||
*/
|
||||
static const int32_t kFloatToIntMapFactor = 128;
|
||||
static const uint32_t kMsPerSec = 1000;
|
||||
/**
|
||||
* Constructor for AudioDelay
|
||||
* @param sampleRate
|
||||
* @param channelCount
|
||||
* @param format
|
||||
* @param delayTimeInMs
|
||||
*/
|
||||
AudioDelay::AudioDelay(int32_t sampleRate, int32_t channelCount,
|
||||
SLuint32 format, size_t delayTimeInMs,
|
||||
float decayWeight)
|
||||
: AudioFormat(sampleRate, channelCount, format),
|
||||
delayTime_(delayTimeInMs),
|
||||
decayWeight_(decayWeight) {
|
||||
feedbackFactor_ = static_cast<int32_t>(decayWeight_ * kFloatToIntMapFactor);
|
||||
liveAudioFactor_ = kFloatToIntMapFactor - feedbackFactor_;
|
||||
allocateBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
AudioDelay::~AudioDelay() {
|
||||
if (buffer_) delete static_cast<uint8_t*>(buffer_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure for delay time ( in miliseconds ), dynamically adjustable
|
||||
* @param delayTimeInMS in miliseconds
|
||||
* @return true if delay time is set successfully
|
||||
*/
|
||||
bool AudioDelay::setDelayTime(size_t delayTimeInMS) {
|
||||
if (delayTimeInMS == delayTime_) return true;
|
||||
|
||||
std::lock_guard<std::mutex> lock(lock_);
|
||||
|
||||
if (buffer_) {
|
||||
delete static_cast<uint8_t*>(buffer_);
|
||||
buffer_ = nullptr;
|
||||
}
|
||||
|
||||
delayTime_ = delayTimeInMS;
|
||||
allocateBuffer();
|
||||
return buffer_ != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper function to allocate buffer for the delay
|
||||
* - calculate the buffer size for the delay time
|
||||
* - allocate and zero out buffer (0 means silent audio)
|
||||
* - configure bufSize_ to be size of audioFrames
|
||||
*/
|
||||
void AudioDelay::allocateBuffer(void) {
|
||||
float floatDelayTime = (float)delayTime_ / kMsPerSec;
|
||||
float fNumFrames = floatDelayTime * (float)sampleRate_ / kMsPerSec;
|
||||
size_t sampleCount = static_cast<uint32_t>(fNumFrames + 0.5f) * channelCount_;
|
||||
|
||||
uint32_t bytePerSample = format_ / 8;
|
||||
assert(bytePerSample <= 4 && bytePerSample);
|
||||
|
||||
uint32_t bytePerFrame = channelCount_ * bytePerSample;
|
||||
|
||||
// get bufCapacity in bytes
|
||||
bufCapacity_ = sampleCount * bytePerSample;
|
||||
bufCapacity_ =
|
||||
((bufCapacity_ + bytePerFrame - 1) / bytePerFrame) * bytePerFrame;
|
||||
|
||||
buffer_ = new uint8_t[bufCapacity_];
|
||||
assert(buffer_);
|
||||
|
||||
memset(buffer_, 0, bufCapacity_);
|
||||
curPos_ = 0;
|
||||
|
||||
// bufSize_ is in Frames ( not samples, not bytes )
|
||||
bufSize_ = bufCapacity_ / bytePerFrame;
|
||||
}
|
||||
|
||||
size_t AudioDelay::getDelayTime(void) const { return delayTime_; }
|
||||
|
||||
/**
|
||||
* setDecayWeight(): set the decay factor
|
||||
* ratio: value of 0.0 -- 1.0f;
|
||||
*
|
||||
* the calculation is in integer ( not in float )
|
||||
* for performance purpose
|
||||
*/
|
||||
void AudioDelay::setDecayWeight(float weight) {
|
||||
if (weight > 0.0f && weight < 1.0f) {
|
||||
float feedback = (weight * kFloatToIntMapFactor + 0.5f);
|
||||
feedbackFactor_ = static_cast<int32_t>(feedback);
|
||||
liveAudioFactor_ = kFloatToIntMapFactor - feedbackFactor_;
|
||||
}
|
||||
}
|
||||
|
||||
float AudioDelay::getDecayWeight(void) const { return decayWeight_; }
|
||||
|
||||
/**
|
||||
* process() filter live audio with "echo" effect:
|
||||
* delay time is run-time adjustable
|
||||
* decay time could also be adjustable, but not used
|
||||
* in this sample, hardcoded to .5
|
||||
*
|
||||
* @param liveAudio is recorded audio stream
|
||||
* @param channelCount for liveAudio, must be 2 for stereo
|
||||
* @param numFrames is length of liveAudio in Frames ( not in byte )
|
||||
*/
|
||||
void AudioDelay::process(int16_t* liveAudio, int32_t numFrames) {
|
||||
if (feedbackFactor_ == 0 || bufSize_ < numFrames) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!lock_.try_lock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (numFrames + curPos_ > bufSize_) {
|
||||
curPos_ = 0;
|
||||
}
|
||||
|
||||
// process every sample
|
||||
int32_t sampleCount = channelCount_ * numFrames;
|
||||
int16_t* samples = &static_cast<int16_t*>(buffer_)[curPos_ * channelCount_];
|
||||
for (size_t idx = 0; idx < sampleCount; idx++) {
|
||||
#if 1
|
||||
int32_t curSample =
|
||||
(samples[idx] * feedbackFactor_ + liveAudio[idx] * liveAudioFactor_) /
|
||||
kFloatToIntMapFactor;
|
||||
if (curSample > SHRT_MAX)
|
||||
curSample = SHRT_MAX;
|
||||
else if (curSample < SHRT_MIN)
|
||||
curSample = SHRT_MIN;
|
||||
|
||||
liveAudio[idx] = samples[idx];
|
||||
samples[idx] = static_cast<int16_t>(curSample);
|
||||
#else
|
||||
// Pure delay
|
||||
int16_t tmp = liveAudio[idx];
|
||||
liveAudio[idx] = samples[idx];
|
||||
samples[idx] = tmp;
|
||||
#endif
|
||||
}
|
||||
|
||||
curPos_ += numFrames;
|
||||
lock_.unlock();
|
||||
}
|
||||
66
audio-echo/app/src/main/cpp/audio_effect.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef EFFECT_PROCESSOR_H
|
||||
#define EFFECT_PROCESSOR_H
|
||||
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
class AudioFormat {
|
||||
protected:
|
||||
int32_t sampleRate_ = SL_SAMPLINGRATE_48;
|
||||
int32_t channelCount_ = 2;
|
||||
SLuint32 format_ = SL_PCMSAMPLEFORMAT_FIXED_16;
|
||||
|
||||
AudioFormat(int32_t sampleRate, int32_t channelCount, SLuint32 format)
|
||||
: sampleRate_(sampleRate), channelCount_(channelCount), format_(format){};
|
||||
|
||||
virtual ~AudioFormat() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* An audio delay effect:
|
||||
* - decay is for feedback(echo)weight
|
||||
* - delay time is adjustable
|
||||
*/
|
||||
class AudioDelay : public AudioFormat {
|
||||
public:
|
||||
~AudioDelay();
|
||||
|
||||
explicit AudioDelay(int32_t sampleRate, int32_t channelCount, SLuint32 format,
|
||||
size_t delayTimeInMs, float Weight);
|
||||
bool setDelayTime(size_t delayTimeInMiliSec);
|
||||
size_t getDelayTime(void) const;
|
||||
void setDecayWeight(float weight);
|
||||
float getDecayWeight(void) const;
|
||||
void process(int16_t *liveAudio, int32_t numFrames);
|
||||
|
||||
private:
|
||||
size_t delayTime_ = 0;
|
||||
float decayWeight_ = 0.5;
|
||||
void *buffer_ = nullptr;
|
||||
size_t bufCapacity_ = 0;
|
||||
size_t bufSize_ = 0;
|
||||
size_t curPos_ = 0;
|
||||
std::mutex lock_;
|
||||
int32_t feedbackFactor_;
|
||||
int32_t liveAudioFactor_;
|
||||
void allocateBuffer(void);
|
||||
};
|
||||
#endif // EFFECT_PROCESSOR_H
|
||||
259
audio-echo/app/src/main/cpp/audio_main.cpp
Normal file
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "jni_interface.h"
|
||||
#include "audio_recorder.h"
|
||||
#include "audio_player.h"
|
||||
#include "audio_effect.h"
|
||||
#include "audio_common.h"
|
||||
#include <jni.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#include <sys/types.h>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
struct EchoAudioEngine {
|
||||
SLmilliHertz fastPathSampleRate_;
|
||||
uint32_t fastPathFramesPerBuf_;
|
||||
uint16_t sampleChannels_;
|
||||
uint16_t bitsPerSample_;
|
||||
|
||||
SLObjectItf slEngineObj_;
|
||||
SLEngineItf slEngineItf_;
|
||||
|
||||
AudioRecorder *recorder_;
|
||||
AudioPlayer *player_;
|
||||
AudioQueue *freeBufQueue_; // Owner of the queue
|
||||
AudioQueue *recBufQueue_; // Owner of the queue
|
||||
|
||||
sample_buf *bufs_;
|
||||
uint32_t bufCount_;
|
||||
uint32_t frameCount_;
|
||||
int64_t echoDelay_;
|
||||
float echoDecay_;
|
||||
AudioDelay *delayEffect_;
|
||||
};
|
||||
static EchoAudioEngine engine;
|
||||
|
||||
bool EngineService(void *ctx, uint32_t msg, void *data);
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_google_sample_echo_MainActivity_createSLEngine(
|
||||
JNIEnv *env, jclass type, jint sampleRate, jint framesPerBuf,
|
||||
jlong delayInMs, jfloat decay) {
|
||||
SLresult result;
|
||||
memset(&engine, 0, sizeof(engine));
|
||||
|
||||
engine.fastPathSampleRate_ = static_cast<SLmilliHertz>(sampleRate) * 1000;
|
||||
engine.fastPathFramesPerBuf_ = static_cast<uint32_t>(framesPerBuf);
|
||||
engine.sampleChannels_ = AUDIO_SAMPLE_CHANNELS;
|
||||
engine.bitsPerSample_ = SL_PCMSAMPLEFORMAT_FIXED_16;
|
||||
|
||||
result = slCreateEngine(&engine.slEngineObj_, 0, NULL, 0, NULL, NULL);
|
||||
SLASSERT(result);
|
||||
|
||||
result =
|
||||
(*engine.slEngineObj_)->Realize(engine.slEngineObj_, SL_BOOLEAN_FALSE);
|
||||
SLASSERT(result);
|
||||
|
||||
result = (*engine.slEngineObj_)
|
||||
->GetInterface(engine.slEngineObj_, SL_IID_ENGINE,
|
||||
&engine.slEngineItf_);
|
||||
SLASSERT(result);
|
||||
|
||||
// compute the RECOMMENDED fast audio buffer size:
|
||||
// the lower latency required
|
||||
// *) the smaller the buffer should be (adjust it here) AND
|
||||
// *) the less buffering should be before starting player AFTER
|
||||
// receiving the recorder buffer
|
||||
// Adjust the bufSize here to fit your bill [before it busts]
|
||||
uint32_t bufSize = engine.fastPathFramesPerBuf_ * engine.sampleChannels_ *
|
||||
engine.bitsPerSample_;
|
||||
bufSize = (bufSize + 7) >> 3; // bits --> byte
|
||||
engine.bufCount_ = BUF_COUNT;
|
||||
engine.bufs_ = allocateSampleBufs(engine.bufCount_, bufSize);
|
||||
assert(engine.bufs_);
|
||||
|
||||
engine.freeBufQueue_ = new AudioQueue(engine.bufCount_);
|
||||
engine.recBufQueue_ = new AudioQueue(engine.bufCount_);
|
||||
assert(engine.freeBufQueue_ && engine.recBufQueue_);
|
||||
for (uint32_t i = 0; i < engine.bufCount_; i++) {
|
||||
engine.freeBufQueue_->push(&engine.bufs_[i]);
|
||||
}
|
||||
|
||||
engine.echoDelay_ = delayInMs;
|
||||
engine.echoDecay_ = decay;
|
||||
engine.delayEffect_ = new AudioDelay(
|
||||
engine.fastPathSampleRate_, engine.sampleChannels_, engine.bitsPerSample_,
|
||||
engine.echoDelay_, engine.echoDecay_);
|
||||
assert(engine.delayEffect_);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_configureEcho(JNIEnv *env, jclass type,
|
||||
jint delayInMs,
|
||||
jfloat decay) {
|
||||
engine.echoDelay_ = delayInMs;
|
||||
engine.echoDecay_ = decay;
|
||||
|
||||
engine.delayEffect_->setDelayTime(delayInMs);
|
||||
engine.delayEffect_->setDecayWeight(decay);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_createSLBufferQueueAudioPlayer(
|
||||
JNIEnv *env, jclass type) {
|
||||
SampleFormat sampleFormat;
|
||||
memset(&sampleFormat, 0, sizeof(sampleFormat));
|
||||
sampleFormat.pcmFormat_ = (uint16_t)engine.bitsPerSample_;
|
||||
sampleFormat.framesPerBuf_ = engine.fastPathFramesPerBuf_;
|
||||
|
||||
// SampleFormat.representation_ = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
|
||||
sampleFormat.channels_ = (uint16_t)engine.sampleChannels_;
|
||||
sampleFormat.sampleRate_ = engine.fastPathSampleRate_;
|
||||
|
||||
engine.player_ = new AudioPlayer(&sampleFormat, engine.slEngineItf_);
|
||||
assert(engine.player_);
|
||||
if (engine.player_ == nullptr) return JNI_FALSE;
|
||||
|
||||
engine.player_->SetBufQueue(engine.recBufQueue_, engine.freeBufQueue_);
|
||||
engine.player_->RegisterCallback(EngineService, (void *)&engine);
|
||||
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_deleteSLBufferQueueAudioPlayer(
|
||||
JNIEnv *env, jclass type) {
|
||||
if (engine.player_) {
|
||||
delete engine.player_;
|
||||
engine.player_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_createAudioRecorder(JNIEnv *env,
|
||||
jclass type) {
|
||||
SampleFormat sampleFormat;
|
||||
memset(&sampleFormat, 0, sizeof(sampleFormat));
|
||||
sampleFormat.pcmFormat_ = static_cast<uint16_t>(engine.bitsPerSample_);
|
||||
|
||||
// SampleFormat.representation_ = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
|
||||
sampleFormat.channels_ = engine.sampleChannels_;
|
||||
sampleFormat.sampleRate_ = engine.fastPathSampleRate_;
|
||||
sampleFormat.framesPerBuf_ = engine.fastPathFramesPerBuf_;
|
||||
engine.recorder_ = new AudioRecorder(&sampleFormat, engine.slEngineItf_);
|
||||
if (!engine.recorder_) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
engine.recorder_->SetBufQueues(engine.freeBufQueue_, engine.recBufQueue_);
|
||||
engine.recorder_->RegisterCallback(EngineService, (void *)&engine);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_deleteAudioRecorder(JNIEnv *env,
|
||||
jclass type) {
|
||||
if (engine.recorder_) delete engine.recorder_;
|
||||
|
||||
engine.recorder_ = nullptr;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_startPlay(JNIEnv *env, jclass type) {
|
||||
engine.frameCount_ = 0;
|
||||
/*
|
||||
* start player: make it into waitForData state
|
||||
*/
|
||||
if (SL_BOOLEAN_FALSE == engine.player_->Start()) {
|
||||
LOGE("====%s failed", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
engine.recorder_->Start();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_stopPlay(JNIEnv *env, jclass type) {
|
||||
engine.recorder_->Stop();
|
||||
engine.player_->Stop();
|
||||
|
||||
delete engine.recorder_;
|
||||
delete engine.player_;
|
||||
engine.recorder_ = NULL;
|
||||
engine.player_ = NULL;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_google_sample_echo_MainActivity_deleteSLEngine(
|
||||
JNIEnv *env, jclass type) {
|
||||
delete engine.recBufQueue_;
|
||||
delete engine.freeBufQueue_;
|
||||
releaseSampleBufs(engine.bufs_, engine.bufCount_);
|
||||
if (engine.slEngineObj_ != NULL) {
|
||||
(*engine.slEngineObj_)->Destroy(engine.slEngineObj_);
|
||||
engine.slEngineObj_ = NULL;
|
||||
engine.slEngineItf_ = NULL;
|
||||
}
|
||||
|
||||
if (engine.delayEffect_) {
|
||||
delete engine.delayEffect_;
|
||||
engine.delayEffect_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t dbgEngineGetBufCount(void) {
|
||||
uint32_t count = engine.player_->dbgGetDevBufCount();
|
||||
count += engine.recorder_->dbgGetDevBufCount();
|
||||
count += engine.freeBufQueue_->size();
|
||||
count += engine.recBufQueue_->size();
|
||||
|
||||
LOGE(
|
||||
"Buf Disrtibutions: PlayerDev=%d, RecDev=%d, FreeQ=%d, "
|
||||
"RecQ=%d",
|
||||
engine.player_->dbgGetDevBufCount(),
|
||||
engine.recorder_->dbgGetDevBufCount(), engine.freeBufQueue_->size(),
|
||||
engine.recBufQueue_->size());
|
||||
if (count != engine.bufCount_) {
|
||||
LOGE("====Lost Bufs among the queue(supposed = %d, found = %d)", BUF_COUNT,
|
||||
count);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* simple message passing for player/recorder to communicate with engine
|
||||
*/
|
||||
bool EngineService(void *ctx, uint32_t msg, void *data) {
|
||||
assert(ctx == &engine);
|
||||
switch (msg) {
|
||||
case ENGINE_SERVICE_MSG_RETRIEVE_DUMP_BUFS: {
|
||||
*(static_cast<uint32_t *>(data)) = dbgEngineGetBufCount();
|
||||
break;
|
||||
}
|
||||
case ENGINE_SERVICE_MSG_RECORDED_AUDIO_AVAILABLE: {
|
||||
// adding audio delay effect
|
||||
sample_buf *buf = static_cast<sample_buf *>(data);
|
||||
assert(engine.fastPathFramesPerBuf_ ==
|
||||
buf->size_ / engine.sampleChannels_ / (engine.bitsPerSample_ / 8));
|
||||
engine.delayEffect_->process(reinterpret_cast<int16_t *>(buf->buf_),
|
||||
engine.fastPathFramesPerBuf_);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
260
audio-echo/app/src/main/cpp/audio_player.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright 2015 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 <cstdlib>
|
||||
#include "audio_player.h"
|
||||
|
||||
/*
|
||||
* Called by OpenSL SimpleBufferQueue for every audio buffer played
|
||||
* directly pass thru to our handler.
|
||||
* The regularity of this callback from openSL/Android System affects
|
||||
* playback continuity. If it does not callback in the regular time
|
||||
* slot, you are under big pressure for audio processing[here we do
|
||||
* not do any filtering/mixing]. Callback from fast audio path are
|
||||
* much more regular than other audio paths by my observation. If it
|
||||
* very regular, you could buffer much less audio samples between
|
||||
* recorder and player, hence lower latency.
|
||||
*/
|
||||
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *ctx) {
|
||||
(static_cast<AudioPlayer *>(ctx))->ProcessSLCallback(bq);
|
||||
}
|
||||
void AudioPlayer::ProcessSLCallback(SLAndroidSimpleBufferQueueItf bq) {
|
||||
#ifdef ENABLE_LOG
|
||||
logFile_->logTime();
|
||||
#endif
|
||||
std::lock_guard<std::mutex> lock(stopMutex_);
|
||||
|
||||
// retrieve the finished device buf and put onto the free queue
|
||||
// so recorder could re-use it
|
||||
sample_buf *buf;
|
||||
if (!devShadowQueue_->front(&buf)) {
|
||||
/*
|
||||
* This should not happen: we got a callback,
|
||||
* but we have no buffer in deviceShadowedQueue
|
||||
* we lost buffers this way...(ERROR)
|
||||
*/
|
||||
if (callback_) {
|
||||
uint32_t count;
|
||||
callback_(ctx_, ENGINE_SERVICE_MSG_RETRIEVE_DUMP_BUFS, &count);
|
||||
}
|
||||
return;
|
||||
}
|
||||
devShadowQueue_->pop();
|
||||
|
||||
if (buf != &silentBuf_) {
|
||||
buf->size_ = 0;
|
||||
freeQueue_->push(buf);
|
||||
|
||||
if (!playQueue_->front(&buf)) {
|
||||
#ifdef ENABLE_LOG
|
||||
logFile_->log("%s", "====Warning: running out of the Audio buffers");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
devShadowQueue_->push(buf);
|
||||
(*bq)->Enqueue(bq, buf->buf_, buf->size_);
|
||||
playQueue_->pop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (playQueue_->size() < PLAY_KICKSTART_BUFFER_COUNT) {
|
||||
(*bq)->Enqueue(bq, buf->buf_, buf->size_);
|
||||
devShadowQueue_->push(&silentBuf_);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(PLAY_KICKSTART_BUFFER_COUNT <=
|
||||
(DEVICE_SHADOW_BUFFER_QUEUE_LEN - devShadowQueue_->size()));
|
||||
for (int32_t idx = 0; idx < PLAY_KICKSTART_BUFFER_COUNT; idx++) {
|
||||
playQueue_->front(&buf);
|
||||
playQueue_->pop();
|
||||
devShadowQueue_->push(buf);
|
||||
(*bq)->Enqueue(bq, buf->buf_, buf->size_);
|
||||
}
|
||||
}
|
||||
|
||||
AudioPlayer::AudioPlayer(SampleFormat *sampleFormat, SLEngineItf slEngine)
|
||||
: freeQueue_(nullptr),
|
||||
playQueue_(nullptr),
|
||||
devShadowQueue_(nullptr),
|
||||
callback_(nullptr) {
|
||||
SLresult result;
|
||||
assert(sampleFormat);
|
||||
sampleInfo_ = *sampleFormat;
|
||||
|
||||
result = (*slEngine)
|
||||
->CreateOutputMix(slEngine, &outputMixObjectItf_, 0, NULL, NULL);
|
||||
SLASSERT(result);
|
||||
|
||||
// realize the output mix
|
||||
result =
|
||||
(*outputMixObjectItf_)->Realize(outputMixObjectItf_, SL_BOOLEAN_FALSE);
|
||||
SLASSERT(result);
|
||||
|
||||
// configure audio source
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
|
||||
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, DEVICE_SHADOW_BUFFER_QUEUE_LEN};
|
||||
|
||||
SLAndroidDataFormat_PCM_EX format_pcm;
|
||||
ConvertToSLSampleFormat(&format_pcm, &sampleInfo_);
|
||||
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
|
||||
|
||||
// configure audio sink
|
||||
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX,
|
||||
outputMixObjectItf_};
|
||||
SLDataSink audioSnk = {&loc_outmix, NULL};
|
||||
/*
|
||||
* create fast path audio player: SL_IID_BUFFERQUEUE and SL_IID_VOLUME
|
||||
* and other non-signal processing interfaces are ok.
|
||||
*/
|
||||
SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
|
||||
SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
||||
result = (*slEngine)->CreateAudioPlayer(
|
||||
slEngine, &playerObjectItf_, &audioSrc, &audioSnk,
|
||||
sizeof(ids) / sizeof(ids[0]), ids, req);
|
||||
SLASSERT(result);
|
||||
|
||||
// realize the player
|
||||
result = (*playerObjectItf_)->Realize(playerObjectItf_, SL_BOOLEAN_FALSE);
|
||||
SLASSERT(result);
|
||||
|
||||
// get the play interface
|
||||
result = (*playerObjectItf_)
|
||||
->GetInterface(playerObjectItf_, SL_IID_PLAY, &playItf_);
|
||||
SLASSERT(result);
|
||||
|
||||
// get the buffer queue interface
|
||||
result = (*playerObjectItf_)
|
||||
->GetInterface(playerObjectItf_, SL_IID_BUFFERQUEUE,
|
||||
&playBufferQueueItf_);
|
||||
SLASSERT(result);
|
||||
|
||||
// register callback on the buffer queue
|
||||
result = (*playBufferQueueItf_)
|
||||
->RegisterCallback(playBufferQueueItf_, bqPlayerCallback, this);
|
||||
SLASSERT(result);
|
||||
|
||||
result = (*playItf_)->SetPlayState(playItf_, SL_PLAYSTATE_STOPPED);
|
||||
SLASSERT(result);
|
||||
|
||||
// create an empty queue to track deviceQueue
|
||||
devShadowQueue_ = new AudioQueue(DEVICE_SHADOW_BUFFER_QUEUE_LEN);
|
||||
assert(devShadowQueue_);
|
||||
|
||||
silentBuf_.cap_ = (format_pcm.containerSize >> 3) * format_pcm.numChannels *
|
||||
sampleInfo_.framesPerBuf_;
|
||||
silentBuf_.buf_ = new uint8_t[silentBuf_.cap_];
|
||||
memset(silentBuf_.buf_, 0, silentBuf_.cap_);
|
||||
silentBuf_.size_ = silentBuf_.cap_;
|
||||
|
||||
#ifdef ENABLE_LOG
|
||||
std::string name = "play";
|
||||
logFile_ = new AndroidLog(name);
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioPlayer::~AudioPlayer() {
|
||||
std::lock_guard<std::mutex> lock(stopMutex_);
|
||||
|
||||
// destroy buffer queue audio player object, and invalidate all associated
|
||||
// interfaces
|
||||
if (playerObjectItf_ != NULL) {
|
||||
(*playerObjectItf_)->Destroy(playerObjectItf_);
|
||||
}
|
||||
// Consume all non-completed audio buffers
|
||||
sample_buf *buf = NULL;
|
||||
while (devShadowQueue_->front(&buf)) {
|
||||
buf->size_ = 0;
|
||||
devShadowQueue_->pop();
|
||||
if(buf != &silentBuf_) {
|
||||
freeQueue_->push(buf);
|
||||
}
|
||||
}
|
||||
delete devShadowQueue_;
|
||||
|
||||
while (playQueue_->front(&buf)) {
|
||||
buf->size_ = 0;
|
||||
playQueue_->pop();
|
||||
freeQueue_->push(buf);
|
||||
}
|
||||
|
||||
// destroy output mix object, and invalidate all associated interfaces
|
||||
if (outputMixObjectItf_) {
|
||||
(*outputMixObjectItf_)->Destroy(outputMixObjectItf_);
|
||||
}
|
||||
|
||||
delete[] silentBuf_.buf_;
|
||||
}
|
||||
|
||||
void AudioPlayer::SetBufQueue(AudioQueue *playQ, AudioQueue *freeQ) {
|
||||
playQueue_ = playQ;
|
||||
freeQueue_ = freeQ;
|
||||
}
|
||||
|
||||
SLresult AudioPlayer::Start(void) {
|
||||
SLuint32 state;
|
||||
SLresult result = (*playItf_)->GetPlayState(playItf_, &state);
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
return SL_BOOLEAN_FALSE;
|
||||
}
|
||||
if (state == SL_PLAYSTATE_PLAYING) {
|
||||
return SL_BOOLEAN_TRUE;
|
||||
}
|
||||
|
||||
result = (*playItf_)->SetPlayState(playItf_, SL_PLAYSTATE_STOPPED);
|
||||
SLASSERT(result);
|
||||
|
||||
result =
|
||||
(*playBufferQueueItf_)
|
||||
->Enqueue(playBufferQueueItf_, silentBuf_.buf_, silentBuf_.size_);
|
||||
SLASSERT(result);
|
||||
devShadowQueue_->push(&silentBuf_);
|
||||
|
||||
result = (*playItf_)->SetPlayState(playItf_, SL_PLAYSTATE_PLAYING);
|
||||
SLASSERT(result);
|
||||
return SL_BOOLEAN_TRUE;
|
||||
}
|
||||
|
||||
void AudioPlayer::Stop(void) {
|
||||
SLuint32 state;
|
||||
|
||||
SLresult result = (*playItf_)->GetPlayState(playItf_, &state);
|
||||
SLASSERT(result);
|
||||
|
||||
if (state == SL_PLAYSTATE_STOPPED) return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(stopMutex_);
|
||||
|
||||
result = (*playItf_)->SetPlayState(playItf_, SL_PLAYSTATE_STOPPED);
|
||||
SLASSERT(result);
|
||||
(*playBufferQueueItf_)->Clear(playBufferQueueItf_);
|
||||
|
||||
#ifdef ENABLE_LOG
|
||||
if (logFile_) {
|
||||
delete logFile_;
|
||||
logFile_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioPlayer::RegisterCallback(ENGINE_CALLBACK cb, void *ctx) {
|
||||
callback_ = cb;
|
||||
ctx_ = ctx;
|
||||
}
|
||||
|
||||
uint32_t AudioPlayer::dbgGetDevBufCount(void) {
|
||||
return (devShadowQueue_->size());
|
||||
}
|
||||
55
audio-echo/app/src/main/cpp/audio_player.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef NATIVE_AUDIO_AUDIO_PLAYER_H
|
||||
#define NATIVE_AUDIO_AUDIO_PLAYER_H
|
||||
#include <sys/types.h>
|
||||
#include "audio_common.h"
|
||||
#include "buf_manager.h"
|
||||
#include "debug_utils.h"
|
||||
|
||||
class AudioPlayer {
|
||||
// buffer queue player interfaces
|
||||
SLObjectItf outputMixObjectItf_;
|
||||
SLObjectItf playerObjectItf_;
|
||||
SLPlayItf playItf_;
|
||||
SLAndroidSimpleBufferQueueItf playBufferQueueItf_;
|
||||
|
||||
SampleFormat sampleInfo_;
|
||||
AudioQueue *freeQueue_; // user
|
||||
AudioQueue *playQueue_; // user
|
||||
AudioQueue *devShadowQueue_; // owner
|
||||
|
||||
ENGINE_CALLBACK callback_;
|
||||
void *ctx_;
|
||||
sample_buf silentBuf_;
|
||||
#ifdef ENABLE_LOG
|
||||
AndroidLog *logFile_;
|
||||
#endif
|
||||
std::mutex stopMutex_;
|
||||
|
||||
public:
|
||||
explicit AudioPlayer(SampleFormat *sampleFormat, SLEngineItf engine);
|
||||
~AudioPlayer();
|
||||
void SetBufQueue(AudioQueue *playQ, AudioQueue *freeQ);
|
||||
SLresult Start(void);
|
||||
void Stop(void);
|
||||
void ProcessSLCallback(SLAndroidSimpleBufferQueueItf bq);
|
||||
uint32_t dbgGetDevBufCount(void);
|
||||
void RegisterCallback(ENGINE_CALLBACK cb, void *ctx);
|
||||
};
|
||||
|
||||
#endif // NATIVE_AUDIO_AUDIO_PLAYER_H
|
||||
213
audio-echo/app/src/main/cpp/audio_recorder.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2015 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 <cstring>
|
||||
#include <cstdlib>
|
||||
#include "audio_recorder.h"
|
||||
/*
|
||||
* bqRecorderCallback(): called for every buffer is full;
|
||||
* pass directly to handler
|
||||
*/
|
||||
void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *rec) {
|
||||
(static_cast<AudioRecorder *>(rec))->ProcessSLCallback(bq);
|
||||
}
|
||||
|
||||
void AudioRecorder::ProcessSLCallback(SLAndroidSimpleBufferQueueItf bq) {
|
||||
#ifdef ENABLE_LOG
|
||||
recLog_->logTime();
|
||||
#endif
|
||||
assert(bq == recBufQueueItf_);
|
||||
sample_buf *dataBuf = NULL;
|
||||
devShadowQueue_->front(&dataBuf);
|
||||
devShadowQueue_->pop();
|
||||
dataBuf->size_ = dataBuf->cap_; // device only calls us when it is really
|
||||
// full
|
||||
|
||||
callback_(ctx_, ENGINE_SERVICE_MSG_RECORDED_AUDIO_AVAILABLE, dataBuf);
|
||||
recQueue_->push(dataBuf);
|
||||
|
||||
sample_buf *freeBuf;
|
||||
while (freeQueue_->front(&freeBuf) && devShadowQueue_->push(freeBuf)) {
|
||||
freeQueue_->pop();
|
||||
SLresult result = (*bq)->Enqueue(bq, freeBuf->buf_, freeBuf->cap_);
|
||||
SLASSERT(result);
|
||||
}
|
||||
|
||||
++audioBufCount;
|
||||
|
||||
// should leave the device to sleep to save power if no buffers
|
||||
if (devShadowQueue_->size() == 0) {
|
||||
(*recItf_)->SetRecordState(recItf_, SL_RECORDSTATE_STOPPED);
|
||||
}
|
||||
}
|
||||
|
||||
AudioRecorder::AudioRecorder(SampleFormat *sampleFormat, SLEngineItf slEngine)
|
||||
: freeQueue_(nullptr),
|
||||
recQueue_(nullptr),
|
||||
devShadowQueue_(nullptr),
|
||||
callback_(nullptr) {
|
||||
SLresult result;
|
||||
sampleInfo_ = *sampleFormat;
|
||||
SLAndroidDataFormat_PCM_EX format_pcm;
|
||||
ConvertToSLSampleFormat(&format_pcm, &sampleInfo_);
|
||||
|
||||
// configure audio source
|
||||
SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE,
|
||||
SL_IODEVICE_AUDIOINPUT,
|
||||
SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
|
||||
SLDataSource audioSrc = {&loc_dev, NULL};
|
||||
|
||||
// configure audio sink
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
|
||||
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, DEVICE_SHADOW_BUFFER_QUEUE_LEN};
|
||||
|
||||
SLDataSink audioSnk = {&loc_bq, &format_pcm};
|
||||
|
||||
// create audio recorder
|
||||
// (requires the RECORD_AUDIO permission)
|
||||
const SLInterfaceID id[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
SL_IID_ANDROIDCONFIGURATION};
|
||||
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
||||
result = (*slEngine)->CreateAudioRecorder(
|
||||
slEngine, &recObjectItf_, &audioSrc, &audioSnk,
|
||||
sizeof(id) / sizeof(id[0]), id, req);
|
||||
SLASSERT(result);
|
||||
|
||||
// Configure the voice recognition preset which has no
|
||||
// signal processing for lower latency.
|
||||
SLAndroidConfigurationItf inputConfig;
|
||||
result = (*recObjectItf_)
|
||||
->GetInterface(recObjectItf_, SL_IID_ANDROIDCONFIGURATION,
|
||||
&inputConfig);
|
||||
if (SL_RESULT_SUCCESS == result) {
|
||||
SLuint32 presetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
|
||||
(*inputConfig)
|
||||
->SetConfiguration(inputConfig, SL_ANDROID_KEY_RECORDING_PRESET,
|
||||
&presetValue, sizeof(SLuint32));
|
||||
}
|
||||
result = (*recObjectItf_)->Realize(recObjectItf_, SL_BOOLEAN_FALSE);
|
||||
SLASSERT(result);
|
||||
result =
|
||||
(*recObjectItf_)->GetInterface(recObjectItf_, SL_IID_RECORD, &recItf_);
|
||||
SLASSERT(result);
|
||||
|
||||
result = (*recObjectItf_)
|
||||
->GetInterface(recObjectItf_, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
&recBufQueueItf_);
|
||||
SLASSERT(result);
|
||||
|
||||
result = (*recBufQueueItf_)
|
||||
->RegisterCallback(recBufQueueItf_, bqRecorderCallback, this);
|
||||
SLASSERT(result);
|
||||
|
||||
devShadowQueue_ = new AudioQueue(DEVICE_SHADOW_BUFFER_QUEUE_LEN);
|
||||
assert(devShadowQueue_);
|
||||
#ifdef ENABLE_LOG
|
||||
std::string name = "rec";
|
||||
recLog_ = new AndroidLog(name);
|
||||
#endif
|
||||
}
|
||||
|
||||
SLboolean AudioRecorder::Start(void) {
|
||||
if (!freeQueue_ || !recQueue_ || !devShadowQueue_) {
|
||||
LOGE("====NULL poiter to Start(%p, %p, %p)", freeQueue_, recQueue_,
|
||||
devShadowQueue_);
|
||||
return SL_BOOLEAN_FALSE;
|
||||
}
|
||||
audioBufCount = 0;
|
||||
|
||||
SLresult result;
|
||||
// in case already recording, stop recording and clear buffer queue
|
||||
result = (*recItf_)->SetRecordState(recItf_, SL_RECORDSTATE_STOPPED);
|
||||
SLASSERT(result);
|
||||
result = (*recBufQueueItf_)->Clear(recBufQueueItf_);
|
||||
SLASSERT(result);
|
||||
|
||||
for (int i = 0; i < RECORD_DEVICE_KICKSTART_BUF_COUNT; i++) {
|
||||
sample_buf *buf = NULL;
|
||||
if (!freeQueue_->front(&buf)) {
|
||||
LOGE("=====OutOfFreeBuffers @ startingRecording @ (%d)", i);
|
||||
break;
|
||||
}
|
||||
freeQueue_->pop();
|
||||
assert(buf->buf_ && buf->cap_ && !buf->size_);
|
||||
|
||||
result = (*recBufQueueItf_)->Enqueue(recBufQueueItf_, buf->buf_, buf->cap_);
|
||||
SLASSERT(result);
|
||||
devShadowQueue_->push(buf);
|
||||
}
|
||||
|
||||
result = (*recItf_)->SetRecordState(recItf_, SL_RECORDSTATE_RECORDING);
|
||||
SLASSERT(result);
|
||||
|
||||
return (result == SL_RESULT_SUCCESS ? SL_BOOLEAN_TRUE : SL_BOOLEAN_FALSE);
|
||||
}
|
||||
|
||||
SLboolean AudioRecorder::Stop(void) {
|
||||
// in case already recording, stop recording and clear buffer queue
|
||||
SLuint32 curState;
|
||||
|
||||
SLresult result = (*recItf_)->GetRecordState(recItf_, &curState);
|
||||
SLASSERT(result);
|
||||
if (curState == SL_RECORDSTATE_STOPPED) {
|
||||
return SL_BOOLEAN_TRUE;
|
||||
}
|
||||
result = (*recItf_)->SetRecordState(recItf_, SL_RECORDSTATE_STOPPED);
|
||||
SLASSERT(result);
|
||||
result = (*recBufQueueItf_)->Clear(recBufQueueItf_);
|
||||
SLASSERT(result);
|
||||
|
||||
#ifdef ENABLE_LOG
|
||||
recLog_->flush();
|
||||
#endif
|
||||
|
||||
return SL_BOOLEAN_TRUE;
|
||||
}
|
||||
|
||||
AudioRecorder::~AudioRecorder() {
|
||||
// destroy audio recorder object, and invalidate all associated interfaces
|
||||
if (recObjectItf_ != NULL) {
|
||||
(*recObjectItf_)->Destroy(recObjectItf_);
|
||||
}
|
||||
|
||||
if (devShadowQueue_) {
|
||||
sample_buf *buf = NULL;
|
||||
while (devShadowQueue_->front(&buf)) {
|
||||
devShadowQueue_->pop();
|
||||
freeQueue_->push(buf);
|
||||
}
|
||||
delete (devShadowQueue_);
|
||||
}
|
||||
#ifdef ENABLE_LOG
|
||||
if (recLog_) {
|
||||
delete recLog_;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioRecorder::SetBufQueues(AudioQueue *freeQ, AudioQueue *recQ) {
|
||||
assert(freeQ && recQ);
|
||||
freeQueue_ = freeQ;
|
||||
recQueue_ = recQ;
|
||||
}
|
||||
|
||||
void AudioRecorder::RegisterCallback(ENGINE_CALLBACK cb, void *ctx) {
|
||||
callback_ = cb;
|
||||
ctx_ = ctx;
|
||||
}
|
||||
int32_t AudioRecorder::dbgGetDevBufCount(void) {
|
||||
return devShadowQueue_->size();
|
||||
}
|
||||
55
audio-echo/app/src/main/cpp/audio_recorder.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef NATIVE_AUDIO_AUDIO_RECORDER_H
|
||||
#define NATIVE_AUDIO_AUDIO_RECORDER_H
|
||||
#include <sys/types.h>
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#include "audio_common.h"
|
||||
#include "buf_manager.h"
|
||||
#include "debug_utils.h"
|
||||
|
||||
class AudioRecorder {
|
||||
SLObjectItf recObjectItf_;
|
||||
SLRecordItf recItf_;
|
||||
SLAndroidSimpleBufferQueueItf recBufQueueItf_;
|
||||
|
||||
SampleFormat sampleInfo_;
|
||||
AudioQueue *freeQueue_; // user
|
||||
AudioQueue *recQueue_; // user
|
||||
AudioQueue *devShadowQueue_; // owner
|
||||
uint32_t audioBufCount;
|
||||
|
||||
ENGINE_CALLBACK callback_;
|
||||
void *ctx_;
|
||||
|
||||
public:
|
||||
explicit AudioRecorder(SampleFormat *, SLEngineItf engineEngine);
|
||||
~AudioRecorder();
|
||||
SLboolean Start(void);
|
||||
SLboolean Stop(void);
|
||||
void SetBufQueues(AudioQueue *freeQ, AudioQueue *recQ);
|
||||
void ProcessSLCallback(SLAndroidSimpleBufferQueueItf bq);
|
||||
void RegisterCallback(ENGINE_CALLBACK cb, void *ctx);
|
||||
int32_t dbgGetDevBufCount(void);
|
||||
|
||||
#ifdef ENABLE_LOG
|
||||
AndroidLog *recLog_;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // NATIVE_AUDIO_AUDIO_RECORDER_H
|
||||
202
audio-echo/app/src/main/cpp/buf_manager.h
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef NATIVE_AUDIO_BUF_MANAGER_H
|
||||
#define NATIVE_AUDIO_BUF_MANAGER_H
|
||||
#include <sys/types.h>
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
|
||||
#ifndef CACHE_ALIGN
|
||||
#define CACHE_ALIGN 64
|
||||
#endif
|
||||
|
||||
/*
|
||||
* ProducerConsumerQueue, borrowed from Ian NiLewis
|
||||
*/
|
||||
template <typename T>
|
||||
class ProducerConsumerQueue {
|
||||
public:
|
||||
explicit ProducerConsumerQueue(int size)
|
||||
: ProducerConsumerQueue(size, new T[size]) {}
|
||||
|
||||
explicit ProducerConsumerQueue(int size, T* buffer)
|
||||
: size_(size), buffer_(buffer) {
|
||||
// This is necessary because we depend on twos-complement wraparound
|
||||
// to take care of overflow conditions.
|
||||
assert(size < std::numeric_limits<int>::max());
|
||||
}
|
||||
|
||||
bool push(const T& item) {
|
||||
return push([&](T* ptr) -> bool {
|
||||
*ptr = item;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// get() is idempotent between calls to commit().
|
||||
T* getWriteablePtr() {
|
||||
T* result = nullptr;
|
||||
|
||||
bool check __attribute__((unused)); //= false;
|
||||
|
||||
check = push([&](T* head) -> bool {
|
||||
result = head;
|
||||
return false; // don't increment
|
||||
});
|
||||
|
||||
// if there's no space, result should not have been set, and vice versa
|
||||
assert(check == (result != nullptr));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool commitWriteablePtr(T* ptr) {
|
||||
bool result = push([&](T* head) -> bool {
|
||||
// this writer func does nothing, because we assume that the caller
|
||||
// has already written to *ptr after acquiring it from a call to get().
|
||||
// So just double-check that ptr is actually at the write head, and
|
||||
// return true to indicate that it's safe to advance.
|
||||
|
||||
// if this isn't the same pointer we got from a call to get(), then
|
||||
// something has gone terribly wrong. Either there was an intervening
|
||||
// call to push() or commit(), or the pointer is spurious.
|
||||
assert(ptr == head);
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// writer() can return false, which indicates that the caller
|
||||
// of push() changed its mind while writing (e.g. ran out of bytes)
|
||||
template <typename F>
|
||||
bool push(const F& writer) {
|
||||
bool result = false;
|
||||
int readptr = read_.load(std::memory_order_acquire);
|
||||
int writeptr = write_.load(std::memory_order_relaxed);
|
||||
|
||||
// note that while readptr and writeptr will eventually
|
||||
// wrap around, taking their difference is still valid as
|
||||
// long as size_ < MAXINT.
|
||||
int space = size_ - (int)(writeptr - readptr);
|
||||
if (space >= 1) {
|
||||
result = true;
|
||||
|
||||
// writer
|
||||
if (writer(buffer_.get() + (writeptr % size_))) {
|
||||
++writeptr;
|
||||
write_.store(writeptr, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// front out the queue, but not pop-out
|
||||
bool front(T* out_item) {
|
||||
return front([&](T* ptr) -> bool {
|
||||
*out_item = *ptr;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void pop(void) {
|
||||
int readptr = read_.load(std::memory_order_relaxed);
|
||||
++readptr;
|
||||
read_.store(readptr, std::memory_order_release);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool front(const F& reader) {
|
||||
bool result = false;
|
||||
|
||||
int writeptr = write_.load(std::memory_order_acquire);
|
||||
int readptr = read_.load(std::memory_order_relaxed);
|
||||
|
||||
// As above, wraparound is ok
|
||||
int available = (int)(writeptr - readptr);
|
||||
if (available >= 1) {
|
||||
result = true;
|
||||
reader(buffer_.get() + (readptr % size_));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
uint32_t size(void) {
|
||||
int writeptr = write_.load(std::memory_order_acquire);
|
||||
int readptr = read_.load(std::memory_order_relaxed);
|
||||
|
||||
return (uint32_t)(writeptr - readptr);
|
||||
}
|
||||
|
||||
private:
|
||||
int size_;
|
||||
std::unique_ptr<T> buffer_;
|
||||
|
||||
// forcing cache line alignment to eliminate false sharing of the
|
||||
// frequently-updated read and write pointers. The object is to never
|
||||
// let these get into the "shared" state where they'd cause a cache miss
|
||||
// for every write.
|
||||
alignas(CACHE_ALIGN) std::atomic<int> read_{0};
|
||||
alignas(CACHE_ALIGN) std::atomic<int> write_{0};
|
||||
};
|
||||
|
||||
struct sample_buf {
|
||||
uint8_t* buf_; // audio sample container
|
||||
uint32_t cap_; // buffer capacity in byte
|
||||
uint32_t size_; // audio sample size (n buf) in byte
|
||||
};
|
||||
|
||||
using AudioQueue = ProducerConsumerQueue<sample_buf*>;
|
||||
|
||||
__inline__ void releaseSampleBufs(sample_buf* bufs, uint32_t& count) {
|
||||
if (!bufs || !count) {
|
||||
return;
|
||||
}
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (bufs[i].buf_) delete[] bufs[i].buf_;
|
||||
}
|
||||
delete[] bufs;
|
||||
}
|
||||
__inline__ sample_buf* allocateSampleBufs(uint32_t count, uint32_t sizeInByte) {
|
||||
if (count <= 0 || sizeInByte <= 0) {
|
||||
return nullptr;
|
||||
}
|
||||
sample_buf* bufs = new sample_buf[count];
|
||||
assert(bufs);
|
||||
memset(bufs, 0, sizeof(sample_buf) * count);
|
||||
|
||||
uint32_t allocSize = (sizeInByte + 3) & ~3; // padding to 4 bytes aligned
|
||||
uint32_t i;
|
||||
for (i = 0; i < count; i++) {
|
||||
bufs[i].buf_ = new uint8_t[allocSize];
|
||||
if (bufs[i].buf_ == nullptr) {
|
||||
LOGW("====Requesting %d buffers, allocated %d in %s", count, i,
|
||||
__FUNCTION__);
|
||||
break;
|
||||
}
|
||||
bufs[i].cap_ = sizeInByte;
|
||||
bufs[i].size_ = 0; // 0 data in it
|
||||
}
|
||||
if (i < 2) {
|
||||
releaseSampleBufs(bufs, i);
|
||||
bufs = nullptr;
|
||||
}
|
||||
count = i;
|
||||
return bufs;
|
||||
}
|
||||
|
||||
#endif // NATIVE_AUDIO_BUF_MANAGER_H
|
||||
104
audio-echo/app/src/main/cpp/debug_utils.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2015 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 <cstdio>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "debug_utils.h"
|
||||
#include "android_debug.h"
|
||||
#include <inttypes.h>
|
||||
|
||||
static const char* FILE_PREFIX = "/sdcard/data/audio";
|
||||
|
||||
volatile uint32_t AndroidLog::fileIdx_ = 0;
|
||||
AndroidLog::AndroidLog() : fp_(NULL), prevTick_(static_cast<uint64_t>(0)) {
|
||||
fileName_ = FILE_PREFIX;
|
||||
openFile();
|
||||
}
|
||||
|
||||
AndroidLog::AndroidLog(std::string& file_name)
|
||||
: fp_(NULL), prevTick_(static_cast<uint64_t>(0)) {
|
||||
fileName_ = std::string(FILE_PREFIX) + std::string("_") + file_name;
|
||||
openFile();
|
||||
}
|
||||
|
||||
AndroidLog::~AndroidLog() { flush(); }
|
||||
|
||||
void AndroidLog::flush() {
|
||||
if (fp_) {
|
||||
fflush(fp_);
|
||||
fclose(fp_);
|
||||
fp_ = NULL;
|
||||
}
|
||||
prevTick_ = static_cast<uint64_t>(0);
|
||||
}
|
||||
|
||||
void AndroidLog::log(void* buf, uint32_t size) {
|
||||
Lock fileLock(&mutex_);
|
||||
if (!buf || !size) return;
|
||||
|
||||
if (fp_ || openFile()) {
|
||||
fwrite(buf, size, 1, fp_);
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidLog::log(const char* fmt, ...) {
|
||||
Lock fileLock(&mutex_);
|
||||
if (!fmt) {
|
||||
return;
|
||||
}
|
||||
if (fp_ || openFile()) {
|
||||
va_list vp;
|
||||
va_start(vp, fmt);
|
||||
vfprintf(fp_, fmt, vp);
|
||||
va_end(vp);
|
||||
}
|
||||
}
|
||||
|
||||
FILE* AndroidLog::openFile() {
|
||||
Lock fileLock(&mutex_);
|
||||
|
||||
if (fp_) {
|
||||
return fp_;
|
||||
}
|
||||
|
||||
char fileName[64];
|
||||
sprintf(fileName, "%s_%d", fileName_.c_str(), AndroidLog::fileIdx_++);
|
||||
fp_ = fopen(fileName, "wb");
|
||||
if (fp_ == NULL) {
|
||||
LOGE("====failed to open file %s", fileName);
|
||||
}
|
||||
return fp_;
|
||||
}
|
||||
void AndroidLog::logTime() {
|
||||
if (prevTick_ == static_cast<uint64_t>(0)) {
|
||||
/*
|
||||
* init counter, bypass the first one
|
||||
*/
|
||||
prevTick_ = getCurrentTicks();
|
||||
return;
|
||||
}
|
||||
uint64_t curTick = getCurrentTicks();
|
||||
uint64_t delta = curTick - prevTick_;
|
||||
log("%" PRIu64 " %" PRIu64 "\n", curTick, delta);
|
||||
prevTick_ = curTick;
|
||||
}
|
||||
|
||||
uint64_t AndroidLog::getCurrentTicks() {
|
||||
struct timeval Time;
|
||||
gettimeofday(&Time, NULL);
|
||||
|
||||
return (static_cast<uint64_t>(1000000) * Time.tv_sec + Time.tv_usec);
|
||||
}
|
||||
61
audio-echo/app/src/main/cpp/debug_utils.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef NATIVE_AUDIO_DEBUG_UTILS_H
|
||||
#define NATIVE_AUDIO_DEBUG_UTILS_H
|
||||
#include <cstdio>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
/*
|
||||
* debug_write_file()
|
||||
* Write given data to a file as binary file. File name is
|
||||
* "/sdcard/data/audio_%d", file_index++
|
||||
* requirement: must have /sdcard/data already created on android device
|
||||
*/
|
||||
class Lock {
|
||||
public:
|
||||
explicit Lock(std::recursive_mutex* mtx) {
|
||||
mutex_ = mtx;
|
||||
mutex_->lock();
|
||||
}
|
||||
~Lock() { mutex_->unlock(); }
|
||||
|
||||
private:
|
||||
std::recursive_mutex* mutex_;
|
||||
};
|
||||
class AndroidLog {
|
||||
public:
|
||||
AndroidLog();
|
||||
AndroidLog(std::string& fileName);
|
||||
~AndroidLog();
|
||||
void log(void* buf, uint32_t size);
|
||||
void log(const char* fmt, ...);
|
||||
void logTime();
|
||||
void flush();
|
||||
static volatile uint32_t fileIdx_;
|
||||
|
||||
private:
|
||||
uint64_t getCurrentTicks();
|
||||
FILE* fp_;
|
||||
FILE* openFile();
|
||||
uint64_t prevTick_; // Tick in milisecond
|
||||
std::recursive_mutex mutex_;
|
||||
std::string fileName_;
|
||||
};
|
||||
|
||||
void debug_write_file(void* buf, uint32_t size);
|
||||
|
||||
#endif // NATIVE_AUDIO_DEBUG_UTILS_H
|
||||
54
audio-echo/app/src/main/cpp/jni_interface.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef JNI_INTERFACE_H
|
||||
#define JNI_INTERFACE_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_google_sample_echo_MainActivity_createSLEngine(
|
||||
JNIEnv *env, jclass, jint, jint, jlong delayInMs, jfloat decay);
|
||||
JNIEXPORT void JNICALL Java_com_google_sample_echo_MainActivity_deleteSLEngine(
|
||||
JNIEnv *env, jclass type);
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_createSLBufferQueueAudioPlayer(
|
||||
JNIEnv *env, jclass);
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_deleteSLBufferQueueAudioPlayer(
|
||||
JNIEnv *env, jclass type);
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_createAudioRecorder(JNIEnv *env,
|
||||
jclass type);
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_deleteAudioRecorder(JNIEnv *env,
|
||||
jclass type);
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_startPlay(JNIEnv *env, jclass type);
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_stopPlay(JNIEnv *env, jclass type);
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_sample_echo_MainActivity_configureEcho(JNIEnv *env, jclass type,
|
||||
jint delayInMs,
|
||||
jfloat decay);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // JNI_INTERFACE_H
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.sample.echo;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRecord;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class MainActivity extends Activity
|
||||
implements ActivityCompat.OnRequestPermissionsResultCallback {
|
||||
private static final int AUDIO_ECHO_REQUEST = 0;
|
||||
|
||||
private Button controlButton;
|
||||
private TextView statusView;
|
||||
private String nativeSampleRate;
|
||||
private String nativeSampleBufSize;
|
||||
|
||||
private SeekBar delaySeekBar;
|
||||
private TextView curDelayTV;
|
||||
private int echoDelayProgress;
|
||||
|
||||
private SeekBar decaySeekBar;
|
||||
private TextView curDecayTV;
|
||||
private float echoDecayProgress;
|
||||
|
||||
private boolean supportRecording;
|
||||
private Boolean isPlaying = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
controlButton = (Button)findViewById((R.id.capture_control_button));
|
||||
statusView = (TextView)findViewById(R.id.statusView);
|
||||
queryNativeAudioParameters();
|
||||
|
||||
delaySeekBar = (SeekBar)findViewById(R.id.delaySeekBar);
|
||||
curDelayTV = (TextView)findViewById(R.id.curDelay);
|
||||
echoDelayProgress = delaySeekBar.getProgress() * 1000 / delaySeekBar.getMax();
|
||||
delaySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
float curVal = (float)progress / delaySeekBar.getMax();
|
||||
curDelayTV.setText(String.format("%s", curVal));
|
||||
setSeekBarPromptPosition(delaySeekBar, curDelayTV);
|
||||
if (!fromUser) return;
|
||||
|
||||
echoDelayProgress = progress * 1000 / delaySeekBar.getMax();
|
||||
configureEcho(echoDelayProgress, echoDecayProgress);
|
||||
}
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {}
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {}
|
||||
});
|
||||
delaySeekBar.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSeekBarPromptPosition(delaySeekBar, curDelayTV);
|
||||
}
|
||||
});
|
||||
|
||||
decaySeekBar = (SeekBar)findViewById(R.id.decaySeekBar);
|
||||
curDecayTV = (TextView)findViewById(R.id.curDecay);
|
||||
echoDecayProgress = (float)decaySeekBar.getProgress() / decaySeekBar.getMax();
|
||||
decaySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
float curVal = (float)progress / seekBar.getMax();
|
||||
curDecayTV.setText(String.format("%s", curVal));
|
||||
setSeekBarPromptPosition(decaySeekBar, curDecayTV);
|
||||
if (!fromUser)
|
||||
return;
|
||||
|
||||
echoDecayProgress = curVal;
|
||||
configureEcho(echoDelayProgress, echoDecayProgress);
|
||||
}
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {}
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {}
|
||||
});
|
||||
decaySeekBar.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSeekBarPromptPosition(decaySeekBar, curDecayTV);
|
||||
}
|
||||
});
|
||||
|
||||
// initialize native audio system
|
||||
updateNativeAudioUI();
|
||||
|
||||
if (supportRecording) {
|
||||
createSLEngine(
|
||||
Integer.parseInt(nativeSampleRate),
|
||||
Integer.parseInt(nativeSampleBufSize),
|
||||
echoDelayProgress,
|
||||
echoDecayProgress);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSeekBarPromptPosition(SeekBar seekBar, TextView label) {
|
||||
float thumbX = (float)seekBar.getProgress()/ seekBar.getMax() *
|
||||
seekBar.getWidth() + seekBar.getX();
|
||||
label.setX(thumbX - label.getWidth()/2.0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (supportRecording) {
|
||||
if (isPlaying) {
|
||||
stopPlay();
|
||||
}
|
||||
deleteSLEngine();
|
||||
isPlaying = false;
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle action bar item clicks here. The action bar will
|
||||
// automatically handle clicks on the Home/Up button, so long
|
||||
// as you specify a parent activity in AndroidManifest.xml.
|
||||
int id = item.getItemId();
|
||||
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (id == R.id.action_settings) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void startEcho() {
|
||||
if(!supportRecording){
|
||||
return;
|
||||
}
|
||||
if (!isPlaying) {
|
||||
if(!createSLBufferQueueAudioPlayer()) {
|
||||
statusView.setText(getString(R.string.player_error_msg));
|
||||
return;
|
||||
}
|
||||
if(!createAudioRecorder()) {
|
||||
deleteSLBufferQueueAudioPlayer();
|
||||
statusView.setText(getString(R.string.recorder_error_msg));
|
||||
return;
|
||||
}
|
||||
startPlay(); // startPlay() triggers startRecording()
|
||||
statusView.setText(getString(R.string.echoing_status_msg));
|
||||
} else {
|
||||
stopPlay(); // stopPlay() triggers stopRecording()
|
||||
updateNativeAudioUI();
|
||||
deleteAudioRecorder();
|
||||
deleteSLBufferQueueAudioPlayer();
|
||||
}
|
||||
isPlaying = !isPlaying;
|
||||
controlButton.setText(getString(isPlaying ?
|
||||
R.string.cmd_stop_echo: R.string.cmd_start_echo));
|
||||
}
|
||||
public void onEchoClick(View view) {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) !=
|
||||
PackageManager.PERMISSION_GRANTED) {
|
||||
statusView.setText(getString(R.string.request_permission_status_msg));
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
new String[] { Manifest.permission.RECORD_AUDIO },
|
||||
AUDIO_ECHO_REQUEST);
|
||||
return;
|
||||
}
|
||||
startEcho();
|
||||
}
|
||||
|
||||
public void getLowLatencyParameters(View view) {
|
||||
updateNativeAudioUI();
|
||||
}
|
||||
|
||||
private void queryNativeAudioParameters() {
|
||||
supportRecording = true;
|
||||
AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
if(myAudioMgr == null) {
|
||||
supportRecording = false;
|
||||
return;
|
||||
}
|
||||
nativeSampleRate = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
|
||||
nativeSampleBufSize =myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
|
||||
|
||||
// hardcoded channel to mono: both sides -- C++ and Java sides
|
||||
int recBufSize = AudioRecord.getMinBufferSize(
|
||||
Integer.parseInt(nativeSampleRate),
|
||||
AudioFormat.CHANNEL_IN_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT);
|
||||
if (recBufSize == AudioRecord.ERROR ||
|
||||
recBufSize == AudioRecord.ERROR_BAD_VALUE) {
|
||||
supportRecording = false;
|
||||
}
|
||||
|
||||
}
|
||||
private void updateNativeAudioUI() {
|
||||
if (!supportRecording) {
|
||||
statusView.setText(getString(R.string.mic_error_msg));
|
||||
controlButton.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
statusView.setText(getString(R.string.fast_audio_info_msg,
|
||||
nativeSampleRate, nativeSampleBufSize));
|
||||
}
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||
@NonNull int[] grantResults) {
|
||||
/*
|
||||
* if any permission failed, the sample could not play
|
||||
*/
|
||||
if (AUDIO_ECHO_REQUEST != requestCode) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
return;
|
||||
}
|
||||
|
||||
if (grantResults.length != 1 ||
|
||||
grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
/*
|
||||
* When user denied permission, throw a Toast to prompt that RECORD_AUDIO
|
||||
* is necessary; also display the status on UI
|
||||
* Then application goes back to the original state: it behaves as if the button
|
||||
* was not clicked. The assumption is that user will re-click the "start" button
|
||||
* (to retry), or shutdown the app in normal way.
|
||||
*/
|
||||
statusView.setText(getString(R.string.permission_error_msg));
|
||||
Toast.makeText(getApplicationContext(),
|
||||
getString(R.string.permission_prompt_msg),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* When permissions are granted, we prompt the user the status. User would
|
||||
* re-try the "start" button to perform the normal operation. This saves us the extra
|
||||
* logic in code for async processing of the button listener.
|
||||
*/
|
||||
statusView.setText(getString(R.string.permission_granted_msg,getString(R.string.cmd_start_echo)));
|
||||
|
||||
|
||||
// The callback runs on app's thread, so we are safe to resume the action
|
||||
startEcho();
|
||||
}
|
||||
|
||||
/*
|
||||
* Loading our lib
|
||||
*/
|
||||
static {
|
||||
System.loadLibrary("echo");
|
||||
}
|
||||
|
||||
/*
|
||||
* jni function declarations
|
||||
*/
|
||||
static native void createSLEngine(int rate, int framesPerBuf,
|
||||
long delayInMs, float decay);
|
||||
static native void deleteSLEngine();
|
||||
static native boolean configureEcho(int delayInMs, float decay);
|
||||
static native boolean createSLBufferQueueAudioPlayer();
|
||||
static native void deleteSLBufferQueueAudioPlayer();
|
||||
|
||||
static native boolean createAudioRecorder();
|
||||
static native void deleteAudioRecorder();
|
||||
static native void startPlay();
|
||||
static native void stopPlay();
|
||||
}
|
||||
109
audio-echo/app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,109 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||
android:id="@+id/mainLayout"
|
||||
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/curDelay"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginTop="192dp"
|
||||
android:layout_toRightOf="@+id/minDelayLabel"
|
||||
android:text="@string/init_delay_val_msg"
|
||||
android:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/minDelayLabel"
|
||||
android:layout_gravity="start"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_below="@+id/curDelay"
|
||||
android:layout_marginTop="0dp"
|
||||
android:text="@string/min_delay_label_msg"
|
||||
android:visibility="visible" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/delaySeekBar"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@+id/minDelayLabel"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_toRightOf="@+id/minDelayLabel"
|
||||
android:maxHeight="3dp"
|
||||
android:minHeight="3dp"
|
||||
android:max="10"
|
||||
android:progress="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/curDecay"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/minDelayLabel"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_toRightOf="@+id/minDecayLabel"
|
||||
android:text="@string/init_decay_val_msg"
|
||||
android:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/minDecayLabel"
|
||||
android:layout_gravity="start"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_below="@+id/curDecay"
|
||||
android:layout_marginTop="0dp"
|
||||
android:text="@string/min_decay_label_msg"
|
||||
android:visibility="visible" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/decaySeekBar"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@+id/minDecayLabel"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_toRightOf="@+id/minDecayLabel"
|
||||
android:maxHeight="3dp"
|
||||
android:minHeight="3dp"
|
||||
android:max="10"
|
||||
android:progress="1" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/capture_control_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/decaySeekBar"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="30dp"
|
||||
android:onClick="onEchoClick"
|
||||
android:text="@string/cmd_start_echo"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/get_parameter_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@+id/statusView"
|
||||
android:layout_alignParentStart="true"
|
||||
android:onClick="getLowLatencyParameters"
|
||||
android:text="@string/cmd_get_param"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<TextView android:text="@string/init_status_msg"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:lines="3"
|
||||
android:id="@+id/statusView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_alignParentBottom="true"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
5
audio-echo/app/src/main/res/menu/menu_main.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
|
||||
<item android:id="@+id/action_settings" android:title="@string/action_settings"
|
||||
android:orderInCategory="100" android:showAsAction="never" />
|
||||
</menu>
|
||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
5
audio-echo/app/src/main/res/values-v21/styles.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="android:Theme.Material.Light">
|
||||
</style>
|
||||
</resources>
|
||||
6
audio-echo/app/src/main/res/values-w820dp/dimens.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||
(such as screen margins) for screens with more than 820dp of available width. This
|
||||
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||
</resources>
|
||||
5
audio-echo/app/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
||||
24
audio-echo/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<resources>
|
||||
<string name="app_name">audio-echo</string>
|
||||
<string name="init_status_msg">Starting Up</string>
|
||||
<string name="request_permission_status_msg">"Requesting RECORD_AUDIO Permission..."</string>
|
||||
<string name="echoing_status_msg">Engine Echoing...</string>
|
||||
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="cmd_start_echo">Start Echo</string>
|
||||
<string name="cmd_stop_echo">Stop Echo</string>
|
||||
<string name="cmd_get_param">FastPathInfo</string>
|
||||
<string name="fast_audio_info_msg">nativeSampleRate = %1$s\nnativeSampleBufSize = %2$s\n</string>
|
||||
|
||||
<string name="player_error_msg">Failed to Create Audio Player</string>
|
||||
<string name="recorder_error_msg">Failed to Create Audio Recorder</string>
|
||||
<string name="mic_error_msg">"Audio recording is not supported"</string>
|
||||
<string name="permission_prompt_msg">"This sample needs RECORD_AUDIO permission"</string>
|
||||
<string name="permission_granted_msg">RECORD_AUDIO permission granted, touch %1$s to begin</string>
|
||||
<string name="permission_error_msg">"Permission for RECORD_AUDIO was denied"</string>
|
||||
<string name="min_delay_label_msg">delay(seconds)</string>
|
||||
<string name="init_delay_val_msg">0.1</string>
|
||||
|
||||
<string name="min_decay_label_msg">decay(decimal)</string>
|
||||
<string name="init_decay_val_msg">0.1</string>
|
||||
</resources>
|
||||
8
audio-echo/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
21
audio-echo/build.gradle
Normal file
@@ -0,0 +1,21 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.2.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
20
audio-echo/gradle.properties
Normal file
@@ -0,0 +1,20 @@
|
||||
# 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.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
# 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
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
BIN
audio-echo/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
@@ -1,6 +1,5 @@
|
||||
#Mon May 06 16:21:05 PDT 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
2
gradlew → audio-echo/gradlew
vendored
@@ -82,7 +82,6 @@ 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
|
||||
@@ -130,7 +129,6 @@ fi
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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
|
||||
100
audio-echo/gradlew.bat
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@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="-Xmx64m" "-Xms64m"
|
||||
|
||||
@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
|
||||
1
audio-echo/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
include ':app'
|
||||
1
base/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,32 +0,0 @@
|
||||
plugins {
|
||||
id("ndksamples.android.library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.android.ndk.samples.base"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = file("src/main/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
prefabPublishing = true
|
||||
}
|
||||
|
||||
prefab {
|
||||
create("base") {
|
||||
headers = "src/main/cpp/include"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation(libs.appcompat)
|
||||
implementation(libs.material)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.ext.junit)
|
||||
androidTestImplementation(libs.espresso.core)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
project(Base LANGUAGES CXX)
|
||||
|
||||
include(AppLibrary)
|
||||
|
||||
add_app_library(base
|
||||
STATIC
|
||||
logging.cpp
|
||||
)
|
||||
|
||||
target_compile_features(base PRIVATE cxx_std_23)
|
||||
target_compile_options(base PRIVATE -Wno-vla-cxx-extension)
|
||||
target_include_directories(base PUBLIC include)
|
||||
target_link_libraries(base PUBLIC log)
|
||||
@@ -1,483 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file logging.h
|
||||
* @brief Logging and assertion utilities.
|
||||
*
|
||||
* This is a modified version of AOSP's android-base/logging.h:
|
||||
* https://cs.android.com/android/platform/superproject/main/+/main:system/libbase/include/android-base/logging.h
|
||||
*
|
||||
* The original file contained a lot of dependencies for things we don't need
|
||||
* (kernel logging, support for non-Android platforms, non-logd loggers, etc).
|
||||
* That's all been removed so we don't need to pull in those dependencies.
|
||||
*
|
||||
* If you copy from this sample, you may want to replace this with something
|
||||
* like absl, which provides a very similar (if not identical) interface for all
|
||||
* platforms. absl was not used here because we didn't need much, and it was
|
||||
* preferable to avoid an additional dependency.
|
||||
*/
|
||||
|
||||
//
|
||||
// Google-style C++ logging.
|
||||
//
|
||||
|
||||
// This header provides a C++ stream interface to logging.
|
||||
//
|
||||
// To log:
|
||||
//
|
||||
// LOG(INFO) << "Some text; " << some_value;
|
||||
//
|
||||
// Replace `INFO` with any severity from `enum LogSeverity`.
|
||||
// Most devices filter out VERBOSE logs by default, run
|
||||
// `adb shell setprop log.tag.<TAG> V` to see them in adb logcat.
|
||||
//
|
||||
// To log the result of a failed function and include the string
|
||||
// representation of `errno` at the end:
|
||||
//
|
||||
// PLOG(ERROR) << "Write failed";
|
||||
//
|
||||
// The output will be something like `Write failed: I/O error`.
|
||||
// Remember this as 'P' as in perror(3).
|
||||
//
|
||||
// To output your own types, simply implement operator<< as normal.
|
||||
//
|
||||
// By default, output goes to logcat on Android and stderr on the host.
|
||||
// A process can use `SetLogger` to decide where all logging goes.
|
||||
// Implementations are provided for logcat, stderr, and dmesg.
|
||||
//
|
||||
// By default, the process' name is used as the log tag.
|
||||
// Code can choose a specific log tag by defining LOG_TAG
|
||||
// before including this header.
|
||||
|
||||
// This header also provides assertions:
|
||||
//
|
||||
// CHECK(must_be_true);
|
||||
// CHECK_EQ(a, b) << z_is_interesting_too;
|
||||
|
||||
#include <base/errno_restorer.h>
|
||||
#include <base/macros.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string_view>
|
||||
|
||||
// Note: DO NOT USE DIRECTLY. Use LOG_TAG instead.
|
||||
#ifdef _LOG_TAG_INTERNAL
|
||||
#error "_LOG_TAG_INTERNAL must not be defined"
|
||||
#endif
|
||||
#ifdef LOG_TAG
|
||||
#define _LOG_TAG_INTERNAL LOG_TAG
|
||||
#else
|
||||
#define _LOG_TAG_INTERNAL nullptr
|
||||
#endif
|
||||
|
||||
namespace ndksamples::base {
|
||||
|
||||
enum LogSeverity {
|
||||
VERBOSE,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR,
|
||||
FATAL_WITHOUT_ABORT, // For loggability tests, this is considered identical
|
||||
// to FATAL.
|
||||
FATAL,
|
||||
};
|
||||
|
||||
enum LogId {
|
||||
DEFAULT,
|
||||
MAIN,
|
||||
SYSTEM,
|
||||
RADIO,
|
||||
CRASH,
|
||||
};
|
||||
|
||||
using LogFunction = std::function<void(
|
||||
LogId /*log_buffer_id*/, LogSeverity /*severity*/, const char* /*tag*/,
|
||||
const char* /*file*/, unsigned int /*line*/, const char* /*message*/)>;
|
||||
using AbortFunction = std::function<void(const char* /*abort_message*/)>;
|
||||
|
||||
void DefaultAborter(const char* abort_message);
|
||||
|
||||
void SetDefaultTag(const std::string& tag);
|
||||
|
||||
// The LogdLogger sends chunks of up to ~4000 bytes at a time to logd. It does
|
||||
// not prevent other threads from writing to logd between sending each chunk, so
|
||||
// other threads may interleave their messages. If preventing interleaving is
|
||||
// required, then a custom logger that takes a lock before calling this logger
|
||||
// should be provided.
|
||||
class LogdLogger {
|
||||
public:
|
||||
explicit LogdLogger(LogId default_log_id = ndksamples::base::MAIN);
|
||||
|
||||
void operator()(LogId, LogSeverity, const char* tag, const char* file,
|
||||
unsigned int line, const char* message);
|
||||
|
||||
private:
|
||||
LogId default_log_id_;
|
||||
};
|
||||
|
||||
// Configure logging based on ANDROID_LOG_TAGS environment variable.
|
||||
// We need to parse a string that looks like
|
||||
//
|
||||
// *:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i
|
||||
//
|
||||
// The tag (or '*' for the global level) comes first, followed by a colon and a
|
||||
// letter indicating the minimum priority level we're expected to log. This can
|
||||
// be used to reveal or conceal logs with specific tags.
|
||||
#define INIT_LOGGING_DEFAULT_LOGGER LogdLogger()
|
||||
void InitLogging(const std::optional<std::string_view> default_tag = {},
|
||||
std::optional<LogSeverity> log_level = {},
|
||||
LogFunction&& logger = INIT_LOGGING_DEFAULT_LOGGER,
|
||||
AbortFunction&& aborter = DefaultAborter);
|
||||
#undef INIT_LOGGING_DEFAULT_LOGGER
|
||||
|
||||
// Replace the current logger and return the old one.
|
||||
LogFunction SetLogger(LogFunction&& logger);
|
||||
|
||||
// Replace the current aborter and return the old one.
|
||||
AbortFunction SetAborter(AbortFunction&& aborter);
|
||||
|
||||
// A helper macro that produces an expression that accepts both a qualified name
|
||||
// and an unqualified name for a LogSeverity, and returns a LogSeverity value.
|
||||
// Note: DO NOT USE DIRECTLY. This is an implementation detail.
|
||||
#define SEVERITY_LAMBDA(severity) \
|
||||
([&]() { \
|
||||
using ::ndksamples::base::VERBOSE; \
|
||||
using ::ndksamples::base::DEBUG; \
|
||||
using ::ndksamples::base::INFO; \
|
||||
using ::ndksamples::base::WARNING; \
|
||||
using ::ndksamples::base::ERROR; \
|
||||
using ::ndksamples::base::FATAL_WITHOUT_ABORT; \
|
||||
using ::ndksamples::base::FATAL; \
|
||||
return (severity); \
|
||||
}())
|
||||
|
||||
#define ABORT_AFTER_LOG_FATAL
|
||||
#define ABORT_AFTER_LOG_EXPR_IF(c, x) (x)
|
||||
#define MUST_LOG_MESSAGE(severity) false
|
||||
#define ABORT_AFTER_LOG_FATAL_EXPR(x) ABORT_AFTER_LOG_EXPR_IF(true, x)
|
||||
|
||||
// Defines whether the given severity will be logged or silently swallowed.
|
||||
#define WOULD_LOG(severity) \
|
||||
(UNLIKELY(::ndksamples::base::ShouldLog(SEVERITY_LAMBDA(severity), \
|
||||
_LOG_TAG_INTERNAL)) || \
|
||||
MUST_LOG_MESSAGE(severity))
|
||||
|
||||
// Get an ostream that can be used for logging at the given severity and to the
|
||||
// default destination.
|
||||
//
|
||||
// Notes:
|
||||
// 1) This will not check whether the severity is high enough. One should use
|
||||
// WOULD_LOG to filter
|
||||
// usage manually.
|
||||
// 2) This does not save and restore errno.
|
||||
#define LOG_STREAM(severity) \
|
||||
::ndksamples::base::LogMessage( \
|
||||
__FILE__, __LINE__, SEVERITY_LAMBDA(severity), _LOG_TAG_INTERNAL, -1) \
|
||||
.stream()
|
||||
|
||||
// Logs a message to logcat on Android otherwise to stderr. If the severity is
|
||||
// FATAL it also causes an abort. For example:
|
||||
//
|
||||
// LOG(FATAL) << "We didn't expect to reach here";
|
||||
#define LOG(severity) LOGGING_PREAMBLE(severity) && LOG_STREAM(severity)
|
||||
|
||||
// Checks if we want to log something, and sets up appropriate RAII objects if
|
||||
// so.
|
||||
// Note: DO NOT USE DIRECTLY. This is an implementation detail.
|
||||
#define LOGGING_PREAMBLE(severity) \
|
||||
(WOULD_LOG(severity) && \
|
||||
ABORT_AFTER_LOG_EXPR_IF( \
|
||||
(SEVERITY_LAMBDA(severity)) == ::ndksamples::base::FATAL, true) && \
|
||||
::ndksamples::base::ErrnoRestorer())
|
||||
|
||||
// A variant of LOG that also logs the current errno value. To be used when
|
||||
// library calls fail.
|
||||
#define PLOG(severity) \
|
||||
LOGGING_PREAMBLE(severity) && \
|
||||
::ndksamples::base::LogMessage(__FILE__, __LINE__, \
|
||||
SEVERITY_LAMBDA(severity), \
|
||||
_LOG_TAG_INTERNAL, errno) \
|
||||
.stream()
|
||||
|
||||
// Marker that code is yet to be implemented.
|
||||
#define UNIMPLEMENTED(level) \
|
||||
LOG(level) << __PRETTY_FUNCTION__ << " unimplemented "
|
||||
|
||||
// Check whether condition x holds and LOG(FATAL) if not. The value of the
|
||||
// expression x is only evaluated once. Extra logging can be appended using <<
|
||||
// after. For example:
|
||||
//
|
||||
// CHECK(false == true) results in a log message of
|
||||
// "Check failed: false == true".
|
||||
#define CHECK(x) \
|
||||
LIKELY((x)) || ABORT_AFTER_LOG_FATAL_EXPR(false) || \
|
||||
::ndksamples::base::LogMessage(__FILE__, __LINE__, \
|
||||
::ndksamples::base::FATAL, \
|
||||
_LOG_TAG_INTERNAL, -1) \
|
||||
.stream() \
|
||||
<< "Check failed: " #x << " "
|
||||
|
||||
// clang-format off
|
||||
// Helper for CHECK_xx(x,y) macros.
|
||||
#define CHECK_OP(LHS, RHS, OP) \
|
||||
for (auto _values = ::ndksamples::base::MakeEagerEvaluator(LHS, RHS); \
|
||||
UNLIKELY(!(_values.lhs.v OP _values.rhs.v)); \
|
||||
/* empty */) \
|
||||
ABORT_AFTER_LOG_FATAL \
|
||||
::ndksamples::base::LogMessage(__FILE__, __LINE__, ::ndksamples::base::FATAL, _LOG_TAG_INTERNAL, -1) \
|
||||
.stream() \
|
||||
<< "Check failed: " << #LHS << " " << #OP << " " << #RHS << " (" #LHS "=" \
|
||||
<< ::ndksamples::base::LogNullGuard<decltype(_values.lhs.v)>::Guard(_values.lhs.v) \
|
||||
<< ", " #RHS "=" \
|
||||
<< ::ndksamples::base::LogNullGuard<decltype(_values.rhs.v)>::Guard(_values.rhs.v) \
|
||||
<< ") "
|
||||
// clang-format on
|
||||
|
||||
// Check whether a condition holds between x and y, LOG(FATAL) if not. The value
|
||||
// of the expressions x and y is evaluated once. Extra logging can be appended
|
||||
// using << after. For example:
|
||||
//
|
||||
// CHECK_NE(0 == 1, false) results in
|
||||
// "Check failed: false != false (0==1=false, false=false) ".
|
||||
#define CHECK_EQ(x, y) CHECK_OP(x, y, ==)
|
||||
#define CHECK_NE(x, y) CHECK_OP(x, y, !=)
|
||||
#define CHECK_LE(x, y) CHECK_OP(x, y, <=)
|
||||
#define CHECK_LT(x, y) CHECK_OP(x, y, <)
|
||||
#define CHECK_GE(x, y) CHECK_OP(x, y, >=)
|
||||
#define CHECK_GT(x, y) CHECK_OP(x, y, >)
|
||||
|
||||
// clang-format off
|
||||
// Helper for CHECK_STRxx(s1,s2) macros.
|
||||
#define CHECK_STROP(s1, s2, sense) \
|
||||
while (UNLIKELY((strcmp(s1, s2) == 0) != (sense))) \
|
||||
ABORT_AFTER_LOG_FATAL \
|
||||
::ndksamples::base::LogMessage(__FILE__, __LINE__, ::ndksamples::base::FATAL, \
|
||||
_LOG_TAG_INTERNAL, -1) \
|
||||
.stream() \
|
||||
<< "Check failed: " << "\"" << (s1) << "\"" \
|
||||
<< ((sense) ? " == " : " != ") << "\"" << (s2) << "\""
|
||||
// clang-format on
|
||||
|
||||
// Check for string (const char*) equality between s1 and s2, LOG(FATAL) if not.
|
||||
#define CHECK_STREQ(s1, s2) CHECK_STROP(s1, s2, true)
|
||||
#define CHECK_STRNE(s1, s2) CHECK_STROP(s1, s2, false)
|
||||
|
||||
// Perform the pthread function call(args), LOG(FATAL) on error.
|
||||
#define CHECK_PTHREAD_CALL(call, args, what) \
|
||||
do { \
|
||||
int rc = call args; \
|
||||
if (rc != 0) { \
|
||||
errno = rc; \
|
||||
ABORT_AFTER_LOG_FATAL \
|
||||
PLOG(FATAL) << #call << " failed for " << (what); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
// DCHECKs are debug variants of CHECKs only enabled in debug builds. Generally
|
||||
// CHECK should be used unless profiling identifies a CHECK as being in
|
||||
// performance critical code.
|
||||
#if defined(NDEBUG) && !defined(__clang_analyzer__)
|
||||
static constexpr bool kEnableDChecks = false;
|
||||
#else
|
||||
static constexpr bool kEnableDChecks = true;
|
||||
#endif
|
||||
|
||||
#define DCHECK(x) \
|
||||
if (::ndksamples::base::kEnableDChecks) CHECK(x)
|
||||
#define DCHECK_EQ(x, y) \
|
||||
if (::ndksamples::base::kEnableDChecks) CHECK_EQ(x, y)
|
||||
#define DCHECK_NE(x, y) \
|
||||
if (::ndksamples::base::kEnableDChecks) CHECK_NE(x, y)
|
||||
#define DCHECK_LE(x, y) \
|
||||
if (::ndksamples::base::kEnableDChecks) CHECK_LE(x, y)
|
||||
#define DCHECK_LT(x, y) \
|
||||
if (::ndksamples::base::kEnableDChecks) CHECK_LT(x, y)
|
||||
#define DCHECK_GE(x, y) \
|
||||
if (::ndksamples::base::kEnableDChecks) CHECK_GE(x, y)
|
||||
#define DCHECK_GT(x, y) \
|
||||
if (::ndksamples::base::kEnableDChecks) CHECK_GT(x, y)
|
||||
#define DCHECK_STREQ(s1, s2) \
|
||||
if (::ndksamples::base::kEnableDChecks) CHECK_STREQ(s1, s2)
|
||||
#define DCHECK_STRNE(s1, s2) \
|
||||
if (::ndksamples::base::kEnableDChecks) CHECK_STRNE(s1, s2)
|
||||
|
||||
namespace log_detail {
|
||||
|
||||
// Temporary storage for a single eagerly evaluated check expression operand.
|
||||
template <typename T>
|
||||
struct Storage {
|
||||
template <typename U>
|
||||
explicit constexpr Storage(U&& u) : v(std::forward<U>(u)) {}
|
||||
explicit Storage(const Storage& t) = delete;
|
||||
explicit Storage(Storage&& t) = delete;
|
||||
T v;
|
||||
};
|
||||
|
||||
// Partial specialization for smart pointers to avoid copying.
|
||||
template <typename T>
|
||||
struct Storage<std::unique_ptr<T>> {
|
||||
explicit constexpr Storage(const std::unique_ptr<T>& ptr) : v(ptr.get()) {}
|
||||
const T* v;
|
||||
};
|
||||
template <typename T>
|
||||
struct Storage<std::shared_ptr<T>> {
|
||||
explicit constexpr Storage(const std::shared_ptr<T>& ptr) : v(ptr.get()) {}
|
||||
const T* v;
|
||||
};
|
||||
|
||||
// Type trait that checks if a type is a (potentially const) char pointer.
|
||||
template <typename T>
|
||||
struct IsCharPointer {
|
||||
using Pointee = std::remove_cv_t<std::remove_pointer_t<T>>;
|
||||
static constexpr bool value =
|
||||
std::is_pointer_v<T> &&
|
||||
(std::is_same_v<Pointee, char> || std::is_same_v<Pointee, signed char> ||
|
||||
std::is_same_v<Pointee, unsigned char>);
|
||||
};
|
||||
|
||||
// Counterpart to Storage that depends on both operands. This is used to prevent
|
||||
// char pointers being treated as strings in the log output - they might point
|
||||
// to buffers of unprintable binary data.
|
||||
template <typename LHS, typename RHS>
|
||||
struct StorageTypes {
|
||||
static constexpr bool voidptr =
|
||||
IsCharPointer<LHS>::value && IsCharPointer<RHS>::value;
|
||||
using LHSType = std::conditional_t<voidptr, const void*, LHS>;
|
||||
using RHSType = std::conditional_t<voidptr, const void*, RHS>;
|
||||
};
|
||||
|
||||
// Temporary class created to evaluate the LHS and RHS, used with
|
||||
// MakeEagerEvaluator to infer the types of LHS and RHS.
|
||||
template <typename LHS, typename RHS>
|
||||
struct EagerEvaluator {
|
||||
template <typename A, typename B>
|
||||
constexpr EagerEvaluator(A&& l, B&& r)
|
||||
: lhs(std::forward<A>(l)), rhs(std::forward<B>(r)) {}
|
||||
const Storage<typename StorageTypes<LHS, RHS>::LHSType> lhs;
|
||||
const Storage<typename StorageTypes<LHS, RHS>::RHSType> rhs;
|
||||
};
|
||||
|
||||
} // namespace log_detail
|
||||
|
||||
// Converts std::nullptr_t and null char pointers to the string "null"
|
||||
// when writing the failure message.
|
||||
template <typename T>
|
||||
struct LogNullGuard {
|
||||
static const T& Guard(const T& v) { return v; }
|
||||
};
|
||||
template <>
|
||||
struct LogNullGuard<std::nullptr_t> {
|
||||
static const char* Guard(const std::nullptr_t&) { return "(null)"; }
|
||||
};
|
||||
template <>
|
||||
struct LogNullGuard<char*> {
|
||||
static const char* Guard(const char* v) { return v ? v : "(null)"; }
|
||||
};
|
||||
template <>
|
||||
struct LogNullGuard<const char*> {
|
||||
static const char* Guard(const char* v) { return v ? v : "(null)"; }
|
||||
};
|
||||
|
||||
// Helper function for CHECK_xx.
|
||||
template <typename LHS, typename RHS>
|
||||
constexpr auto MakeEagerEvaluator(LHS&& lhs, RHS&& rhs) {
|
||||
return log_detail::EagerEvaluator<std::decay_t<LHS>, std::decay_t<RHS>>(
|
||||
std::forward<LHS>(lhs), std::forward<RHS>(rhs));
|
||||
}
|
||||
|
||||
// Data for the log message, not stored in LogMessage to avoid increasing the
|
||||
// stack size.
|
||||
class LogMessageData;
|
||||
|
||||
// A LogMessage is a temporarily scoped object used by LOG and the unlikely part
|
||||
// of a CHECK. The destructor will abort if the severity is FATAL.
|
||||
class LogMessage {
|
||||
public:
|
||||
// LogId has been deprecated, but this constructor must exist for prebuilts.
|
||||
LogMessage(const char* file, unsigned int line, LogId, LogSeverity severity,
|
||||
const char* tag, int error);
|
||||
LogMessage(const char* file, unsigned int line, LogSeverity severity,
|
||||
const char* tag, int error);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(LogMessage);
|
||||
|
||||
~LogMessage();
|
||||
|
||||
// Returns the stream associated with the message, the LogMessage performs
|
||||
// output when it goes out of scope.
|
||||
std::ostream& stream();
|
||||
|
||||
// The routine that performs the actual logging.
|
||||
static void LogLine(const char* file, unsigned int line, LogSeverity severity,
|
||||
const char* tag, const char* msg);
|
||||
|
||||
private:
|
||||
const std::unique_ptr<LogMessageData> data_;
|
||||
};
|
||||
|
||||
// Get the minimum severity level for logging.
|
||||
LogSeverity GetMinimumLogSeverity();
|
||||
|
||||
// Set the minimum severity level for logging, returning the old severity.
|
||||
LogSeverity SetMinimumLogSeverity(LogSeverity new_severity);
|
||||
|
||||
// Return whether or not a log message with the associated tag should be logged.
|
||||
bool ShouldLog(LogSeverity severity, const char* tag);
|
||||
|
||||
// Allows to temporarily change the minimum severity level for logging.
|
||||
class ScopedLogSeverity {
|
||||
public:
|
||||
explicit ScopedLogSeverity(LogSeverity level);
|
||||
~ScopedLogSeverity();
|
||||
|
||||
private:
|
||||
LogSeverity old_;
|
||||
};
|
||||
|
||||
} // namespace ndksamples::base
|
||||
|
||||
namespace std { // NOLINT(cert-dcl58-cpp)
|
||||
|
||||
// Emit a warning of ostream<< with std::string*. The intention was most likely
|
||||
// to print *string.
|
||||
//
|
||||
// Note: for this to work, we need to have this in a namespace.
|
||||
// Note: using a pragma because "-Wgcc-compat" (included in "-Weverything")
|
||||
// complains about
|
||||
// diagnose_if.
|
||||
// Note: to print the pointer, use "<< static_cast<const void*>(string_pointer)"
|
||||
// instead. Note: a not-recommended alternative is to let Clang ignore the
|
||||
// warning by adding
|
||||
// -Wno-user-defined-warnings to CPPFLAGS.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgcc-compat"
|
||||
#define OSTREAM_STRING_POINTER_USAGE_WARNING \
|
||||
__attribute__(( \
|
||||
diagnose_if(true, "Unexpected logging of string pointer", "warning")))
|
||||
inline OSTREAM_STRING_POINTER_USAGE_WARNING std::ostream& operator<<(
|
||||
std::ostream& stream, const std::string* string_pointer) {
|
||||
return stream << static_cast<const void*>(string_pointer);
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace std
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h> // for size_t
|
||||
|
||||
#include <utility>
|
||||
|
||||
// A macro to disallow the copy constructor and operator= functions
|
||||
// This must be placed in the private: declarations for a class.
|
||||
//
|
||||
// For disallowing only assign or copy, delete the relevant operator or
|
||||
// constructor, for example:
|
||||
// void operator=(const TypeName&) = delete;
|
||||
// Note, that most uses of DISALLOW_ASSIGN and DISALLOW_COPY are broken
|
||||
// semantically, one should either use disallow both or neither. Try to
|
||||
// avoid these in new code.
|
||||
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&) = delete; \
|
||||
void operator=(const TypeName&) = delete
|
||||
|
||||
// A macro to disallow all the implicit constructors, namely the
|
||||
// default constructor, copy constructor and operator= functions.
|
||||
//
|
||||
// This should be used in the private: declarations for a class
|
||||
// that wants to prevent anyone from instantiating it. This is
|
||||
// especially useful for classes containing only static methods.
|
||||
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
|
||||
TypeName() = delete; \
|
||||
DISALLOW_COPY_AND_ASSIGN(TypeName)
|
||||
|
||||
// The arraysize(arr) macro returns the # of elements in an array arr.
|
||||
// The expression is a compile-time constant, and therefore can be
|
||||
// used in defining new arrays, for example. If you use arraysize on
|
||||
// a pointer by mistake, you will get a compile-time error.
|
||||
//
|
||||
// One caveat is that arraysize() doesn't accept any array of an
|
||||
// anonymous type or a type defined inside a function. In these rare
|
||||
// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is
|
||||
// due to a limitation in C++'s template system. The limitation might
|
||||
// eventually be removed, but it hasn't happened yet.
|
||||
|
||||
// This template function declaration is used in defining arraysize.
|
||||
// Note that the function doesn't need an implementation, as we only
|
||||
// use its type.
|
||||
template <typename T, size_t N>
|
||||
char (&ArraySizeHelper(T (&array)[N]))[N]; // NOLINT(readability/casting)
|
||||
|
||||
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
|
||||
|
||||
#define SIZEOF_MEMBER(t, f) sizeof(std::declval<t>().f)
|
||||
|
||||
// Changing this definition will cause you a lot of pain. A majority of
|
||||
// vendor code defines LIKELY and UNLIKELY this way, and includes
|
||||
// this header through an indirect path.
|
||||
#define LIKELY(exp) (__builtin_expect((exp) != 0, true))
|
||||
#define UNLIKELY(exp) (__builtin_expect((exp) != 0, false))
|
||||
|
||||
/// True if the (runtime) version of the OS is at least x.
|
||||
///
|
||||
/// Clang is very particular about how __builtin_available is used. Logical
|
||||
/// operations (including negation) may not be combined with
|
||||
/// __builtin_available, so to negate this check you must do:
|
||||
///
|
||||
/// if (API_AT_LEAST(x)) {
|
||||
/// } else {
|
||||
/// // do negated stuff
|
||||
/// }
|
||||
#define API_AT_LEAST(x) __builtin_available(android x, *)
|
||||
|
||||
/// Marks a function as not callable on OS versions older than x.
|
||||
///
|
||||
/// This is a minor abuse of Clang's __attribute__((availability)), so the
|
||||
/// diagnostic for this will be a little odd, but it allows us to extract
|
||||
/// functions from code that already has an API_AT_LEAST guard without rewriting
|
||||
/// the guard in every called function.
|
||||
#define REQUIRES_API(x) __INTRODUCED_IN(x)
|
||||
@@ -1,294 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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 "base/logging.h"
|
||||
|
||||
#include <android/log.h>
|
||||
#include <android/set_abort_message.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <libgen.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "logging_splitters.h"
|
||||
|
||||
namespace ndksamples::base {
|
||||
|
||||
static const char* GetFileBasename(const char* file) {
|
||||
// We can't use basename(3) even on Unix because the Mac doesn't
|
||||
// have a non-modifying basename.
|
||||
const char* last_slash = strrchr(file, '/');
|
||||
if (last_slash != nullptr) {
|
||||
return last_slash + 1;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
static int32_t LogIdTolog_id_t(LogId log_id) {
|
||||
switch (log_id) {
|
||||
case MAIN:
|
||||
return LOG_ID_MAIN;
|
||||
case SYSTEM:
|
||||
return LOG_ID_SYSTEM;
|
||||
case RADIO:
|
||||
return LOG_ID_RADIO;
|
||||
case CRASH:
|
||||
return LOG_ID_CRASH;
|
||||
case DEFAULT:
|
||||
default:
|
||||
return LOG_ID_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t LogSeverityToPriority(LogSeverity severity) {
|
||||
switch (severity) {
|
||||
case VERBOSE:
|
||||
return ANDROID_LOG_VERBOSE;
|
||||
case DEBUG:
|
||||
return ANDROID_LOG_DEBUG;
|
||||
case INFO:
|
||||
return ANDROID_LOG_INFO;
|
||||
case WARNING:
|
||||
return ANDROID_LOG_WARN;
|
||||
case ERROR:
|
||||
return ANDROID_LOG_ERROR;
|
||||
case FATAL_WITHOUT_ABORT:
|
||||
case FATAL:
|
||||
default:
|
||||
return ANDROID_LOG_FATAL;
|
||||
}
|
||||
}
|
||||
|
||||
static LogFunction& Logger() {
|
||||
static auto& logger = *new LogFunction(LogdLogger());
|
||||
return logger;
|
||||
}
|
||||
|
||||
static AbortFunction& Aborter() {
|
||||
static auto& aborter = *new AbortFunction(DefaultAborter);
|
||||
return aborter;
|
||||
}
|
||||
|
||||
// Only used for Q fallback.
|
||||
static std::recursive_mutex& TagLock() {
|
||||
static auto& tag_lock = *new std::recursive_mutex();
|
||||
return tag_lock;
|
||||
}
|
||||
|
||||
static std::string* gDefaultTag;
|
||||
|
||||
void SetDefaultTag(const std::string_view tag) {
|
||||
std::lock_guard<std::recursive_mutex> lock(TagLock());
|
||||
if (gDefaultTag != nullptr) {
|
||||
delete gDefaultTag;
|
||||
gDefaultTag = nullptr;
|
||||
}
|
||||
if (!tag.empty()) {
|
||||
gDefaultTag = new std::string(tag);
|
||||
}
|
||||
}
|
||||
|
||||
static bool gInitialized = false;
|
||||
|
||||
// Only used for Q fallback.
|
||||
static LogSeverity gMinimumLogSeverity = INFO;
|
||||
|
||||
void DefaultAborter(const char* abort_message) {
|
||||
android_set_abort_message(abort_message);
|
||||
abort();
|
||||
}
|
||||
|
||||
static void LogdLogChunk(LogId id, LogSeverity severity, const char* tag,
|
||||
const char* message) {
|
||||
int32_t lg_id = LogIdTolog_id_t(id);
|
||||
int32_t priority = LogSeverityToPriority(severity);
|
||||
|
||||
__android_log_buf_print(lg_id, priority, tag, "%s", message);
|
||||
}
|
||||
|
||||
LogdLogger::LogdLogger(LogId default_log_id)
|
||||
: default_log_id_(default_log_id) {}
|
||||
|
||||
void LogdLogger::operator()(LogId id, LogSeverity severity, const char* tag,
|
||||
const char* file, unsigned int line,
|
||||
const char* message) {
|
||||
if (id == DEFAULT) {
|
||||
id = default_log_id_;
|
||||
}
|
||||
|
||||
SplitByLogdChunks(id, severity, tag, file, line, message, LogdLogChunk);
|
||||
}
|
||||
|
||||
void InitLogging(const std::optional<std::string_view> default_tag,
|
||||
std::optional<LogSeverity> log_level, LogFunction&& logger,
|
||||
AbortFunction&& aborter) {
|
||||
SetLogger(std::forward<LogFunction>(logger));
|
||||
SetAborter(std::forward<AbortFunction>(aborter));
|
||||
|
||||
if (gInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
gInitialized = true;
|
||||
|
||||
if (default_tag.has_value()) {
|
||||
SetDefaultTag(default_tag.value());
|
||||
}
|
||||
|
||||
const char* tags = getenv("ANDROID_LOG_TAGS");
|
||||
if (tags == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (log_level.has_value()) {
|
||||
SetMinimumLogSeverity(log_level.value());
|
||||
}
|
||||
}
|
||||
|
||||
LogFunction SetLogger(LogFunction&& logger) {
|
||||
LogFunction old_logger = std::move(Logger());
|
||||
Logger() = std::move(logger);
|
||||
return old_logger;
|
||||
}
|
||||
|
||||
AbortFunction SetAborter(AbortFunction&& aborter) {
|
||||
AbortFunction old_aborter = std::move(Aborter());
|
||||
Aborter() = std::move(aborter);
|
||||
return old_aborter;
|
||||
}
|
||||
|
||||
// This indirection greatly reduces the stack impact of having lots of
|
||||
// checks/logging in a function.
|
||||
class LogMessageData {
|
||||
public:
|
||||
LogMessageData(const char* file, unsigned int line, LogSeverity severity,
|
||||
const char* tag, int error)
|
||||
: file_(GetFileBasename(file)),
|
||||
line_number_(line),
|
||||
severity_(severity),
|
||||
tag_(tag),
|
||||
error_(error) {}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(LogMessageData);
|
||||
|
||||
const char* GetFile() const { return file_; }
|
||||
|
||||
unsigned int GetLineNumber() const { return line_number_; }
|
||||
|
||||
LogSeverity GetSeverity() const { return severity_; }
|
||||
|
||||
const char* GetTag() const { return tag_; }
|
||||
|
||||
int GetError() const { return error_; }
|
||||
|
||||
std::ostream& GetBuffer() { return buffer_; }
|
||||
|
||||
std::string ToString() const { return buffer_.str(); }
|
||||
|
||||
private:
|
||||
std::ostringstream buffer_;
|
||||
const char* const file_;
|
||||
const unsigned int line_number_;
|
||||
const LogSeverity severity_;
|
||||
const char* const tag_;
|
||||
const int error_;
|
||||
};
|
||||
|
||||
LogMessage::LogMessage(const char* file, unsigned int line, LogId,
|
||||
LogSeverity severity, const char* tag, int error)
|
||||
: LogMessage(file, line, severity, tag, error) {}
|
||||
|
||||
LogMessage::LogMessage(const char* file, unsigned int line,
|
||||
LogSeverity severity, const char* tag, int error)
|
||||
: data_(new LogMessageData(file, line, severity, tag, error)) {}
|
||||
|
||||
LogMessage::~LogMessage() {
|
||||
// Check severity again. This is duplicate work wrt/ LOG macros, but not
|
||||
// LOG_STREAM.
|
||||
if (!WOULD_LOG(data_->GetSeverity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Finish constructing the message.
|
||||
if (data_->GetError() != -1) {
|
||||
data_->GetBuffer() << ": " << strerror(data_->GetError());
|
||||
}
|
||||
std::string msg(data_->ToString());
|
||||
|
||||
if (data_->GetSeverity() == FATAL) {
|
||||
// Set the bionic abort message early to avoid liblog doing it
|
||||
// with the individual lines, so that we get the whole message.
|
||||
android_set_abort_message(msg.c_str());
|
||||
}
|
||||
|
||||
LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetSeverity(),
|
||||
data_->GetTag(), msg.c_str());
|
||||
|
||||
// Abort if necessary.
|
||||
if (data_->GetSeverity() == FATAL) {
|
||||
Aborter()(msg.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& LogMessage::stream() { return data_->GetBuffer(); }
|
||||
|
||||
void LogMessage::LogLine(const char* file, unsigned int line,
|
||||
LogSeverity severity, const char* tag,
|
||||
const char* message) {
|
||||
if (tag == nullptr) {
|
||||
std::lock_guard<std::recursive_mutex> lock(TagLock());
|
||||
if (gDefaultTag == nullptr) {
|
||||
gDefaultTag = new std::string(getprogname());
|
||||
}
|
||||
|
||||
Logger()(DEFAULT, severity, gDefaultTag->c_str(), file, line, message);
|
||||
} else {
|
||||
Logger()(DEFAULT, severity, tag, file, line, message);
|
||||
}
|
||||
}
|
||||
|
||||
LogSeverity GetMinimumLogSeverity() { return gMinimumLogSeverity; }
|
||||
|
||||
bool ShouldLog(LogSeverity severity, const char*) {
|
||||
return severity >= gMinimumLogSeverity;
|
||||
}
|
||||
|
||||
LogSeverity SetMinimumLogSeverity(LogSeverity new_severity) {
|
||||
LogSeverity old_severity = gMinimumLogSeverity;
|
||||
gMinimumLogSeverity = new_severity;
|
||||
return old_severity;
|
||||
}
|
||||
|
||||
ScopedLogSeverity::ScopedLogSeverity(LogSeverity new_severity) {
|
||||
old_ = SetMinimumLogSeverity(new_severity);
|
||||
}
|
||||
|
||||
ScopedLogSeverity::~ScopedLogSeverity() { SetMinimumLogSeverity(old_); }
|
||||
|
||||
} // namespace ndksamples::base
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <format>
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
#define LOGGER_ENTRY_MAX_PAYLOAD 4068 // This constant is not in the NDK.
|
||||
|
||||
namespace ndksamples::base {
|
||||
|
||||
// This splits the message up line by line, by calling log_function with a
|
||||
// pointer to the start of each line and the size up to the newline character.
|
||||
// It sends size = -1 for the final line.
|
||||
template <typename F, typename... Args>
|
||||
static void SplitByLines(const char* msg, const F& log_function,
|
||||
Args&&... args) {
|
||||
const char* newline = strchr(msg, '\n');
|
||||
while (newline != nullptr) {
|
||||
log_function(msg, newline - msg, args...);
|
||||
msg = newline + 1;
|
||||
newline = strchr(msg, '\n');
|
||||
}
|
||||
|
||||
log_function(msg, -1, args...);
|
||||
}
|
||||
|
||||
// This splits the message up into chunks that logs can process delimited by new
|
||||
// lines. It calls log_function with the exact null terminated message that
|
||||
// should be sent to logd. Note, despite the loops and snprintf's, if severity
|
||||
// is not fatal and there are no new lines, this function simply calls
|
||||
// log_function with msg without any extra overhead.
|
||||
template <typename F>
|
||||
static void SplitByLogdChunks(LogId log_id, LogSeverity severity,
|
||||
const char* tag, const char* file,
|
||||
unsigned int line, const char* msg,
|
||||
const F& log_function) {
|
||||
// The maximum size of a payload, after the log header that logd will accept
|
||||
// is LOGGER_ENTRY_MAX_PAYLOAD, so subtract the other elements in the payload
|
||||
// to find the size of the string that we can log in each pass. The protocol
|
||||
// is documented in liblog/README.protocol.md. Specifically we subtract a byte
|
||||
// for the priority, the length of the tag + its null terminator, and an
|
||||
// additional byte for the null terminator on the payload. We subtract an
|
||||
// additional 32 bytes for slack, similar to java/android/util/Log.java.
|
||||
ptrdiff_t max_size = LOGGER_ENTRY_MAX_PAYLOAD - strlen(tag) - 35;
|
||||
if (max_size <= 0) {
|
||||
abort();
|
||||
}
|
||||
// If we're logging a fatal message, we'll append the file and line numbers.
|
||||
bool add_file =
|
||||
file != nullptr && (severity == FATAL || severity == FATAL_WITHOUT_ABORT);
|
||||
|
||||
std::string file_header;
|
||||
if (add_file) {
|
||||
file_header = std::format("{}:{}]", file, line);
|
||||
}
|
||||
int file_header_size = file_header.size();
|
||||
|
||||
__attribute__((uninitialized)) char logd_chunk[max_size + 1];
|
||||
ptrdiff_t chunk_position = 0;
|
||||
|
||||
auto call_log_function = [&]() {
|
||||
log_function(log_id, severity, tag, logd_chunk);
|
||||
chunk_position = 0;
|
||||
};
|
||||
|
||||
auto write_to_logd_chunk = [&](const char* message, int length) {
|
||||
int size_written = 0;
|
||||
const char* new_line = chunk_position > 0 ? "\n" : "";
|
||||
if (add_file) {
|
||||
size_written = snprintf(logd_chunk + chunk_position,
|
||||
sizeof(logd_chunk) - chunk_position, "%s%s%.*s",
|
||||
new_line, file_header.c_str(), length, message);
|
||||
} else {
|
||||
size_written = snprintf(logd_chunk + chunk_position,
|
||||
sizeof(logd_chunk) - chunk_position, "%s%.*s",
|
||||
new_line, length, message);
|
||||
}
|
||||
|
||||
// This should never fail, if it does and we set size_written to 0, which
|
||||
// will skip this line and move to the next one.
|
||||
if (size_written < 0) {
|
||||
size_written = 0;
|
||||
}
|
||||
chunk_position += size_written;
|
||||
};
|
||||
|
||||
const char* newline = strchr(msg, '\n');
|
||||
while (newline != nullptr) {
|
||||
// If we have data in the buffer and this next line doesn't fit, write the
|
||||
// buffer.
|
||||
if (chunk_position != 0 &&
|
||||
chunk_position + (newline - msg) + 1 + file_header_size > max_size) {
|
||||
call_log_function();
|
||||
}
|
||||
|
||||
// Otherwise, either the next line fits or we have any empty buffer and too
|
||||
// large of a line to ever fit, in both cases, we add it to the buffer and
|
||||
// continue.
|
||||
write_to_logd_chunk(msg, newline - msg);
|
||||
|
||||
msg = newline + 1;
|
||||
newline = strchr(msg, '\n');
|
||||
}
|
||||
|
||||
// If we have left over data in the buffer and we can fit the rest of msg, add
|
||||
// it to the buffer then write the buffer.
|
||||
if (chunk_position != 0 &&
|
||||
chunk_position + static_cast<int>(strlen(msg)) + 1 + file_header_size <=
|
||||
max_size) {
|
||||
write_to_logd_chunk(msg, -1);
|
||||
call_log_function();
|
||||
} else {
|
||||
// If the buffer is not empty and we can't fit the rest of msg into it,
|
||||
// write its contents.
|
||||
if (chunk_position != 0) {
|
||||
call_log_function();
|
||||
}
|
||||
// Then write the rest of the msg.
|
||||
if (add_file) {
|
||||
snprintf(logd_chunk, sizeof(logd_chunk), "%s%s", file_header.c_str(),
|
||||
msg);
|
||||
log_function(log_id, severity, tag, logd_chunk);
|
||||
} else {
|
||||
log_function(log_id, severity, tag, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ndksamples::base
|
||||
7
bitmap-plasma/.google/packaging.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
status: PUBLISHED
|
||||
technologies: [Android, NDK]
|
||||
categories: [NDK]
|
||||
languages: [C++, Java]
|
||||
solutions: [Mobile]
|
||||
github: googlesamples/android-ndk
|
||||
license: apache2
|
||||
@@ -1,10 +1,52 @@
|
||||
# Bitmap Plasma
|
||||
Bitmap Plasma
|
||||
=============
|
||||
Bitmap Plasma is an Android sample that uses JNI to render a plasma effect in an Android [Bitmap](http://developer.android.com/reference/android/graphics/Bitmap.html) from C code.
|
||||
|
||||
Bitmap Plasma is an Android sample that uses JNI to render a plasma effect in an
|
||||
Android
|
||||
[Bitmap](http://developer.android.com/reference/android/graphics/Bitmap.html)
|
||||
from C code.
|
||||
This sample uses the new [Android Studio CMake plugin](http://tools.android.com/tech-docs/external-c-builds) with C++ support.
|
||||
|
||||
## Screenshots
|
||||
Pre-requisites
|
||||
--------------
|
||||
- Android Studio 2.2+ with [NDK](https://developer.android.com/ndk/) bundle.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
1. [Download Android Studio](http://developer.android.com/sdk/index.html)
|
||||
1. Launch Android Studio.
|
||||
1. Open the sample directory.
|
||||
1. Open *File/Project Structure...*
|
||||
- Click *Download* or *Select NDK location*.
|
||||
1. Click *Tools/Android/Sync Project with Gradle Files*.
|
||||
1. Click *Run/Run 'app'*.
|
||||
|
||||
Screenshots
|
||||
-----------
|
||||

|
||||
|
||||
Support
|
||||
-------
|
||||
If you've found an error in these samples, please [file an issue](https://github.com/googlesamples/android-ndk/issues/new).
|
||||
|
||||
Patches are encouraged, and may be submitted by [forking this project](https://github.com/googlesamples/android-ndk/fork) and
|
||||
submitting a pull request through GitHub. Please see [CONTRIBUTING.md](../CONTRIBUTING.md) for more details.
|
||||
|
||||
- [Stack Overflow](http://stackoverflow.com/questions/tagged/android-ndk)
|
||||
- [Android Tools Feedbacks](http://tools.android.com/feedback)
|
||||
|
||||
License
|
||||
-------
|
||||
Copyright 2015 Google, Inc.
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||
license agreements. See the NOTICE file distributed with this work for
|
||||
additional information regarding copyright ownership. The ASF licenses this
|
||||
file to you 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.
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
plugins {
|
||||
id "ndksamples.android.application"
|
||||
}
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
namespace 'com.example.plasma'
|
||||
defaultConfig {
|
||||
compileSdkVersion 29
|
||||
ndkVersion '21.2.6472646'
|
||||
|
||||
defaultConfig.with {
|
||||
applicationId 'com.example.plasma'
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 28
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'),
|
||||
'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version '3.18.1'
|
||||
path 'src/main/cpp/CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
prefab true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":base")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="1"
|
||||
package="com.example.plasma"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
@@ -8,8 +9,7 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name">
|
||||
<activity android:name=".Plasma"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true">
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
project(BitmapPlasma LANGUAGES CXX)
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
include(AppLibrary)
|
||||
find_package(base CONFIG REQUIRED)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wno-unused-function")
|
||||
|
||||
add_app_library(plasma SHARED jni.cpp plasma.cpp)
|
||||
add_library(plasma SHARED
|
||||
plasma.c)
|
||||
|
||||
# Include libraries needed for plasma lib
|
||||
target_link_libraries(plasma
|
||||
base::base
|
||||
android
|
||||
jnigraphics
|
||||
log
|
||||
m
|
||||
)
|
||||
android
|
||||
jnigraphics
|
||||
log
|
||||
m)
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (C) 2025 The Android Open Source Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include <base/macros.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include "plasma.h"
|
||||
|
||||
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* _Nonnull vm, void* _Nullable) {
|
||||
JNIEnv* env;
|
||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
jclass c = env->FindClass("com/example/plasma/PlasmaView");
|
||||
if (c == nullptr) return JNI_ERR;
|
||||
|
||||
static const JNINativeMethod methods[] = {
|
||||
{"renderPlasma", "(Landroid/graphics/Bitmap;J)V",
|
||||
reinterpret_cast<void*>(RenderPlasma)},
|
||||
};
|
||||
int rc = env->RegisterNatives(c, methods, arraysize(methods));
|
||||
if (rc != JNI_OK) return rc;
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
LIBPLASMA {
|
||||
global:
|
||||
JNI_OnLoad;
|
||||
local:
|
||||
*;
|
||||
};
|
||||
399
bitmap-plasma/app/src/main/cpp/plasma.c
Normal file
@@ -0,0 +1,399 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include <time.h>
|
||||
#include <android/log.h>
|
||||
#include <android/bitmap.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#define LOG_TAG "libplasma"
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
|
||||
|
||||
/* Set to 1 to enable debug log traces. */
|
||||
#define DEBUG 0
|
||||
|
||||
/* Set to 1 to optimize memory stores when generating plasma. */
|
||||
#define OPTIMIZE_WRITES 1
|
||||
|
||||
/* Return current time in milliseconds */
|
||||
static double now_ms(void)
|
||||
{
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return tv.tv_sec*1000. + tv.tv_usec/1000.;
|
||||
}
|
||||
|
||||
/* We're going to perform computations for every pixel of the target
|
||||
* bitmap. floating-point operations are very slow on ARMv5, and not
|
||||
* too bad on ARMv7 with the exception of trigonometric functions.
|
||||
*
|
||||
* For better performance on all platforms, we're going to use fixed-point
|
||||
* arithmetic and all kinds of tricks
|
||||
*/
|
||||
|
||||
typedef int32_t Fixed;
|
||||
|
||||
#define FIXED_BITS 16
|
||||
#define FIXED_ONE (1 << FIXED_BITS)
|
||||
#define FIXED_AVERAGE(x,y) (((x) + (y)) >> 1)
|
||||
|
||||
#define FIXED_FROM_INT(x) ((x) << FIXED_BITS)
|
||||
#define FIXED_TO_INT(x) ((x) >> FIXED_BITS)
|
||||
|
||||
#define FIXED_FROM_FLOAT(x) ((Fixed)((x)*FIXED_ONE))
|
||||
#define FIXED_TO_FLOAT(x) ((x)/(1.*FIXED_ONE))
|
||||
|
||||
#define FIXED_MUL(x,y) (((int64_t)(x) * (y)) >> FIXED_BITS)
|
||||
#define FIXED_DIV(x,y) (((int64_t)(x) * FIXED_ONE) / (y))
|
||||
|
||||
#define FIXED_DIV2(x) ((x) >> 1)
|
||||
#define FIXED_AVERAGE(x,y) (((x) + (y)) >> 1)
|
||||
|
||||
#define FIXED_FRAC(x) ((x) & ((1 << FIXED_BITS)-1))
|
||||
#define FIXED_TRUNC(x) ((x) & ~((1 << FIXED_BITS)-1))
|
||||
|
||||
#define FIXED_FROM_INT_FLOAT(x,f) (Fixed)((x)*(FIXED_ONE*(f)))
|
||||
|
||||
typedef int32_t Angle;
|
||||
|
||||
#define ANGLE_BITS 9
|
||||
|
||||
#if ANGLE_BITS < 8
|
||||
# error ANGLE_BITS must be at least 8
|
||||
#endif
|
||||
|
||||
#define ANGLE_2PI (1 << ANGLE_BITS)
|
||||
#define ANGLE_PI (1 << (ANGLE_BITS-1))
|
||||
#define ANGLE_PI2 (1 << (ANGLE_BITS-2))
|
||||
#define ANGLE_PI4 (1 << (ANGLE_BITS-3))
|
||||
|
||||
#define ANGLE_FROM_FLOAT(x) (Angle)((x)*ANGLE_PI/M_PI)
|
||||
#define ANGLE_TO_FLOAT(x) ((x)*M_PI/ANGLE_PI)
|
||||
|
||||
#if ANGLE_BITS <= FIXED_BITS
|
||||
# define ANGLE_FROM_FIXED(x) (Angle)((x) >> (FIXED_BITS - ANGLE_BITS))
|
||||
# define ANGLE_TO_FIXED(x) (Fixed)((x) << (FIXED_BITS - ANGLE_BITS))
|
||||
#else
|
||||
# define ANGLE_FROM_FIXED(x) (Angle)((x) << (ANGLE_BITS - FIXED_BITS))
|
||||
# define ANGLE_TO_FIXED(x) (Fixed)((x) >> (ANGLE_BITS - FIXED_BITS))
|
||||
#endif
|
||||
|
||||
static Fixed angle_sin_tab[ANGLE_2PI+1];
|
||||
|
||||
static void init_angles(void)
|
||||
{
|
||||
int nn;
|
||||
for (nn = 0; nn < ANGLE_2PI+1; nn++) {
|
||||
double radians = nn*M_PI/ANGLE_PI;
|
||||
angle_sin_tab[nn] = FIXED_FROM_FLOAT(sin(radians));
|
||||
}
|
||||
}
|
||||
|
||||
static __inline__ Fixed angle_sin( Angle a )
|
||||
{
|
||||
return angle_sin_tab[(uint32_t)a & (ANGLE_2PI-1)];
|
||||
}
|
||||
|
||||
static __inline__ Fixed angle_cos( Angle a )
|
||||
{
|
||||
return angle_sin(a + ANGLE_PI2);
|
||||
}
|
||||
|
||||
static __inline__ Fixed fixed_sin( Fixed f )
|
||||
{
|
||||
return angle_sin(ANGLE_FROM_FIXED(f));
|
||||
}
|
||||
|
||||
static __inline__ Fixed fixed_cos( Fixed f )
|
||||
{
|
||||
return angle_cos(ANGLE_FROM_FIXED(f));
|
||||
}
|
||||
|
||||
/* Color palette used for rendering the plasma */
|
||||
#define PALETTE_BITS 8
|
||||
#define PALETTE_SIZE (1 << PALETTE_BITS)
|
||||
|
||||
#if PALETTE_BITS > FIXED_BITS
|
||||
# error PALETTE_BITS must be smaller than FIXED_BITS
|
||||
#endif
|
||||
|
||||
static uint16_t palette[PALETTE_SIZE];
|
||||
|
||||
static uint16_t make565(int red, int green, int blue)
|
||||
{
|
||||
return (uint16_t)( ((red << 8) & 0xf800) |
|
||||
((green << 3) & 0x07e0) |
|
||||
((blue >> 3) & 0x001f) );
|
||||
}
|
||||
|
||||
static void init_palette(void)
|
||||
{
|
||||
int nn, mm = 0;
|
||||
/* fun with colors */
|
||||
for (nn = 0; nn < PALETTE_SIZE/4; nn++) {
|
||||
int jj = (nn-mm)*4*255/PALETTE_SIZE;
|
||||
palette[nn] = make565(255, jj, 255-jj);
|
||||
}
|
||||
|
||||
for ( mm = nn; nn < PALETTE_SIZE/2; nn++ ) {
|
||||
int jj = (nn-mm)*4*255/PALETTE_SIZE;
|
||||
palette[nn] = make565(255-jj, 255, jj);
|
||||
}
|
||||
|
||||
for ( mm = nn; nn < PALETTE_SIZE*3/4; nn++ ) {
|
||||
int jj = (nn-mm)*4*255/PALETTE_SIZE;
|
||||
palette[nn] = make565(0, 255-jj, 255);
|
||||
}
|
||||
|
||||
for ( mm = nn; nn < PALETTE_SIZE; nn++ ) {
|
||||
int jj = (nn-mm)*4*255/PALETTE_SIZE;
|
||||
palette[nn] = make565(jj, 0, 255);
|
||||
}
|
||||
}
|
||||
|
||||
static __inline__ uint16_t palette_from_fixed( Fixed x )
|
||||
{
|
||||
if (x < 0) x = -x;
|
||||
if (x >= FIXED_ONE) x = FIXED_ONE-1;
|
||||
int idx = FIXED_FRAC(x) >> (FIXED_BITS - PALETTE_BITS);
|
||||
return palette[idx & (PALETTE_SIZE-1)];
|
||||
}
|
||||
|
||||
/* Angles expressed as fixed point radians */
|
||||
|
||||
static void init_tables(void)
|
||||
{
|
||||
init_palette();
|
||||
init_angles();
|
||||
}
|
||||
|
||||
static void fill_plasma( AndroidBitmapInfo* info, void* pixels, double t )
|
||||
{
|
||||
Fixed yt1 = FIXED_FROM_FLOAT(t/1230.);
|
||||
Fixed yt2 = yt1;
|
||||
Fixed xt10 = FIXED_FROM_FLOAT(t/3000.);
|
||||
Fixed xt20 = xt10;
|
||||
|
||||
#define YT1_INCR FIXED_FROM_FLOAT(1/100.)
|
||||
#define YT2_INCR FIXED_FROM_FLOAT(1/163.)
|
||||
|
||||
int yy;
|
||||
for (yy = 0; yy < info->height; yy++) {
|
||||
uint16_t* line = (uint16_t*)pixels;
|
||||
Fixed base = fixed_sin(yt1) + fixed_sin(yt2);
|
||||
Fixed xt1 = xt10;
|
||||
Fixed xt2 = xt20;
|
||||
|
||||
yt1 += YT1_INCR;
|
||||
yt2 += YT2_INCR;
|
||||
|
||||
#define XT1_INCR FIXED_FROM_FLOAT(1/173.)
|
||||
#define XT2_INCR FIXED_FROM_FLOAT(1/242.)
|
||||
|
||||
#if OPTIMIZE_WRITES
|
||||
/* optimize memory writes by generating one aligned 32-bit store
|
||||
* for every pair of pixels.
|
||||
*/
|
||||
uint16_t* line_end = line + info->width;
|
||||
|
||||
if (line < line_end) {
|
||||
if (((uint32_t)(uintptr_t)line & 3) != 0) {
|
||||
Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);
|
||||
|
||||
xt1 += XT1_INCR;
|
||||
xt2 += XT2_INCR;
|
||||
|
||||
line[0] = palette_from_fixed(ii >> 2);
|
||||
line++;
|
||||
}
|
||||
|
||||
while (line + 2 <= line_end) {
|
||||
Fixed i1 = base + fixed_sin(xt1) + fixed_sin(xt2);
|
||||
xt1 += XT1_INCR;
|
||||
xt2 += XT2_INCR;
|
||||
|
||||
Fixed i2 = base + fixed_sin(xt1) + fixed_sin(xt2);
|
||||
xt1 += XT1_INCR;
|
||||
xt2 += XT2_INCR;
|
||||
|
||||
uint32_t pixel = ((uint32_t)palette_from_fixed(i1 >> 2) << 16) |
|
||||
(uint32_t)palette_from_fixed(i2 >> 2);
|
||||
|
||||
((uint32_t*)line)[0] = pixel;
|
||||
line += 2;
|
||||
}
|
||||
|
||||
if (line < line_end) {
|
||||
Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);
|
||||
line[0] = palette_from_fixed(ii >> 2);
|
||||
line++;
|
||||
}
|
||||
}
|
||||
#else /* !OPTIMIZE_WRITES */
|
||||
int xx;
|
||||
for (xx = 0; xx < info->width; xx++) {
|
||||
|
||||
Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);
|
||||
|
||||
xt1 += XT1_INCR;
|
||||
xt2 += XT2_INCR;
|
||||
|
||||
line[xx] = palette_from_fixed(ii / 4);
|
||||
}
|
||||
#endif /* !OPTIMIZE_WRITES */
|
||||
|
||||
// go to next line
|
||||
pixels = (char*)pixels + info->stride;
|
||||
}
|
||||
}
|
||||
|
||||
/* simple stats management */
|
||||
typedef struct {
|
||||
double renderTime;
|
||||
double frameTime;
|
||||
} FrameStats;
|
||||
|
||||
#define MAX_FRAME_STATS 200
|
||||
#define MAX_PERIOD_MS 1500
|
||||
|
||||
typedef struct {
|
||||
double firstTime;
|
||||
double lastTime;
|
||||
double frameTime;
|
||||
|
||||
int firstFrame;
|
||||
int numFrames;
|
||||
FrameStats frames[ MAX_FRAME_STATS ];
|
||||
} Stats;
|
||||
|
||||
static void
|
||||
stats_init( Stats* s )
|
||||
{
|
||||
s->lastTime = now_ms();
|
||||
s->firstTime = 0.;
|
||||
s->firstFrame = 0;
|
||||
s->numFrames = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
stats_startFrame( Stats* s )
|
||||
{
|
||||
s->frameTime = now_ms();
|
||||
}
|
||||
|
||||
static void
|
||||
stats_endFrame( Stats* s )
|
||||
{
|
||||
double now = now_ms();
|
||||
double renderTime = now - s->frameTime;
|
||||
double frameTime = now - s->lastTime;
|
||||
int nn;
|
||||
|
||||
if (now - s->firstTime >= MAX_PERIOD_MS) {
|
||||
if (s->numFrames > 0) {
|
||||
double minRender, maxRender, avgRender;
|
||||
double minFrame, maxFrame, avgFrame;
|
||||
int count;
|
||||
|
||||
nn = s->firstFrame;
|
||||
minRender = maxRender = avgRender = s->frames[nn].renderTime;
|
||||
minFrame = maxFrame = avgFrame = s->frames[nn].frameTime;
|
||||
for (count = s->numFrames; count > 0; count-- ) {
|
||||
nn += 1;
|
||||
if (nn >= MAX_FRAME_STATS)
|
||||
nn -= MAX_FRAME_STATS;
|
||||
double render = s->frames[nn].renderTime;
|
||||
if (render < minRender) minRender = render;
|
||||
if (render > maxRender) maxRender = render;
|
||||
double frame = s->frames[nn].frameTime;
|
||||
if (frame < minFrame) minFrame = frame;
|
||||
if (frame > maxFrame) maxFrame = frame;
|
||||
avgRender += render;
|
||||
avgFrame += frame;
|
||||
}
|
||||
avgRender /= s->numFrames;
|
||||
avgFrame /= s->numFrames;
|
||||
|
||||
LOGI("frame/s (avg,min,max) = (%.1f,%.1f,%.1f) "
|
||||
"render time ms (avg,min,max) = (%.1f,%.1f,%.1f)\n",
|
||||
1000./avgFrame, 1000./maxFrame, 1000./minFrame,
|
||||
avgRender, minRender, maxRender);
|
||||
}
|
||||
s->numFrames = 0;
|
||||
s->firstFrame = 0;
|
||||
s->firstTime = now;
|
||||
}
|
||||
|
||||
nn = s->firstFrame + s->numFrames;
|
||||
if (nn >= MAX_FRAME_STATS)
|
||||
nn -= MAX_FRAME_STATS;
|
||||
|
||||
s->frames[nn].renderTime = renderTime;
|
||||
s->frames[nn].frameTime = frameTime;
|
||||
|
||||
if (s->numFrames < MAX_FRAME_STATS) {
|
||||
s->numFrames += 1;
|
||||
} else {
|
||||
s->firstFrame += 1;
|
||||
if (s->firstFrame >= MAX_FRAME_STATS)
|
||||
s->firstFrame -= MAX_FRAME_STATS;
|
||||
}
|
||||
|
||||
s->lastTime = now;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_example_plasma_PlasmaView_renderPlasma(JNIEnv * env, jobject obj, jobject bitmap, jlong time_ms)
|
||||
{
|
||||
AndroidBitmapInfo info;
|
||||
void* pixels;
|
||||
int ret;
|
||||
static Stats stats;
|
||||
static int init;
|
||||
|
||||
if (!init) {
|
||||
init_tables();
|
||||
stats_init(&stats);
|
||||
init = 1;
|
||||
}
|
||||
|
||||
if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
|
||||
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.format != ANDROID_BITMAP_FORMAT_RGB_565) {
|
||||
LOGE("Bitmap format is not RGB_565 !");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {
|
||||
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
|
||||
}
|
||||
|
||||
stats_startFrame(&stats);
|
||||
|
||||
/* Now fill the values with a nice little plasma */
|
||||
fill_plasma(&info, pixels, time_ms );
|
||||
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
|
||||
stats_endFrame(&stats);
|
||||
}
|
||||
@@ -1,365 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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 <android/bitmap.h>
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#define LOG_TAG "libplasma"
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
/* Set to 1 to enable debug log traces. */
|
||||
#define DEBUG 0
|
||||
|
||||
/* Set to 1 to optimize memory stores when generating plasma. */
|
||||
#define OPTIMIZE_WRITES 1
|
||||
|
||||
/* Return current time in milliseconds */
|
||||
static double now_ms(void) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return tv.tv_sec * 1000. + tv.tv_usec / 1000.;
|
||||
}
|
||||
|
||||
/* We're going to perform computations for every pixel of the target
|
||||
* bitmap. floating-point operations are very slow on ARMv5, and not
|
||||
* too bad on ARMv7 with the exception of trigonometric functions.
|
||||
*
|
||||
* For better performance on all platforms, we're going to use fixed-point
|
||||
* arithmetic and all kinds of tricks
|
||||
*/
|
||||
|
||||
typedef int32_t Fixed;
|
||||
|
||||
#define FIXED_BITS 16
|
||||
#define FIXED_ONE (1 << FIXED_BITS)
|
||||
#define FIXED_AVERAGE(x, y) (((x) + (y)) >> 1)
|
||||
|
||||
#define FIXED_FROM_INT(x) ((x) << FIXED_BITS)
|
||||
#define FIXED_TO_INT(x) ((x) >> FIXED_BITS)
|
||||
|
||||
#define FIXED_FROM_FLOAT(x) ((Fixed)((x) * FIXED_ONE))
|
||||
#define FIXED_TO_FLOAT(x) ((x) / (1. * FIXED_ONE))
|
||||
|
||||
#define FIXED_MUL(x, y) (((int64_t)(x) * (y)) >> FIXED_BITS)
|
||||
#define FIXED_DIV(x, y) (((int64_t)(x) * FIXED_ONE) / (y))
|
||||
|
||||
#define FIXED_DIV2(x) ((x) >> 1)
|
||||
#define FIXED_AVERAGE(x, y) (((x) + (y)) >> 1)
|
||||
|
||||
#define FIXED_FRAC(x) ((x) & ((1 << FIXED_BITS) - 1))
|
||||
#define FIXED_TRUNC(x) ((x) & ~((1 << FIXED_BITS) - 1))
|
||||
|
||||
#define FIXED_FROM_INT_FLOAT(x, f) (Fixed)((x) * (FIXED_ONE * (f)))
|
||||
|
||||
typedef int32_t Angle;
|
||||
|
||||
#define ANGLE_BITS 9
|
||||
|
||||
#if ANGLE_BITS < 8
|
||||
#error ANGLE_BITS must be at least 8
|
||||
#endif
|
||||
|
||||
#define ANGLE_2PI (1 << ANGLE_BITS)
|
||||
#define ANGLE_PI (1 << (ANGLE_BITS - 1))
|
||||
#define ANGLE_PI2 (1 << (ANGLE_BITS - 2))
|
||||
#define ANGLE_PI4 (1 << (ANGLE_BITS - 3))
|
||||
|
||||
#define ANGLE_FROM_FLOAT(x) (Angle)((x) * ANGLE_PI / M_PI)
|
||||
#define ANGLE_TO_FLOAT(x) ((x) * M_PI / ANGLE_PI)
|
||||
|
||||
#if ANGLE_BITS <= FIXED_BITS
|
||||
#define ANGLE_FROM_FIXED(x) (Angle)((x) >> (FIXED_BITS - ANGLE_BITS))
|
||||
#define ANGLE_TO_FIXED(x) (Fixed)((x) << (FIXED_BITS - ANGLE_BITS))
|
||||
#else
|
||||
#define ANGLE_FROM_FIXED(x) (Angle)((x) << (ANGLE_BITS - FIXED_BITS))
|
||||
#define ANGLE_TO_FIXED(x) (Fixed)((x) >> (ANGLE_BITS - FIXED_BITS))
|
||||
#endif
|
||||
|
||||
static Fixed angle_sin_tab[ANGLE_2PI + 1];
|
||||
|
||||
static void init_angles(void) {
|
||||
int nn;
|
||||
for (nn = 0; nn < ANGLE_2PI + 1; nn++) {
|
||||
double radians = nn * M_PI / ANGLE_PI;
|
||||
angle_sin_tab[nn] = FIXED_FROM_FLOAT(sin(radians));
|
||||
}
|
||||
}
|
||||
|
||||
static __inline__ Fixed angle_sin(Angle a) {
|
||||
return angle_sin_tab[(uint32_t)a & (ANGLE_2PI - 1)];
|
||||
}
|
||||
|
||||
static __inline__ Fixed fixed_sin(Fixed f) {
|
||||
return angle_sin(ANGLE_FROM_FIXED(f));
|
||||
}
|
||||
|
||||
/* Color palette used for rendering the plasma */
|
||||
#define PALETTE_BITS 8
|
||||
#define PALETTE_SIZE (1 << PALETTE_BITS)
|
||||
|
||||
#if PALETTE_BITS > FIXED_BITS
|
||||
#error PALETTE_BITS must be smaller than FIXED_BITS
|
||||
#endif
|
||||
|
||||
static uint16_t palette[PALETTE_SIZE];
|
||||
|
||||
static uint16_t make565(int red, int green, int blue) {
|
||||
return (uint16_t)(((red << 8) & 0xf800) | ((green << 3) & 0x07e0) |
|
||||
((blue >> 3) & 0x001f));
|
||||
}
|
||||
|
||||
static void init_palette(void) {
|
||||
int nn, mm = 0;
|
||||
/* fun with colors */
|
||||
for (nn = 0; nn < PALETTE_SIZE / 4; nn++) {
|
||||
int jj = (nn - mm) * 4 * 255 / PALETTE_SIZE;
|
||||
palette[nn] = make565(255, jj, 255 - jj);
|
||||
}
|
||||
|
||||
for (mm = nn; nn < PALETTE_SIZE / 2; nn++) {
|
||||
int jj = (nn - mm) * 4 * 255 / PALETTE_SIZE;
|
||||
palette[nn] = make565(255 - jj, 255, jj);
|
||||
}
|
||||
|
||||
for (mm = nn; nn < PALETTE_SIZE * 3 / 4; nn++) {
|
||||
int jj = (nn - mm) * 4 * 255 / PALETTE_SIZE;
|
||||
palette[nn] = make565(0, 255 - jj, 255);
|
||||
}
|
||||
|
||||
for (mm = nn; nn < PALETTE_SIZE; nn++) {
|
||||
int jj = (nn - mm) * 4 * 255 / PALETTE_SIZE;
|
||||
palette[nn] = make565(jj, 0, 255);
|
||||
}
|
||||
}
|
||||
|
||||
static __inline__ uint16_t palette_from_fixed(Fixed x) {
|
||||
if (x < 0) x = -x;
|
||||
if (x >= FIXED_ONE) x = FIXED_ONE - 1;
|
||||
int idx = FIXED_FRAC(x) >> (FIXED_BITS - PALETTE_BITS);
|
||||
return palette[idx & (PALETTE_SIZE - 1)];
|
||||
}
|
||||
|
||||
/* Angles expressed as fixed point radians */
|
||||
|
||||
static void init_tables(void) {
|
||||
init_palette();
|
||||
init_angles();
|
||||
}
|
||||
|
||||
static void fill_plasma(AndroidBitmapInfo* info, void* pixels, double t) {
|
||||
Fixed yt1 = FIXED_FROM_FLOAT(t / 1230.);
|
||||
Fixed yt2 = yt1;
|
||||
Fixed xt10 = FIXED_FROM_FLOAT(t / 3000.);
|
||||
Fixed xt20 = xt10;
|
||||
|
||||
#define YT1_INCR FIXED_FROM_FLOAT(1 / 100.)
|
||||
#define YT2_INCR FIXED_FROM_FLOAT(1 / 163.)
|
||||
|
||||
for (uint32_t yy = 0; yy < info->height; yy++) {
|
||||
uint16_t* line = (uint16_t*)pixels;
|
||||
Fixed base = fixed_sin(yt1) + fixed_sin(yt2);
|
||||
Fixed xt1 = xt10;
|
||||
Fixed xt2 = xt20;
|
||||
|
||||
yt1 += YT1_INCR;
|
||||
yt2 += YT2_INCR;
|
||||
|
||||
#define XT1_INCR FIXED_FROM_FLOAT(1 / 173.)
|
||||
#define XT2_INCR FIXED_FROM_FLOAT(1 / 242.)
|
||||
|
||||
#if OPTIMIZE_WRITES
|
||||
/* optimize memory writes by generating one aligned 32-bit store
|
||||
* for every pair of pixels.
|
||||
*/
|
||||
uint16_t* line_end = line + info->width;
|
||||
|
||||
if (line < line_end) {
|
||||
if (((uint32_t)(uintptr_t)line & 3) != 0) {
|
||||
Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);
|
||||
|
||||
xt1 += XT1_INCR;
|
||||
xt2 += XT2_INCR;
|
||||
|
||||
line[0] = palette_from_fixed(ii >> 2);
|
||||
line++;
|
||||
}
|
||||
|
||||
while (line + 2 <= line_end) {
|
||||
Fixed i1 = base + fixed_sin(xt1) + fixed_sin(xt2);
|
||||
xt1 += XT1_INCR;
|
||||
xt2 += XT2_INCR;
|
||||
|
||||
Fixed i2 = base + fixed_sin(xt1) + fixed_sin(xt2);
|
||||
xt1 += XT1_INCR;
|
||||
xt2 += XT2_INCR;
|
||||
|
||||
uint32_t pixel = ((uint32_t)palette_from_fixed(i1 >> 2) << 16) |
|
||||
(uint32_t)palette_from_fixed(i2 >> 2);
|
||||
|
||||
((uint32_t*)line)[0] = pixel;
|
||||
line += 2;
|
||||
}
|
||||
|
||||
if (line < line_end) {
|
||||
Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);
|
||||
line[0] = palette_from_fixed(ii >> 2);
|
||||
line++;
|
||||
}
|
||||
}
|
||||
#else /* !OPTIMIZE_WRITES */
|
||||
int xx;
|
||||
for (xx = 0; xx < info->width; xx++) {
|
||||
Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);
|
||||
|
||||
xt1 += XT1_INCR;
|
||||
xt2 += XT2_INCR;
|
||||
|
||||
line[xx] = palette_from_fixed(ii / 4);
|
||||
}
|
||||
#endif /* !OPTIMIZE_WRITES */
|
||||
|
||||
// go to next line
|
||||
pixels = (char*)pixels + info->stride;
|
||||
}
|
||||
}
|
||||
|
||||
/* simple stats management */
|
||||
typedef struct {
|
||||
double renderTime;
|
||||
double frameTime;
|
||||
} FrameStats;
|
||||
|
||||
#define MAX_FRAME_STATS 200
|
||||
#define MAX_PERIOD_MS 1500
|
||||
|
||||
typedef struct {
|
||||
double firstTime;
|
||||
double lastTime;
|
||||
double frameTime;
|
||||
|
||||
int firstFrame;
|
||||
int numFrames;
|
||||
FrameStats frames[MAX_FRAME_STATS];
|
||||
} Stats;
|
||||
|
||||
static void stats_init(Stats* s) {
|
||||
s->lastTime = now_ms();
|
||||
s->firstTime = 0.;
|
||||
s->firstFrame = 0;
|
||||
s->numFrames = 0;
|
||||
}
|
||||
|
||||
static void stats_startFrame(Stats* s) { s->frameTime = now_ms(); }
|
||||
|
||||
static void stats_endFrame(Stats* s) {
|
||||
double now = now_ms();
|
||||
double renderTime = now - s->frameTime;
|
||||
double frameTime = now - s->lastTime;
|
||||
int nn;
|
||||
|
||||
if (now - s->firstTime >= MAX_PERIOD_MS) {
|
||||
if (s->numFrames > 0) {
|
||||
double minRender, maxRender, avgRender;
|
||||
double minFrame, maxFrame, avgFrame;
|
||||
int count;
|
||||
|
||||
nn = s->firstFrame;
|
||||
minRender = maxRender = avgRender = s->frames[nn].renderTime;
|
||||
minFrame = maxFrame = avgFrame = s->frames[nn].frameTime;
|
||||
for (count = s->numFrames; count > 0; count--) {
|
||||
nn += 1;
|
||||
if (nn >= MAX_FRAME_STATS) nn -= MAX_FRAME_STATS;
|
||||
double render = s->frames[nn].renderTime;
|
||||
if (render < minRender) minRender = render;
|
||||
if (render > maxRender) maxRender = render;
|
||||
double frame = s->frames[nn].frameTime;
|
||||
if (frame < minFrame) minFrame = frame;
|
||||
if (frame > maxFrame) maxFrame = frame;
|
||||
avgRender += render;
|
||||
avgFrame += frame;
|
||||
}
|
||||
avgRender /= s->numFrames;
|
||||
avgFrame /= s->numFrames;
|
||||
|
||||
LOGI(
|
||||
"frame/s (avg,min,max) = (%.1f,%.1f,%.1f) "
|
||||
"render time ms (avg,min,max) = (%.1f,%.1f,%.1f)\n",
|
||||
1000. / avgFrame, 1000. / maxFrame, 1000. / minFrame, avgRender,
|
||||
minRender, maxRender);
|
||||
}
|
||||
s->numFrames = 0;
|
||||
s->firstFrame = 0;
|
||||
s->firstTime = now;
|
||||
}
|
||||
|
||||
nn = s->firstFrame + s->numFrames;
|
||||
if (nn >= MAX_FRAME_STATS) nn -= MAX_FRAME_STATS;
|
||||
|
||||
s->frames[nn].renderTime = renderTime;
|
||||
s->frames[nn].frameTime = frameTime;
|
||||
|
||||
if (s->numFrames < MAX_FRAME_STATS) {
|
||||
s->numFrames += 1;
|
||||
} else {
|
||||
s->firstFrame += 1;
|
||||
if (s->firstFrame >= MAX_FRAME_STATS) s->firstFrame -= MAX_FRAME_STATS;
|
||||
}
|
||||
|
||||
s->lastTime = now;
|
||||
}
|
||||
|
||||
void RenderPlasma(JNIEnv* env, jclass, jobject bitmap, jlong time_ms) {
|
||||
AndroidBitmapInfo info;
|
||||
void* pixels;
|
||||
int ret;
|
||||
static Stats stats;
|
||||
static int init;
|
||||
|
||||
if (!init) {
|
||||
init_tables();
|
||||
stats_init(&stats);
|
||||
init = 1;
|
||||
}
|
||||
|
||||
if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
|
||||
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.format != ANDROID_BITMAP_FORMAT_RGB_565) {
|
||||
LOGE("Bitmap format is not RGB_565 !");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {
|
||||
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
|
||||
}
|
||||
|
||||
stats_startFrame(&stats);
|
||||
|
||||
/* Now fill the values with a nice little plasma */
|
||||
fill_plasma(&info, pixels, time_ms);
|
||||
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
|
||||
stats_endFrame(&stats);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (C) 2025 The Android Open Source Project
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
void RenderPlasma(JNIEnv* env, jclass, jobject bitmap, jlong time_ms);
|
||||
@@ -69,9 +69,4 @@ class PlasmaView extends View {
|
||||
// force a redraw, with a different time-based pattern.
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
17
bitmap-plasma/build.gradle
Normal file
@@ -0,0 +1,17 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.2.0'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
BIN
bitmap-plasma/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
5
bitmap-plasma/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
183
bitmap-plasma/gradlew
vendored
Executable file
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## 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='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# 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 or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
100
bitmap-plasma/gradlew.bat
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@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="-Xmx64m" "-Xms64m"
|
||||
|
||||
@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
|
||||
1
bitmap-plasma/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
include ':app'
|
||||
1
build-logic/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/bin
|
||||
@@ -1,11 +0,0 @@
|
||||
# Convention plugins
|
||||
|
||||
This directory contains [convention plugins] used by the NDK samples. These are
|
||||
used to remove Gradle boiler plate from individual samples in favor of common
|
||||
configuration here. Using convention plugins for single module projects is
|
||||
overkill, but any non-trivial app will likely need their own eventually. See
|
||||
[Now In Android's build-logic][nia-build-logic] for a more thorough example of
|
||||
building convention plugins for Android projects.
|
||||
|
||||
[convention plugins]: https://docs.gradle.org/current/samples/sample_convention_plugins.html
|
||||
[nia-build-logic]: https://github.com/android/nowinandroid/blob/main/build-logic/README.md
|
||||
@@ -1,40 +0,0 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
id("java-gradle-plugin")
|
||||
`kotlin-dsl`
|
||||
alias(libs.plugins.jetbrains.kotlin.jvm)
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.android.gradlePlugin)
|
||||
compileOnly(libs.kotlin.gradlePlugin)
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
register("androidApplication") {
|
||||
id = "ndksamples.android.application"
|
||||
implementationClass = "com.android.ndk.samples.buildlogic.AndroidApplicationConventionPlugin"
|
||||
}
|
||||
register("androidLibrary") {
|
||||
id = "ndksamples.android.library"
|
||||
implementationClass = "com.android.ndk.samples.buildlogic.AndroidLibraryConventionPlugin"
|
||||
}
|
||||
register("kotlinAndroid") {
|
||||
id = "ndksamples.android.kotlin"
|
||||
implementationClass = "com.android.ndk.samples.buildlogic.KotlinConventionPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
versionCatalogs {
|
||||
create("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "build-logic"
|
||||
@@ -1,72 +0,0 @@
|
||||
package com.android.ndk.samples.buildlogic
|
||||
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
|
||||
class AndroidApplicationConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
with(target) {
|
||||
with(pluginManager) {
|
||||
apply("com.android.application")
|
||||
}
|
||||
|
||||
extensions.configure<ApplicationExtension> {
|
||||
compileSdk = Versions.COMPILE_SDK
|
||||
ndkVersion = Versions.NDK
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = Versions.CMAKE
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = Versions.MIN_SDK
|
||||
targetSdk = Versions.TARGET_SDK
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments.add("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON")
|
||||
arguments.add("-DCMAKE_MODULE_PATH=${rootDir.resolve("cmake")}")
|
||||
}
|
||||
}
|
||||
|
||||
ndk {
|
||||
// riscv64 isn't a supported Android ABI yet (August 2025), but we're
|
||||
// enabling it here as part of that experiment. Until it's a supported ABI,
|
||||
// don't include this in your app, as Play will block uploads of APKs which
|
||||
// contain riscv64 libraries.
|
||||
abiFilters.addAll(
|
||||
listOf(
|
||||
"arm64-v8a",
|
||||
"armeabi-v7a",
|
||||
"riscv64",
|
||||
"x86",
|
||||
"x86_64",
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = Versions.JAVA
|
||||
targetCompatibility = Versions.JAVA
|
||||
}
|
||||
|
||||
// Studio will not automatically pass logcat through ndk-stack, so we need to avoid
|
||||
// stripping debug binaries if we want the crash trace to be readable.
|
||||
buildTypes {
|
||||
debug {
|
||||
packaging {
|
||||
jniLibs {
|
||||
keepDebugSymbols += "**/*.so"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.android.ndk.samples.buildlogic
|
||||
|
||||
import com.android.build.api.dsl.LibraryExtension
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
|
||||
class AndroidLibraryConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
with(target) {
|
||||
with(pluginManager) {
|
||||
apply("com.android.library")
|
||||
}
|
||||
|
||||
extensions.configure<LibraryExtension> {
|
||||
compileSdk = Versions.COMPILE_SDK
|
||||
ndkVersion = Versions.NDK
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = Versions.CMAKE
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = Versions.MIN_SDK
|
||||
lint {
|
||||
targetSdk = Versions.TARGET_SDK
|
||||
}
|
||||
testOptions {
|
||||
targetSdk = Versions.TARGET_SDK
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments.add("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON")
|
||||
arguments.add("-DCMAKE_MODULE_PATH=${rootDir.resolve("cmake")}")
|
||||
}
|
||||
}
|
||||
ndk {
|
||||
// riscv64 isn't a supported Android ABI yet (August 2025), but we're
|
||||
// enabling it here as part of that experiment. Until it's a supported ABI,
|
||||
// don't include this in your app, as Play will block uploads of APKs which
|
||||
// contain riscv64 libraries.
|
||||
abiFilters.addAll(
|
||||
listOf(
|
||||
"arm64-v8a",
|
||||
"armeabi-v7a",
|
||||
"riscv64",
|
||||
"x86",
|
||||
"x86_64",
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = Versions.JAVA
|
||||
targetCompatibility = Versions.JAVA
|
||||
}
|
||||
|
||||
// Studio will not automatically pass logcat through ndk-stack, so we need to avoid
|
||||
// stripping debug binaries if we want the crash trace to be readable.
|
||||
buildTypes {
|
||||
debug {
|
||||
packaging {
|
||||
jniLibs {
|
||||
keepDebugSymbols += "**/*.so"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.android.ndk.samples.buildlogic
|
||||
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.withType
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
class KotlinConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
with(target) {
|
||||
with(pluginManager) {
|
||||
apply("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
extensions.configure<ApplicationExtension> {
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = Versions.JAVA.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.android.ndk.samples.buildlogic
|
||||
|
||||
import org.gradle.api.JavaVersion
|
||||
|
||||
object Versions {
|
||||
const val COMPILE_SDK = 35
|
||||
const val TARGET_SDK = 35
|
||||
const val MIN_SDK = 21
|
||||
const val NDK = "28.2.13676358" // r28c
|
||||
const val CMAKE = "4.1.0"
|
||||
val JAVA = JavaVersion.VERSION_1_8
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
|
||||
}
|
||||
20
builder/build.gradle
Normal file
@@ -0,0 +1,20 @@
|
||||
apply plugin: 'java'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.gradle:gradle-tooling-api:+'
|
||||
implementation 'org.slf4j:slf4j-api:1.7.7'
|
||||
implementation 'org.eclipse.jgit:org.eclipse.jgit:4.0.1.201506240215-r'
|
||||
implementation 'com.google.guava:guava:+'
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
test {
|
||||
testLogging {
|
||||
exceptionFormat 'full'
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
BIN
builder/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
5
builder/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
183
builder/gradlew
vendored
Executable file
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## 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='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# 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 or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
100
builder/gradlew.bat
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@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="-Xmx64m" "-Xms64m"
|
||||
|
||||
@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
|
||||
19
builder/settings.gradle
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* This settings file was auto generated by the Gradle buildInit task
|
||||
* by 'proppy' at '6/18/15 4:35 PM' with Gradle 2.3
|
||||
*
|
||||
* The settings file is used to specify which projects to include in your build.
|
||||
* In a single project build this file can be empty or even removed.
|
||||
*
|
||||
* Detailed information about configuring a multi-project build in Gradle can be found
|
||||
* in the user guide at http://gradle.org/docs/2.3/userguide/multi_project_builds.html
|
||||
*/
|
||||
|
||||
/*
|
||||
// To declare projects as part of a multi-project build use the 'include' method
|
||||
include 'shared'
|
||||
include 'api'
|
||||
include 'services:webservice'
|
||||
*/
|
||||
|
||||
rootProject.name = 'builder'
|
||||
104
builder/src/test/java/GradleBuildTest.java
Normal file
@@ -0,0 +1,104 @@
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import org.gradle.tooling.BuildException;
|
||||
import org.gradle.tooling.BuildLauncher;
|
||||
import org.gradle.tooling.GradleConnector;
|
||||
import org.gradle.tooling.ProjectConnection;
|
||||
import org.gradle.tooling.model.GradleProject;
|
||||
import org.gradle.tooling.model.GradleTask;
|
||||
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class GradleBuildTest {
|
||||
@Parameters(name="TestBuild{0}")
|
||||
public static Collection<String> data() {
|
||||
LinkedHashSet<String> projects = new LinkedHashSet<String>();
|
||||
try {
|
||||
Repository repository = new FileRepositoryBuilder()
|
||||
.readEnvironment() // scan environment GIT_* variables
|
||||
.findGitDir() // scan up the file system tree
|
||||
.build();
|
||||
RevCommit head = new Git(repository)
|
||||
.log().call()
|
||||
.iterator().next();
|
||||
TreeWalk treeWalk = new TreeWalk(repository);
|
||||
treeWalk.addTree(head.getTree());
|
||||
for (RevCommit p : head.getParents()) {
|
||||
treeWalk.addTree(p.getTree());
|
||||
}
|
||||
treeWalk.setRecursive(false);
|
||||
treeWalk.setFilter(TreeFilter.ANY_DIFF);
|
||||
while (treeWalk.next()) {
|
||||
File f = new File("../" + treeWalk.getPathString());
|
||||
if (isProject(f)) {
|
||||
System.err.println("project changed: " + f.getName());
|
||||
projects.add(f.getName());
|
||||
}
|
||||
}
|
||||
} catch (java.io.IOException e) {
|
||||
System.err.println("error opening git repository: " + e);
|
||||
} catch (GitAPIException e) {
|
||||
System.err.println("error reading git repository log: " + e);
|
||||
}
|
||||
|
||||
for (File p : new File("..").listFiles(new FileFilter() {
|
||||
public boolean accept(File f) { return isProject(f); }
|
||||
})) {
|
||||
projects.add(p.getName());
|
||||
}
|
||||
return projects;
|
||||
}
|
||||
|
||||
private static boolean isProject(File f) {
|
||||
return f.isDirectory() && Arrays.asList(f.list()).containsAll(Arrays.asList("build.gradle", "app"));
|
||||
}
|
||||
|
||||
private File gradleProject;
|
||||
public GradleBuildTest(String projectDirectory) {
|
||||
this.gradleProject = new File("../" + projectDirectory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
GradleConnector connector = GradleConnector.newConnector();
|
||||
connector.forProjectDirectory(gradleProject);
|
||||
ProjectConnection connection = connector.connect();
|
||||
BuildLauncher launcher = connection.newBuild();
|
||||
launcher.setStandardOutput(System.out);
|
||||
launcher.setStandardError(System.err);
|
||||
try {
|
||||
launcher.forTasks("app:lint");
|
||||
launcher.run();
|
||||
launcher.forTasks("assembleDebug");
|
||||
launcher.run();
|
||||
launcher.forTasks("assembleRelease");
|
||||
launcher.run();
|
||||
} catch (BuildException e) {
|
||||
fail(String.format("BUILD FAILED: %s", e));
|
||||
} finally {
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,69 @@
|
||||
# NdkCamera Sample
|
||||
|
||||
NdkCamera Sample
|
||||
=============
|
||||
Two API samples:
|
||||
- texture-view:
|
||||
Preview NDK camera image with [Android TextureView](https://developer.android.com/reference/android/view/TextureView.html)
|
||||
- basic:
|
||||
A basic NdkCamera sample to preview camera images with AReadImage and take jpeg photos.
|
||||
Exposure and sensitivity are adjustable for preview, however capturing photos is in auto mode
|
||||
(it could be adjustable with similar method as used for preview).
|
||||
|
||||
- texture-view: Preview NDK camera image with
|
||||
[Android TextureView](https://developer.android.com/reference/android/view/TextureView.html)
|
||||
- basic: A basic NdkCamera sample to preview camera images with AReadImage and
|
||||
take jpeg photos. Exposure and sensitivity are adjustable for preview, however
|
||||
capturing photos is in auto mode (it could be adjustable with similar method
|
||||
as used for preview).
|
||||
|
||||
## Other Resources
|
||||
|
||||
Other Resources
|
||||
---------------
|
||||
- Getting familiar with the 5 Camera2 objects
|
||||

|
||||

|
||||
|
||||
- [Camera2 blogs](https://medium.com/androiddevelopers/camera-enumeration-on-android-9a053b910cb5)
|
||||
|
||||
- [Camera2 Java documentation](https://developer.android.com/reference/android/hardware/camera2/package-summary)
|
||||
- [Camera2 Java
|
||||
documentation](https://developer.android.com/reference/android/hardware/camera2/package-summary)
|
||||
|
||||
## Screenshots
|
||||
Pre-requisites
|
||||
--------------
|
||||
- Android Studio 2.3.0+ with [NDK-r15+](https://developer.android.com/ndk/) bundle
|
||||
- Android device running android-24+
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
1. Download Android Studio from [latest stable release](http://developer.android.com/sdk/index.html) or [canary](http://tools.android.com/download/studio/canary)
|
||||
1. Launch Android Studio
|
||||
1. Select "Import project (Eclipse ADT, Gradle,etc)"
|
||||
1. Browse into downloaded sample directory, select webp/build.gradle
|
||||
1. Click *Tools/Android/Sync Project with Gradle Files*.
|
||||
1. Click *Run/Run 'app'*.
|
||||
|
||||
|
||||
Screenshots
|
||||
-----------
|
||||

|
||||
|
||||
Support
|
||||
-------
|
||||
If you've found an error in these samples, please [file an issue](https://github.com/googlesamples/android-ndk/issues/new).
|
||||
|
||||
Patches are encouraged, and may be submitted by [forking this project](https://github.com/googlesamples/android-ndk/fork) and
|
||||
submitting a pull request through GitHub. Please see [CONTRIBUTING.md](../CONTRIBUTING.md) for more details.
|
||||
|
||||
For generic questions about Android Camera and other feedbacks, please go to
|
||||
|
||||
- [Stack Overflow](http://stackoverflow.com/questions/tagged/android-camera)
|
||||
- [Android Tools Feedbacks](http://tools.android.com/feedback)
|
||||
|
||||
License
|
||||
-------
|
||||
Copyright 2015 Google, Inc.
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||
license agreements. See the NOTICE file distributed with this work for
|
||||
additional information regarding copyright ownership. The ASF licenses this
|
||||
file to you 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.
|
||||
|
||||
@@ -1,31 +1,38 @@
|
||||
plugins {
|
||||
id "ndksamples.android.application"
|
||||
}
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
namespace 'com.sample.camera.basic'
|
||||
compileSdkVersion 29
|
||||
ndkVersion '21.2.6472646'
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.sample.camera.basic'
|
||||
minSdkVersion 24
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 28
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments '-DANDROID_STL=c++_static'
|
||||
}
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path 'src/main/cpp/CMakeLists.txt'
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'),
|
||||
'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
prefab true
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version '3.18.1'
|
||||
path 'src/main/cpp/CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":base")
|
||||
implementation libs.appcompat
|
||||
implementation project(":camera:camera-utils")
|
||||
implementation fileTree(dir:'libs', include: ['*.jar'])
|
||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="1"
|
||||
package="com.sample.camera.basic"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
@@ -13,8 +15,7 @@
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:hasCode="true">
|
||||
<activity android:name="com.sample.camera.basic.CameraActivity"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true">
|
||||
android:label="@string/app_name">
|
||||
<meta-data android:name="android.app.lib_name"
|
||||
android:value="ndk_camera" />
|
||||
<intent-filter>
|
||||
|
||||
@@ -14,33 +14,39 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
project(CameraBasic LANGUAGES C CXX)
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
include(AppLibrary)
|
||||
include(AndroidNdkModules)
|
||||
find_package(base REQUIRED CONFIG)
|
||||
find_package(camera-utils REQUIRED CONFIG)
|
||||
set(CMAKE_VERBOSE_MAKEFILE on)
|
||||
set(COMMON_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../common)
|
||||
|
||||
android_ndk_import_module_native_app_glue()
|
||||
# build native_app_glue as a static lib
|
||||
include_directories(${ANDROID_NDK}/sources/android/native_app_glue
|
||||
${COMMON_SOURCE_DIR})
|
||||
|
||||
add_app_library(ndk_camera SHARED
|
||||
add_library(app_glue STATIC
|
||||
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
|
||||
|
||||
# now build app's shared lib
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Werror")
|
||||
# Export ANativeActivity_onCreate(),
|
||||
# Refer to: https://github.com/android-ndk/ndk/issues/381.
|
||||
set(CMAKE_SHARED_LINKER_FLAGS
|
||||
"${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
|
||||
|
||||
add_library(ndk_camera SHARED
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/android_main.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/camera_engine.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/camera_manager.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/camera_listeners.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/image_reader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/camera_ui.cpp
|
||||
)
|
||||
${COMMON_SOURCE_DIR}/utils/camera_utils.cpp)
|
||||
|
||||
# add lib dependencies
|
||||
target_link_libraries(ndk_camera
|
||||
PRIVATE
|
||||
base::base
|
||||
camera-utils::camera-utils
|
||||
android
|
||||
log
|
||||
m
|
||||
$<LINK_LIBRARY:WHOLE_ARCHIVE,native_app_glue>
|
||||
app_glue
|
||||
camera2ndk
|
||||
mediandk
|
||||
)
|
||||
mediandk)
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <ndksamples/camera/native_debug.h>
|
||||
|
||||
#include "camera_engine.h"
|
||||
#include "utils/native_debug.h"
|
||||
|
||||
/*
|
||||
* SampleEngine global object
|
||||
@@ -65,19 +64,27 @@ extern "C" void android_main(struct android_app* state) {
|
||||
state->onAppCmd = ProcessAndroidCmd;
|
||||
|
||||
// loop waiting for stuff to do.
|
||||
while (!state->destroyRequested) {
|
||||
struct android_poll_source* source = nullptr;
|
||||
auto result = ALooper_pollOnce(0, NULL, nullptr, (void**)&source);
|
||||
ASSERT(result != ALOOPER_POLL_ERROR, "ALooper_pollOnce returned an error");
|
||||
if (source != NULL) {
|
||||
source->process(state, source);
|
||||
while (1) {
|
||||
// Read all pending events.
|
||||
int events;
|
||||
struct android_poll_source* source;
|
||||
|
||||
while (ALooper_pollAll(0, NULL, &events, (void**)&source) >= 0) {
|
||||
// Process this event.
|
||||
if (source != NULL) {
|
||||
source->process(state, source);
|
||||
}
|
||||
|
||||
// Check if we are exiting.
|
||||
if (state->destroyRequested != 0) {
|
||||
LOGI("CameraEngine thread destroy requested!");
|
||||
engine.DeleteCamera();
|
||||
pEngineObj = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
pEngineObj->DrawFrame();
|
||||
}
|
||||
|
||||
LOGI("CameraEngine thread destroy requested!");
|
||||
engine.DeleteCamera();
|
||||
pEngineObj = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,11 +18,9 @@
|
||||
* Demonstrate NDK Camera interface added to android-24
|
||||
*/
|
||||
|
||||
#include "camera_engine.h"
|
||||
|
||||
#include <ndksamples/camera/native_debug.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include "camera_engine.h"
|
||||
#include "utils/native_debug.h"
|
||||
|
||||
/**
|
||||
* constructor and destructor for main application class
|
||||
@@ -45,7 +43,9 @@ CameraEngine::~CameraEngine() {
|
||||
DeleteCamera();
|
||||
}
|
||||
|
||||
struct android_app* CameraEngine::AndroidApp(void) const { return app_; }
|
||||
struct android_app* CameraEngine::AndroidApp(void) const {
|
||||
return app_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a camera object for onboard BACK_FACING camera
|
||||
@@ -91,8 +91,8 @@ void CameraEngine::CreateCamera(void) {
|
||||
yuvReader_->SetPresentRotation(imageRotation);
|
||||
jpgReader_ = new ImageReader(&capture, AIMAGE_FORMAT_JPEG);
|
||||
jpgReader_->SetPresentRotation(imageRotation);
|
||||
jpgReader_->RegisterCallback(this, [](void* ctx, const char* str) -> void {
|
||||
reinterpret_cast<CameraEngine*>(ctx)->OnPhotoTaken(str);
|
||||
jpgReader_->RegisterCallback(this, [this](void* ctx, const char* str) -> void {
|
||||
reinterpret_cast<CameraEngine* >(ctx)->OnPhotoTaken(str);
|
||||
});
|
||||
|
||||
// now we could create session
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
#include <android/native_window.h>
|
||||
#include <android_native_app_glue.h>
|
||||
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
@@ -60,7 +59,7 @@ class CameraEngine {
|
||||
|
||||
private:
|
||||
void OnPhotoTaken(const char* fileName);
|
||||
int GetDisplayRotation(void);
|
||||
int GetDisplayRotation(void);
|
||||
|
||||
struct android_app* app_;
|
||||
ImageFormat savedNativeWinRes_;
|
||||
|
||||
@@ -13,16 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <camera/NdkCameraManager.h>
|
||||
#include <ndksamples/camera/camera_utils.h>
|
||||
#include <ndksamples/camera/native_debug.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <queue>
|
||||
#include <cinttypes>
|
||||
#include <thread>
|
||||
|
||||
#include <camera/NdkCameraManager.h>
|
||||
#include "camera_manager.h"
|
||||
#include "utils/native_debug.h"
|
||||
#include "utils/camera_utils.h"
|
||||
|
||||
/*
|
||||
* Camera Manager Listener object
|
||||
@@ -122,18 +121,18 @@ void NDKCamera::OnDeviceError(ACameraDevice* dev, int err) {
|
||||
// CaptureSession state callbacks
|
||||
void OnSessionClosed(void* ctx, ACameraCaptureSession* ses) {
|
||||
LOGW("session %p closed", ses);
|
||||
reinterpret_cast<NDKCamera*>(ctx)->OnSessionState(
|
||||
ses, CaptureSessionState::CLOSED);
|
||||
reinterpret_cast<NDKCamera*>(ctx)
|
||||
->OnSessionState(ses, CaptureSessionState::CLOSED);
|
||||
}
|
||||
void OnSessionReady(void* ctx, ACameraCaptureSession* ses) {
|
||||
LOGW("session %p ready", ses);
|
||||
reinterpret_cast<NDKCamera*>(ctx)->OnSessionState(ses,
|
||||
CaptureSessionState::READY);
|
||||
reinterpret_cast<NDKCamera*>(ctx)
|
||||
->OnSessionState(ses, CaptureSessionState::READY);
|
||||
}
|
||||
void OnSessionActive(void* ctx, ACameraCaptureSession* ses) {
|
||||
LOGW("session %p active", ses);
|
||||
reinterpret_cast<NDKCamera*>(ctx)->OnSessionState(
|
||||
ses, CaptureSessionState::ACTIVE);
|
||||
reinterpret_cast<NDKCamera*>(ctx)
|
||||
->OnSessionState(ses, CaptureSessionState::ACTIVE);
|
||||
}
|
||||
|
||||
ACameraCaptureSession_stateCallbacks* NDKCamera::GetSessionListener() {
|
||||
@@ -157,8 +156,7 @@ void NDKCamera::OnSessionState(ACameraCaptureSession* ses,
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT(state < CaptureSessionState::MAX_STATE, "Wrong state %d",
|
||||
static_cast<int>(state));
|
||||
ASSERT(state < CaptureSessionState::MAX_STATE, "Wrong state %d", state);
|
||||
|
||||
captureSessionState_ = state;
|
||||
}
|
||||
@@ -212,7 +210,7 @@ ACameraCaptureSession_captureCallbacks* NDKCamera::GetCaptureCallback() {
|
||||
* @param request the capture request that failed
|
||||
* @param failure for additional fail info.
|
||||
*/
|
||||
void NDKCamera::OnCaptureFailed(ACameraCaptureSession*,
|
||||
void NDKCamera::OnCaptureFailed(ACameraCaptureSession* session,
|
||||
ACaptureRequest* request,
|
||||
ACameraCaptureFailure* failure) {
|
||||
if (valid_ && request == requests_[JPG_CAPTURE_REQUEST_IDX].request_) {
|
||||
@@ -230,8 +228,8 @@ void NDKCamera::OnCaptureFailed(ACameraCaptureSession*,
|
||||
*
|
||||
* If this is jpg capture, turn back on preview after a catpure.
|
||||
*/
|
||||
void NDKCamera::OnCaptureSequenceEnd(ACameraCaptureSession*, int sequenceId,
|
||||
int64_t) {
|
||||
void NDKCamera::OnCaptureSequenceEnd(ACameraCaptureSession* session,
|
||||
int sequenceId, int64_t frameNumber) {
|
||||
if (sequenceId != requests_[JPG_CAPTURE_REQUEST_IDX].sessionSequenceId_)
|
||||
return;
|
||||
|
||||
|
||||