From 92973bc18ed006c16febfac5960983e3a41fde90 Mon Sep 17 00:00:00 2001 From: oparviai Date: Fri, 15 May 2015 00:07:10 +0000 Subject: [PATCH] Developed more refined Android example application that also works in ARM & X86 platforms. --- include/STTypes.h | 1 + source/Android-lib/AndroidManifest.xml | 3 + .../README-SoundTouch-Android.html | 77 +++---- source/Android-lib/jni/Android.mk | 8 +- source/Android-lib/jni/Application.mk | 2 + source/Android-lib/jni/soundtouch-jni.cpp | 158 +++++++++++++ .../res/layout/activity_example.xml | 55 +++-- .../src/net/surina/ExampleActivity.java | 207 +++++++++++++++--- .../src/net/surina/soundtouch/SoundTouch.java | 54 +++++ 9 files changed, 460 insertions(+), 105 deletions(-) diff --git a/include/STTypes.h b/include/STTypes.h index c11a9d9..e5633b9 100644 --- a/include/STTypes.h +++ b/include/STTypes.h @@ -172,6 +172,7 @@ namespace soundtouch #else // use c++ standard exceptions #include + #include #define ST_THROW_RT_ERROR(x) {throw std::runtime_error(x);} #endif diff --git a/source/Android-lib/AndroidManifest.xml b/source/Android-lib/AndroidManifest.xml index b1f9798..b77e35e 100644 --- a/source/Android-lib/AndroidManifest.xml +++ b/source/Android-lib/AndroidManifest.xml @@ -8,6 +8,9 @@ android:minSdkVersion="11" android:targetSdkVersion="21" /> + + + "soundtouch/source/Android-lib/jni" and invoke the NDK compiler with following command:

    $NDK/ndk-build
-

This will build the ARMv5 and ARMv7 versions of SoundTouch library (including - also the example JNI - interface, see below) into the "libs" subdirectory.

+

This will build binaries for all the supported Android platforms (arm-v5, arm-v7, X86, MIPS etc) of SoundTouch library, plus the JNI wrapper interface as discussed below. The target binaries will be built into the "libs" subdirectory. As long as all these .so binary library versions are included in the APK Application delivery package, the targer Android device can choose the correct library version to use.

Notice that to allow Cygwin/bash to locate the NDK compile scripts, you need to define the location of the NDK installation defined in environment variable "NDK". That's easiest done by adding the NDK path definition at end of your ~/.bash_profile file, for instance as follows:

    NDK=/cygdrive/d/Android/android-ndk-r6

-

- Android floating-point performance considerations

-

- Default build target for - Android NDK is ARMv5 CPU generation, as that works in - all ARM-based Android devices.

- This has a pitfall though: For ideal sound quality SoundTouch should be compiled - to use floating-point algorithms, however, all low-end Android devices do not - have floating-point hardware in their CPUs, and hence the default ARMv5 compilation uses software-emulation for floating-point calculations instead of - hardware floating-point to allow running the binary executables also in low-end devices.

- The floating point software-emulation is however several tens of times slower - than real hardware-level floating-point calculations, making - floating-point-intensive applications such as SoundTouch infeasible with low-end - devices.

- As workaround, the SoundTouch Android compilation builds two separate versions - of the library:

    -
  • ARMv5 version that compiles SoundTouch using integer algorithm version. The integer - algorithm version compromises the sound quality but provides good performance also - with low-end - devices without hardware floating-point support in the CPU level.
  • -
  • ARMv7 version that compiles SoundTouch using hardware floating-point algorithms. - These algorithms provide ideal sound quality yet do not work in simpler CPU - models.
  • -
-

- These two library compilations are already defined in file "jni/Application.mk" - so that these two separate library targets are automatically built under the "libs" - directory. As far as you include both these compiled library versions into your - application delivery, the Android devices can automatically select the right - library version based on the available device's capabilities.

