1 ////////////////////////////////////////////////////////////////////////////////
2 ///
3 /// Example Interface class for SoundTouch native compilation
4 ///
5 /// Author        : Copyright (c) Olli Parviainen
6 /// Author e-mail : oparviai 'at' iki.fi
7 /// WWW           : http://www.surina.net
8 ///
9 ////////////////////////////////////////////////////////////////////////////////
10 
11 #include <jni.h>
12 #include <android/log.h>
13 #include <stdexcept>
14 #include <string>
15 
16 using namespace std;
17 
18 #include "../../../include/SoundTouch.h"
19 #include "../source/SoundStretch/WavFile.h"
20 
21 #define LOGV(...)   __android_log_print((int)ANDROID_LOG_INFO, "SOUNDTOUCH", __VA_ARGS__)
22 //#define LOGV(...)
23 
24 
25 // String for keeping possible c++ exception error messages. Notice that this isn't
26 // thread-safe but it's expected that exceptions are special situations that won't
27 // occur in several threads in parallel.
28 static string _errMsg = "";
29 
30 
31 #define DLL_PUBLIC __attribute__ ((visibility ("default")))
32 #define BUFF_SIZE 4096
33 
34 
35 using namespace soundtouch;
36 
37 
38 // Set error message to return
_setErrmsg(const char * msg)39 static void _setErrmsg(const char *msg)
40 {
41 	_errMsg = msg;
42 }
43 
44 #if 0   // apparently following workaround not needed any more with concurrent Android SDKs
45 #ifdef _OPENMP
46 
47 #include <pthread.h>
48 extern pthread_key_t gomp_tls_key;
49 static void * _p_gomp_tls = NULL;
50 
51 /// Function to initialize threading for OpenMP.
52 ///
53 /// This is a workaround for bug in Android NDK v10 regarding OpenMP: OpenMP works only if
54 /// called from the Android App main thread because in the main thread the gomp_tls storage is
55 /// properly set, however, Android does not properly initialize gomp_tls storage for other threads.
56 /// Thus if OpenMP routines are invoked from some other thread than the main thread,
57 /// the OpenMP routine will crash the application due to NULL pointer access on uninitialized storage.
58 ///
59 /// This workaround stores the gomp_tls storage from main thread, and copies to other threads.
60 /// In order this to work, the Application main thread needws to call at least "getVersionString"
61 /// routine.
62 static int _init_threading(bool warn)
63 {
64 	void *ptr = pthread_getspecific(gomp_tls_key);
65 	LOGV("JNI thread-specific TLS storage %ld", (long)ptr);
66 	if (ptr == NULL)
67 	{
68 		LOGV("JNI set missing TLS storage to %ld", (long)_p_gomp_tls);
69 		pthread_setspecific(gomp_tls_key, _p_gomp_tls);
70 	}
71 	else
72 	{
73 		LOGV("JNI store this TLS storage");
74 		_p_gomp_tls = ptr;
75 	}
76 	// Where critical, show warning if storage still not properly initialized
77 	if ((warn) && (_p_gomp_tls == NULL))
78 	{
79 		_setErrmsg("Error - OpenMP threading not properly initialized: Call SoundTouch.getVersionString() from the App main thread!");
80 		return -1;
81 	}
82 	return 0;
83 }
84 
85 #else
86 static int _init_threading(bool warn)
87 {
88 	// do nothing if not OpenMP build
89 	return 0;
90 }
91 #endif
92 
93 #endif
94 
95 // Processes the sound file
_processFile(SoundTouch * pSoundTouch,const char * inFileName,const char * outFileName)96 static void _processFile(SoundTouch *pSoundTouch, const char *inFileName, const char *outFileName)
97 {
98     int nSamples;
99     int nChannels;
100     int buffSizeSamples;
101     SAMPLETYPE sampleBuffer[BUFF_SIZE];
102 
103     // open input file
104     WavInFile inFile(inFileName);
105     int sampleRate = inFile.getSampleRate();
106     int bits = inFile.getNumBits();
107     nChannels = inFile.getNumChannels();
108 
109     // create output file
110     WavOutFile outFile(outFileName, sampleRate, bits, nChannels);
111 
112     pSoundTouch->setSampleRate(sampleRate);
113     pSoundTouch->setChannels(nChannels);
114 
115     assert(nChannels > 0);
116     buffSizeSamples = BUFF_SIZE / nChannels;
117 
118     // Process samples read from the input file
119     while (inFile.eof() == 0)
120     {
121         int num;
122 
123         // Read a chunk of samples from the input file
124         num = inFile.read(sampleBuffer, BUFF_SIZE);
125         nSamples = num / nChannels;
126 
127         // Feed the samples into SoundTouch processor
128         pSoundTouch->putSamples(sampleBuffer, nSamples);
129 
130         // Read ready samples from SoundTouch processor & write them output file.
131         // NOTES:
132         // - 'receiveSamples' doesn't necessarily return any samples at all
133         //   during some rounds!
134         // - On the other hand, during some round 'receiveSamples' may have more
135         //   ready samples than would fit into 'sampleBuffer', and for this reason
136         //   the 'receiveSamples' call is iterated for as many times as it
137         //   outputs samples.
138         do
139         {
140             nSamples = pSoundTouch->receiveSamples(sampleBuffer, buffSizeSamples);
141             outFile.write(sampleBuffer, nSamples * nChannels);
142         } while (nSamples != 0);
143     }
144 
145     // Now the input file is processed, yet 'flush' few last samples that are
146     // hiding in the SoundTouch's internal processing pipeline.
147     pSoundTouch->flush();
148     do
149     {
150         nSamples = pSoundTouch->receiveSamples(sampleBuffer, buffSizeSamples);
151         outFile.write(sampleBuffer, nSamples * nChannels);
152     } while (nSamples != 0);
153 }
154 
155 
156 
Java_net_surina_soundtouch_SoundTouch_getVersionString(JNIEnv * env,jobject thiz)157 extern "C" DLL_PUBLIC jstring Java_net_surina_soundtouch_SoundTouch_getVersionString(JNIEnv *env, jobject thiz)
158 {
159     const char *verStr;
160 
161     LOGV("JNI call SoundTouch.getVersionString");
162 
163     // Call example SoundTouch routine
164     verStr = SoundTouch::getVersionString();
165 
166     // gomp_tls storage bug workaround - see comments in _init_threading() function!
167     // update: apparently this is not needed any more with concurrent Android SDKs
168     // _init_threading(false);
169 
170     int threads = 0;
171 	#pragma omp parallel
172     {
173 		#pragma omp atomic
174     	threads ++;
175     }
176     LOGV("JNI thread count %d", threads);
177 
178     // return version as string
179     return env->NewStringUTF(verStr);
180 }
181 
182 
183 
Java_net_surina_soundtouch_SoundTouch_newInstance(JNIEnv * env,jobject thiz)184 extern "C" DLL_PUBLIC jlong Java_net_surina_soundtouch_SoundTouch_newInstance(JNIEnv *env, jobject thiz)
185 {
186 	return (jlong)(new SoundTouch());
187 }
188 
189 
Java_net_surina_soundtouch_SoundTouch_deleteInstance(JNIEnv * env,jobject thiz,jlong handle)190 extern "C" DLL_PUBLIC void Java_net_surina_soundtouch_SoundTouch_deleteInstance(JNIEnv *env, jobject thiz, jlong handle)
191 {
192 	SoundTouch *ptr = (SoundTouch*)handle;
193 	delete ptr;
194 }
195 
196 
Java_net_surina_soundtouch_SoundTouch_setTempo(JNIEnv * env,jobject thiz,jlong handle,jfloat tempo)197 extern "C" DLL_PUBLIC void Java_net_surina_soundtouch_SoundTouch_setTempo(JNIEnv *env, jobject thiz, jlong handle, jfloat tempo)
198 {
199 	SoundTouch *ptr = (SoundTouch*)handle;
200 	ptr->setTempo(tempo);
201 }
202 
203 
Java_net_surina_soundtouch_SoundTouch_setPitchSemiTones(JNIEnv * env,jobject thiz,jlong handle,jfloat pitch)204 extern "C" DLL_PUBLIC void Java_net_surina_soundtouch_SoundTouch_setPitchSemiTones(JNIEnv *env, jobject thiz, jlong handle, jfloat pitch)
205 {
206 	SoundTouch *ptr = (SoundTouch*)handle;
207 	ptr->setPitchSemiTones(pitch);
208 }
209 
210 
Java_net_surina_soundtouch_SoundTouch_setSpeed(JNIEnv * env,jobject thiz,jlong handle,jfloat speed)211 extern "C" DLL_PUBLIC void Java_net_surina_soundtouch_SoundTouch_setSpeed(JNIEnv *env, jobject thiz, jlong handle, jfloat speed)
212 {
213 	SoundTouch *ptr = (SoundTouch*)handle;
214 	ptr->setRate(speed);
215 }
216 
217 
Java_net_surina_soundtouch_SoundTouch_getErrorString(JNIEnv * env,jobject thiz)218 extern "C" DLL_PUBLIC jstring Java_net_surina_soundtouch_SoundTouch_getErrorString(JNIEnv *env, jobject thiz)
219 {
220 	jstring result = env->NewStringUTF(_errMsg.c_str());
221 	_errMsg.clear();
222 
223 	return result;
224 }
225 
226 
Java_net_surina_soundtouch_SoundTouch_processFile(JNIEnv * env,jobject thiz,jlong handle,jstring jinputFile,jstring joutputFile)227 extern "C" DLL_PUBLIC int Java_net_surina_soundtouch_SoundTouch_processFile(JNIEnv *env, jobject thiz, jlong handle, jstring jinputFile, jstring joutputFile)
228 {
229 	SoundTouch *ptr = (SoundTouch*)handle;
230 
231 	const char *inputFile = env->GetStringUTFChars(jinputFile, 0);
232 	const char *outputFile = env->GetStringUTFChars(joutputFile, 0);
233 
234 	LOGV("JNI process file %s", inputFile);
235 
236     /// gomp_tls storage bug workaround - see comments in _init_threading() function!
237     if (_init_threading(true)) return -1;
238 
239 	try
240 	{
241 		_processFile(ptr, inputFile, outputFile);
242 	}
243 	catch (const runtime_error &e)
244     {
245 		const char *err = e.what();
246         // An exception occurred during processing, return the error message
247     	LOGV("JNI exception in SoundTouch::processFile: %s", err);
248         _setErrmsg(err);
249         return -1;
250     }
251 
252 
253 	env->ReleaseStringUTFChars(jinputFile, inputFile);
254 	env->ReleaseStringUTFChars(joutputFile, outputFile);
255 
256 	return 0;
257 }
258