3 Commits

1439 changed files with 120454 additions and 119430 deletions

131
.ci_tools/build_samples.sh Executable file
View 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
View 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
View File

@@ -0,0 +1,4 @@
#!/bin/bash
# TBD: load apks on emulator
#

100
.ci_tools/setup_env.sh Executable file
View 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

View File

@@ -1,2 +0,0 @@
BasedOnStyle: Google
DerivePointerAlignment: false

View File

@@ -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
View 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

View File

@@ -1,13 +0,0 @@
status: PUBLISHED
technologies:
- Android
- NDK
- Platform
categories:
- NDK
languages:
- C++
solutions:
- Mobile
github: android/ndk-samples
license: apache2

View File

@@ -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

View File

@@ -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.

View File

@@ -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
View File

@@ -1,142 +1,66 @@
# Android NDK Samples
NDK Samples [![build](https://github.com/android/ndk-samples/workflows/build/badge.svg)](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
[![build](https://github.com/android/ndk-samples/actions/workflows/build.yml/badge.svg)](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

View File

@@ -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:
&nbsp;&nbsp;&nbsp;&nbsp; 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

View File

@@ -0,0 +1,7 @@
status: PUBLISHED
technologies: [Android, NDK]
categories: [NDK]
languages: [C++, Java]
solutions: [Mobile]
github: googlesamples/android-ndk
license: apache2

View File

@@ -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.

View 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
View 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 *;
#}

View 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>

View 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)

View 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

View 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);
}
}

View 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

View 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();
}

View 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

View 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;
}

View 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());
}

View 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

View 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();
}

View 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

View 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

View 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);
}

View 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

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef 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

View File

@@ -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();
}

View 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>

View 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>

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

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

View File

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

View File

@@ -0,0 +1,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>

View 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>

View File

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

21
audio-echo/build.gradle Normal file
View 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()
}
}

View 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

Binary file not shown.

View 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

View File

@@ -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
View 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

View File

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

1
base/.gitignore vendored
View File

@@ -1 +0,0 @@
/build

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,7 @@
status: PUBLISHED
technologies: [Android, NDK]
categories: [NDK]
languages: [C++, Java]
solutions: [Mobile]
github: googlesamples/android-ndk
license: apache2

View File

@@ -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
-----------
![screenshot](screenshot.png)
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.

View File

@@ -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")
}

View File

@@ -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" />

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -1,6 +0,0 @@
LIBPLASMA {
global:
JNI_OnLoad;
local:
*;
};

View 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);
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);
}
}

View 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()
}
}

Binary file not shown.

View 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
View 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
View 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

View File

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

View File

@@ -1 +0,0 @@
/bin

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -1,13 +0,0 @@
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
rootProject.name = "build-logic"

View File

@@ -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"
}
}
}
}
}
}
}
}

View File

@@ -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"
}
}
}
}
}
}
}
}

View File

@@ -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()
}
}
}
}
}
}

View File

@@ -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
}

View File

@@ -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
View 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
}
}

Binary file not shown.

View 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
View 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
View 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
View 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'

View 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();
}
}
}

View File

@@ -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 API Model](Camera2ProgrammingModel.png)
![Camera2 API Model](Camera2ProgrammingModel.png)
- [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
-----------
![screenshot](ndkCamera.png)
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.

View File

@@ -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'
}

View File

@@ -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>

View File

@@ -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)

View File

@@ -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;
}
/**

View File

@@ -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

View File

@@ -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_;

View File

@@ -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;

Some files were not shown because too many files have changed in this diff Show More