- Please yet be aware that depending on capabilities of the Android devices you - will need to provide the SoundTouch routines with samples in either integer or - floating-point format, so build your interface routines to take this into - account.


Calling SoundTouch native routines from Android application

The NDK tools build the SoundTouch c++ routines into a native binary library, while @@ -96,23 +60,42 @@ Interface (JNI).

The SoundTouch source code package provides source code example how to - use JNI to call native c++ routines from a Java class through the following - source code file pair:

    -
  • Android-lib/jni/soundtouch-jni.cpp: This file contains c/c++ routine that - calls SoundTouch library routine to return the library version string to the main - Android application. The NDK compiles this file along with the SoundTouch + use JNI to call native c++ routines from a Java class, and provides source codes also for + a simple example Android application:
      +
    • ExampleActivity: This is simple Android example application that + utilizes SoundTouch native routines for processing WAV audio files. To build the example + application, use Eclipse Android SDK environment to import the "ExampleActivity" project in the "Android-lib" folder into the Eclipse workspace. +
    • Android-lib/jni/soundtouch-jni.cpp: This file contains c/c++ wrapper routines + for performing elementary audio file processing with adjusted tempo/pitch/speed parameters + from the Android application. The wrapper interface is not complete, but provides example + that is easy to extend when necessary. The NDK compiles this file along with the SoundTouch routines into the native binary library.
    • -
    • Android-lib/src/net/surina/soundtouch/SoundTouch.java: This file provides - a Java interface class to load the native library and to invoke the native routine implemented in - the file soundtouch-jni.cpp
    • +
    • Android-lib/src/net/surina/soundtouch/SoundTouch.java: This file implements + the Java interface class that loasd & accesses the JNI routines in the natively compiled library. + The example Android application uses this class as interface for processing audio files + with SoundTouch.

    Feel free to examine and extend the provided cpp/java source code example file pair to - implement and integrate the desired SoundTouch library capabilities into your Android application.

    + implement and integrate the desired SoundTouch library capabilities into your own Android application.

    +
    +

    + Android floating-point performance considerations

    +

    + The make process will build dedicated binaries for each supported Android CPU hardware platform type. +

    SoundTouch uses floating-point algorithms for ideal sound quality on all other platform than in the lowest-end ARMv5. That is because lowest-end Android devices are not guaranteed to + have floating-point hardware in their CPUs, so that the ARMv5 compilation uses by default software-emulation for floating-point calculations to allow running the binary executables also in low-end devices without floating-point hardware.

    + As floating point software-emulation is however several tens of times slower + than real hardware-level floating-point calculations, that would make running + floating-point-intensive applications such as SoundTouch infeasible in these low-end + devices. As workaround, the SoundTouch Android compilation builds the ARMv5 version using integer algorithm versions. The integer + algorithm version compromises the sound quality but provides good performance also + with low-end devices without hardware floating-point support in the CPU level.

    +

    When Android devices with more capable device is used, the device will automatically choose a proper library version for ideal sound quality.


    Copyright © Olli Parviainen

    - \ No newline at end of file + diff --git a/source/Android-lib/jni/Android.mk b/source/Android-lib/jni/Android.mk index 6a88196..ef5cfef 100644 --- a/source/Android-lib/jni/Android.mk +++ b/source/Android-lib/jni/Android.mk @@ -23,7 +23,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := soundtouch LOCAL_SRC_FILES := soundtouch-jni.cpp ../../SoundTouch/AAFilter.cpp ../../SoundTouch/FIFOSampleBuffer.cpp \ ../../SoundTouch/FIRFilter.cpp ../../SoundTouch/cpu_detect_x86.cpp \ - ../../SoundTouch/sse_optimized.cpp \ + ../../SoundTouch/sse_optimized.cpp ../../SoundStretch/WavFile.cpp \ ../../SoundTouch/RateTransposer.cpp ../../SoundTouch/SoundTouch.cpp \ ../../SoundTouch/InterpolateCubic.cpp ../../SoundTouch/InterpolateLinear.cpp \ ../../SoundTouch/InterpolateShannon.cpp ../../SoundTouch/TDStretch.cpp \ @@ -37,7 +37,9 @@ LOCAL_LDLIBS += -llog # for native asset manager #LOCAL_LDLIBS += -landroid # don't export all symbols -# added "-marm" switch to use arm instruction set instead of thumb for improved calculation performance. -LOCAL_CFLAGS += -fvisibility=hidden -I ../../../include -D ST_NO_EXCEPTION_HANDLING -fdata-sections -ffunction-sections +# +# in ARM-only environment could add "-marm" switch to force arm instruction set instead +# of thumb for improved calculation performance. +LOCAL_CFLAGS += -fvisibility=hidden -I ../../../include -fdata-sections -ffunction-sections include $(BUILD_SHARED_LIBRARY) diff --git a/source/Android-lib/jni/Application.mk b/source/Android-lib/jni/Application.mk index 9f0c50a..68ef2d0 100644 --- a/source/Android-lib/jni/Application.mk +++ b/source/Android-lib/jni/Application.mk @@ -5,3 +5,5 @@ APP_ABI := all #armeabi-v7a armeabi APP_OPTIM := release +APP_STL := stlport_static +APP_CPPFLAGS := -fexceptions \ No newline at end of file diff --git a/source/Android-lib/jni/soundtouch-jni.cpp b/source/Android-lib/jni/soundtouch-jni.cpp index 8df3543..15fb446 100644 --- a/source/Android-lib/jni/soundtouch-jni.cpp +++ b/source/Android-lib/jni/soundtouch-jni.cpp @@ -14,17 +14,101 @@ #include #include +#include +#include + +using namespace std; #include "../../../include/SoundTouch.h" +#include "../source/SoundStretch/WavFile.h" #define LOGV(...) __android_log_print((int)ANDROID_LOG_INFO, "SOUNDTOUCH", __VA_ARGS__) //#define LOGV(...) +// String for keeping possible c++ exception error messages. Notice that this isn't +// thread-safe but it's expected that exceptions are special situations that won't +// occur in several threads in parallel. +static string _errMsg = ""; + + #define DLL_PUBLIC __attribute__ ((visibility ("default"))) +#define BUFF_SIZE 4096 + using namespace soundtouch; + +// Set error message to return +static void _setErrmsg(const char *msg) +{ + _errMsg = msg; +} + + + +// Processes the sound file +static void _processFile(SoundTouch *pSoundTouch, const char *inFileName, const char *outFileName) +{ + int nSamples; + int nChannels; + int buffSizeSamples; + SAMPLETYPE sampleBuffer[BUFF_SIZE]; + + // open input file + WavInFile inFile(inFileName); + int sampleRate = inFile.getSampleRate(); + int bits = inFile.getNumBits(); + nChannels = inFile.getNumChannels(); + + // create output file + WavOutFile outFile(outFileName, sampleRate, bits, nChannels); + + pSoundTouch->setSampleRate(sampleRate); + pSoundTouch->setChannels(nChannels); + + assert(nChannels > 0); + buffSizeSamples = BUFF_SIZE / nChannels; + + // Process samples read from the input file + while (inFile.eof() == 0) + { + int num; + + // Read a chunk of samples from the input file + num = inFile.read(sampleBuffer, BUFF_SIZE); + nSamples = num / nChannels; + + // Feed the samples into SoundTouch processor + pSoundTouch->putSamples(sampleBuffer, nSamples); + + // Read ready samples from SoundTouch processor & write them output file. + // NOTES: + // - 'receiveSamples' doesn't necessarily return any samples at all + // during some rounds! + // - On the other hand, during some round 'receiveSamples' may have more + // ready samples than would fit into 'sampleBuffer', and for this reason + // the 'receiveSamples' call is iterated for as many times as it + // outputs samples. + do + { + nSamples = pSoundTouch->receiveSamples(sampleBuffer, buffSizeSamples); + outFile.write(sampleBuffer, nSamples * nChannels); + } while (nSamples != 0); + } + + // Now the input file is processed, yet 'flush' few last samples that are + // hiding in the SoundTouch's internal processing pipeline. + pSoundTouch->flush(); + do + { + nSamples = pSoundTouch->receiveSamples(sampleBuffer, buffSizeSamples); + outFile.write(sampleBuffer, nSamples * nChannels); + } while (nSamples != 0); +} + + + extern "C" DLL_PUBLIC jstring Java_net_surina_soundtouch_SoundTouch_getVersionString(JNIEnv *env, jobject thiz) { const char *verStr; @@ -37,3 +121,77 @@ extern "C" DLL_PUBLIC jstring Java_net_surina_soundtouch_SoundTouch_getVersionSt // return version as string return env->NewStringUTF(verStr); } + + + +extern "C" DLL_PUBLIC jlong Java_net_surina_soundtouch_SoundTouch_newInstance(JNIEnv *env, jobject thiz) +{ + return (jlong)(new SoundTouch()); +} + + +extern "C" DLL_PUBLIC void Java_net_surina_soundtouch_SoundTouch_deleteInstance(JNIEnv *env, jobject thiz, jlong handle) +{ + SoundTouch *ptr = (SoundTouch*)handle; + delete ptr; +} + + +extern "C" DLL_PUBLIC void Java_net_surina_soundtouch_SoundTouch_setTempo(JNIEnv *env, jobject thiz, jlong handle, jfloat tempo) +{ + SoundTouch *ptr = (SoundTouch*)handle; + ptr->setTempo(tempo); +} + + +extern "C" DLL_PUBLIC void Java_net_surina_soundtouch_SoundTouch_setPitchSemiTones(JNIEnv *env, jobject thiz, jlong handle, jfloat pitch) +{ + SoundTouch *ptr = (SoundTouch*)handle; + ptr->setPitchSemiTones(pitch); +} + + +extern "C" DLL_PUBLIC void Java_net_surina_soundtouch_SoundTouch_setSpeed(JNIEnv *env, jobject thiz, jlong handle, jfloat speed) +{ + SoundTouch *ptr = (SoundTouch*)handle; + ptr->setRate(speed); +} + + +extern "C" DLL_PUBLIC jstring Java_net_surina_soundtouch_SoundTouch_getErrorString(JNIEnv *env, jobject thiz) +{ + jstring result = env->NewStringUTF(_errMsg.c_str()); + _errMsg.clear(); + + return result; +} + + +extern "C" DLL_PUBLIC int Java_net_surina_soundtouch_SoundTouch_processFile(JNIEnv *env, jobject thiz, jlong handle, jstring jinputFile, jstring joutputFile) +{ + SoundTouch *ptr = (SoundTouch*)handle; + + const char *inputFile = env->GetStringUTFChars(jinputFile, 0); + const char *outputFile = env->GetStringUTFChars(joutputFile, 0); + + LOGV("JNI process file %s", inputFile); + + try + { + _processFile(ptr, inputFile, outputFile); + } + catch (const runtime_error &e) + { + const char *err = e.what(); + // An exception occurred during processing, return the error message + LOGV("JNI exception in SoundTouch::processFile: %s", err); + _setErrmsg(err); + return -1; + } + + + env->ReleaseStringUTFChars(jinputFile, inputFile); + env->ReleaseStringUTFChars(joutputFile, outputFile); + + return 0; +} diff --git a/source/Android-lib/res/layout/activity_example.xml b/source/Android-lib/res/layout/activity_example.xml index fcb296f..b333511 100644 --- a/source/Android-lib/res/layout/activity_example.xml +++ b/source/Android-lib/res/layout/activity_example.xml @@ -24,7 +24,7 @@ android:id="@+id/editTextTempo" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:ems="10" + android:ems="5" android:inputType="numberDecimal" android:text="100" > @@ -38,45 +38,24 @@ - - - - - - - - + android:layout_marginTop="10dp" > +