Merge changes I8c36f5af,I800bc352 am: 9acfdcf214 am: 8b93d78c01 am: 9daeb3e9ad
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1826714 Change-Id: I635670dd84a4dd9db0d0936a6a5bd31435d74533
This commit is contained in:
@@ -14,8 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
//#define LOG_NDEBUG 0
|
//#define LOG_NDEBUG 0
|
||||||
#define LOG_TAG "AudioEffects-JNI"
|
#define LOG_TAG "AudioEffects-JNI"
|
||||||
@@ -61,21 +61,14 @@ static fields_t fields;
|
|||||||
struct effect_callback_cookie {
|
struct effect_callback_cookie {
|
||||||
jclass audioEffect_class; // AudioEffect class
|
jclass audioEffect_class; // AudioEffect class
|
||||||
jobject audioEffect_ref; // AudioEffect object instance
|
jobject audioEffect_ref; // AudioEffect object instance
|
||||||
};
|
bool busy;
|
||||||
|
Condition cond;
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
class AudioEffectJniStorage {
|
|
||||||
public:
|
|
||||||
effect_callback_cookie mCallbackData;
|
|
||||||
|
|
||||||
AudioEffectJniStorage() {
|
|
||||||
}
|
|
||||||
|
|
||||||
~AudioEffectJniStorage() {
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
struct AudioEffectJniStorage {
|
||||||
|
effect_callback_cookie mCallbackData{};
|
||||||
|
};
|
||||||
|
|
||||||
jint AudioEffectJni::translateNativeErrorToJava(int code) {
|
jint AudioEffectJni::translateNativeErrorToJava(int code) {
|
||||||
switch(code) {
|
switch(code) {
|
||||||
@@ -104,6 +97,7 @@ jint AudioEffectJni::translateNativeErrorToJava(int code) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Mutex sLock;
|
static Mutex sLock;
|
||||||
|
static std::unordered_set<effect_callback_cookie*> sAudioEffectCallBackCookies;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
static void effectCallback(int event, void* user, void *info) {
|
static void effectCallback(int event, void* user, void *info) {
|
||||||
@@ -124,7 +118,13 @@ static void effectCallback(int event, void* user, void *info) {
|
|||||||
ALOGW("effectCallback error user %p, env %p", user, env);
|
ALOGW("effectCallback error user %p, env %p", user, env);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
Mutex::Autolock l(sLock);
|
||||||
|
if (sAudioEffectCallBackCookies.count(callbackInfo) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callbackInfo->busy = true;
|
||||||
|
}
|
||||||
ALOGV("effectCallback: callbackInfo %p, audioEffect_ref %p audioEffect_class %p",
|
ALOGV("effectCallback: callbackInfo %p, audioEffect_ref %p audioEffect_class %p",
|
||||||
callbackInfo,
|
callbackInfo,
|
||||||
callbackInfo->audioEffect_ref,
|
callbackInfo->audioEffect_ref,
|
||||||
@@ -191,6 +191,11 @@ effectCallback_Exit:
|
|||||||
env->ExceptionDescribe();
|
env->ExceptionDescribe();
|
||||||
env->ExceptionClear();
|
env->ExceptionClear();
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
Mutex::Autolock l(sLock);
|
||||||
|
callbackInfo->busy = false;
|
||||||
|
callbackInfo->cond.broadcast();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@@ -401,6 +406,10 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t
|
|||||||
setAudioEffect(env, thiz, lpAudioEffect);
|
setAudioEffect(env, thiz, lpAudioEffect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Mutex::Autolock l(sLock);
|
||||||
|
sAudioEffectCallBackCookies.insert(&lpJniStorage->mCallbackData);
|
||||||
|
}
|
||||||
env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage);
|
env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage);
|
||||||
|
|
||||||
return (jint) AUDIOEFFECT_SUCCESS;
|
return (jint) AUDIOEFFECT_SUCCESS;
|
||||||
@@ -432,6 +441,7 @@ setup_failure:
|
|||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
#define CALLBACK_COND_WAIT_TIMEOUT_MS 1000
|
||||||
static void android_media_AudioEffect_native_release(JNIEnv *env, jobject thiz) {
|
static void android_media_AudioEffect_native_release(JNIEnv *env, jobject thiz) {
|
||||||
sp<AudioEffect> lpAudioEffect = setAudioEffect(env, thiz, 0);
|
sp<AudioEffect> lpAudioEffect = setAudioEffect(env, thiz, 0);
|
||||||
if (lpAudioEffect == 0) {
|
if (lpAudioEffect == 0) {
|
||||||
@@ -447,7 +457,17 @@ static void android_media_AudioEffect_native_release(JNIEnv *env, jobject thiz)
|
|||||||
env->SetLongField(thiz, fields.fidJniData, 0);
|
env->SetLongField(thiz, fields.fidJniData, 0);
|
||||||
|
|
||||||
if (lpJniStorage) {
|
if (lpJniStorage) {
|
||||||
ALOGV("deleting pJniStorage: %p\n", lpJniStorage);
|
Mutex::Autolock l(sLock);
|
||||||
|
effect_callback_cookie *lpCookie = &lpJniStorage->mCallbackData;
|
||||||
|
ALOGV("deleting lpJniStorage: %p\n", lpJniStorage);
|
||||||
|
sAudioEffectCallBackCookies.erase(lpCookie);
|
||||||
|
while (lpCookie->busy) {
|
||||||
|
if (lpCookie->cond.waitRelative(sLock,
|
||||||
|
milliseconds(CALLBACK_COND_WAIT_TIMEOUT_MS)) !=
|
||||||
|
NO_ERROR) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_class);
|
env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_class);
|
||||||
env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_ref);
|
env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_ref);
|
||||||
delete lpJniStorage;
|
delete lpJniStorage;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
//#define LOG_NDEBUG 0
|
//#define LOG_NDEBUG 0
|
||||||
#define LOG_TAG "visualizers-JNI"
|
#define LOG_TAG "visualizers-JNI"
|
||||||
@@ -68,6 +69,12 @@ struct visualizer_callback_cookie {
|
|||||||
jclass visualizer_class; // Visualizer class
|
jclass visualizer_class; // Visualizer class
|
||||||
jobject visualizer_ref; // Visualizer object instance
|
jobject visualizer_ref; // Visualizer object instance
|
||||||
|
|
||||||
|
// 'busy_count' and 'cond' together with 'sLock' are used to serialize
|
||||||
|
// concurrent access to the callback cookie from 'setup'/'release'
|
||||||
|
// and the callback.
|
||||||
|
int busy_count;
|
||||||
|
Condition cond;
|
||||||
|
|
||||||
// Lazily allocated arrays used to hold callback data provided to java
|
// Lazily allocated arrays used to hold callback data provided to java
|
||||||
// applications. These arrays are allocated during the first callback and
|
// applications. These arrays are allocated during the first callback and
|
||||||
// reallocated when the size of the callback data changes. Allocating on
|
// reallocated when the size of the callback data changes. Allocating on
|
||||||
@@ -75,14 +82,12 @@ struct visualizer_callback_cookie {
|
|||||||
// reference to the provided data (they need to make a copy if they want to
|
// reference to the provided data (they need to make a copy if they want to
|
||||||
// hold onto outside of the callback scope), but it avoids GC thrash caused
|
// hold onto outside of the callback scope), but it avoids GC thrash caused
|
||||||
// by constantly allocating and releasing arrays to hold callback data.
|
// by constantly allocating and releasing arrays to hold callback data.
|
||||||
|
// 'callback_data_lock' must never be held at the same time with 'sLock'.
|
||||||
Mutex callback_data_lock;
|
Mutex callback_data_lock;
|
||||||
jbyteArray waveform_data;
|
jbyteArray waveform_data;
|
||||||
jbyteArray fft_data;
|
jbyteArray fft_data;
|
||||||
|
|
||||||
visualizer_callback_cookie() {
|
// Assumes use of default initialization by the client.
|
||||||
waveform_data = NULL;
|
|
||||||
fft_data = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
~visualizer_callback_cookie() {
|
~visualizer_callback_cookie() {
|
||||||
cleanupBuffers();
|
cleanupBuffers();
|
||||||
@@ -107,15 +112,8 @@ struct visualizer_callback_cookie {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
class VisualizerJniStorage {
|
struct VisualizerJniStorage {
|
||||||
public:
|
visualizer_callback_cookie mCallbackData{};
|
||||||
visualizer_callback_cookie mCallbackData;
|
|
||||||
|
|
||||||
VisualizerJniStorage() {
|
|
||||||
}
|
|
||||||
|
|
||||||
~VisualizerJniStorage() {
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -141,6 +139,7 @@ static jint translateError(int code) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Mutex sLock;
|
static Mutex sLock;
|
||||||
|
static std::unordered_set<visualizer_callback_cookie*> sVisualizerCallBackCookies;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) {
|
static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) {
|
||||||
@@ -178,11 +177,19 @@ static void captureCallback(void* user,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Mutex::Autolock l(sLock);
|
||||||
|
if (sVisualizerCallBackCookies.count(callbackInfo) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callbackInfo->busy_count++;
|
||||||
|
}
|
||||||
ALOGV("captureCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p",
|
ALOGV("captureCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p",
|
||||||
callbackInfo,
|
callbackInfo,
|
||||||
callbackInfo->visualizer_ref,
|
callbackInfo->visualizer_ref,
|
||||||
callbackInfo->visualizer_class);
|
callbackInfo->visualizer_class);
|
||||||
|
|
||||||
|
{
|
||||||
AutoMutex lock(&callbackInfo->callback_data_lock);
|
AutoMutex lock(&callbackInfo->callback_data_lock);
|
||||||
|
|
||||||
if (waveformSize != 0 && waveform != NULL) {
|
if (waveformSize != 0 && waveform != NULL) {
|
||||||
@@ -224,11 +231,17 @@ static void captureCallback(void* user,
|
|||||||
jArray);
|
jArray);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} // callback_data_lock scope
|
||||||
|
|
||||||
if (env->ExceptionCheck()) {
|
if (env->ExceptionCheck()) {
|
||||||
env->ExceptionDescribe();
|
env->ExceptionDescribe();
|
||||||
env->ExceptionClear();
|
env->ExceptionClear();
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
Mutex::Autolock l(sLock);
|
||||||
|
callbackInfo->busy_count--;
|
||||||
|
callbackInfo->cond.broadcast();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@@ -337,16 +350,41 @@ static void android_media_visualizer_effect_callback(int32_t event,
|
|||||||
void *info) {
|
void *info) {
|
||||||
if ((event == AudioEffect::EVENT_ERROR) &&
|
if ((event == AudioEffect::EVENT_ERROR) &&
|
||||||
(*((status_t*)info) == DEAD_OBJECT)) {
|
(*((status_t*)info) == DEAD_OBJECT)) {
|
||||||
VisualizerJniStorage* lpJniStorage = (VisualizerJniStorage*)user;
|
visualizer_callback_cookie* callbackInfo =
|
||||||
visualizer_callback_cookie* callbackInfo = &lpJniStorage->mCallbackData;
|
(visualizer_callback_cookie *)user;
|
||||||
JNIEnv *env = AndroidRuntime::getJNIEnv();
|
JNIEnv *env = AndroidRuntime::getJNIEnv();
|
||||||
|
|
||||||
|
if (!user || !env) {
|
||||||
|
ALOGW("effectCallback error user %p, env %p", user, env);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Mutex::Autolock l(sLock);
|
||||||
|
if (sVisualizerCallBackCookies.count(callbackInfo) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callbackInfo->busy_count++;
|
||||||
|
}
|
||||||
|
ALOGV("effectCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p",
|
||||||
|
callbackInfo,
|
||||||
|
callbackInfo->visualizer_ref,
|
||||||
|
callbackInfo->visualizer_class);
|
||||||
|
|
||||||
env->CallStaticVoidMethod(
|
env->CallStaticVoidMethod(
|
||||||
callbackInfo->visualizer_class,
|
callbackInfo->visualizer_class,
|
||||||
fields.midPostNativeEvent,
|
fields.midPostNativeEvent,
|
||||||
callbackInfo->visualizer_ref,
|
callbackInfo->visualizer_ref,
|
||||||
NATIVE_EVENT_SERVER_DIED,
|
NATIVE_EVENT_SERVER_DIED,
|
||||||
0, NULL);
|
0, NULL);
|
||||||
|
if (env->ExceptionCheck()) {
|
||||||
|
env->ExceptionDescribe();
|
||||||
|
env->ExceptionClear();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Mutex::Autolock l(sLock);
|
||||||
|
callbackInfo->busy_count--;
|
||||||
|
callbackInfo->cond.broadcast();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,7 +434,7 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th
|
|||||||
}
|
}
|
||||||
lpVisualizer->set(0,
|
lpVisualizer->set(0,
|
||||||
android_media_visualizer_effect_callback,
|
android_media_visualizer_effect_callback,
|
||||||
lpJniStorage,
|
&lpJniStorage->mCallbackData,
|
||||||
(audio_session_t) sessionId);
|
(audio_session_t) sessionId);
|
||||||
|
|
||||||
lStatus = translateError(lpVisualizer->initCheck());
|
lStatus = translateError(lpVisualizer->initCheck());
|
||||||
@@ -417,6 +455,10 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th
|
|||||||
|
|
||||||
setVisualizer(env, thiz, lpVisualizer);
|
setVisualizer(env, thiz, lpVisualizer);
|
||||||
|
|
||||||
|
{
|
||||||
|
Mutex::Autolock l(sLock);
|
||||||
|
sVisualizerCallBackCookies.insert(&lpJniStorage->mCallbackData);
|
||||||
|
}
|
||||||
env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage);
|
env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage);
|
||||||
|
|
||||||
return VISUALIZER_SUCCESS;
|
return VISUALIZER_SUCCESS;
|
||||||
@@ -439,13 +481,15 @@ setup_failure:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
#define CALLBACK_COND_WAIT_TIMEOUT_MS 1000
|
||||||
static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) {
|
static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) {
|
||||||
{ //limit scope so that lpVisualizer is deleted before JNI storage data.
|
{
|
||||||
sp<Visualizer> lpVisualizer = setVisualizer(env, thiz, 0);
|
sp<Visualizer> lpVisualizer = setVisualizer(env, thiz, 0);
|
||||||
if (lpVisualizer == 0) {
|
if (lpVisualizer == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lpVisualizer->release();
|
lpVisualizer->release();
|
||||||
|
// Visualizer can still can be held by AudioEffect::EffectClient
|
||||||
}
|
}
|
||||||
// delete the JNI data
|
// delete the JNI data
|
||||||
VisualizerJniStorage* lpJniStorage =
|
VisualizerJniStorage* lpJniStorage =
|
||||||
@@ -456,9 +500,22 @@ static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz)
|
|||||||
env->SetLongField(thiz, fields.fidJniData, 0);
|
env->SetLongField(thiz, fields.fidJniData, 0);
|
||||||
|
|
||||||
if (lpJniStorage) {
|
if (lpJniStorage) {
|
||||||
|
{
|
||||||
|
Mutex::Autolock l(sLock);
|
||||||
|
visualizer_callback_cookie *lpCookie = &lpJniStorage->mCallbackData;
|
||||||
ALOGV("deleting pJniStorage: %p\n", lpJniStorage);
|
ALOGV("deleting pJniStorage: %p\n", lpJniStorage);
|
||||||
|
sVisualizerCallBackCookies.erase(lpCookie);
|
||||||
|
while (lpCookie->busy_count > 0) {
|
||||||
|
if (lpCookie->cond.waitRelative(sLock,
|
||||||
|
milliseconds(CALLBACK_COND_WAIT_TIMEOUT_MS)) !=
|
||||||
|
NO_ERROR) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ALOG_ASSERT(lpCookie->busy_count == 0, "Unbalanced busy_count inc/dec");
|
||||||
env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_class);
|
env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_class);
|
||||||
env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_ref);
|
env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_ref);
|
||||||
|
} // sLock scope
|
||||||
delete lpJniStorage;
|
delete lpJniStorage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -714,4 +771,3 @@ int register_android_media_visualizer(JNIEnv *env)
|
|||||||
{
|
{
|
||||||
return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
|
return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,10 @@
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
<!--
|
||||||
|
Make sure to enable access to the mic in settings and run:
|
||||||
|
adb shell am compat enable ALLOW_TEST_API_ACCESS com.android.effectstest
|
||||||
|
-->
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.android.effectstest">
|
package="com.android.effectstest">
|
||||||
|
|||||||
@@ -187,6 +187,11 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scaleType="fitXY"/>
|
android:scaleType="fitXY"/>
|
||||||
|
|
||||||
|
<Button android:id="@+id/hammer_on_release_bug"
|
||||||
|
android:layout_width="fill_parent" android:layout_height="wrap_content"
|
||||||
|
android:text="@string/hammer_on_release_bug_name">
|
||||||
|
</Button>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -175,6 +175,11 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button android:id="@+id/hammer_on_release_bug"
|
||||||
|
android:layout_width="fill_parent" android:layout_height="wrap_content"
|
||||||
|
android:text="@string/hammer_on_release_bug_name">
|
||||||
|
</Button>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:src="@android:drawable/divider_horizontal_dark"
|
android:src="@android:drawable/divider_horizontal_dark"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
|
|||||||
@@ -37,4 +37,6 @@
|
|||||||
<string name="send_level_name">Send Level</string>
|
<string name="send_level_name">Send Level</string>
|
||||||
<!-- Toggles use of a multi-threaded client for an effect [CHAR LIMIT=24] -->
|
<!-- Toggles use of a multi-threaded client for an effect [CHAR LIMIT=24] -->
|
||||||
<string name="effect_multithreaded">Multithreaded Use</string>
|
<string name="effect_multithreaded">Multithreaded Use</string>
|
||||||
|
<!-- Runs a stress test for a bug related to simultaneous release of multiple effect instances [CHAR LIMIT=24] -->
|
||||||
|
<string name="hammer_on_release_bug_name">Hammer on release()</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -17,29 +17,24 @@
|
|||||||
package com.android.effectstest;
|
package com.android.effectstest;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.media.audiofx.AudioEffect;
|
||||||
import android.content.Intent;
|
import android.media.audiofx.BassBoost;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.View.OnClickListener;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.SeekBar;
|
|
||||||
import android.widget.ToggleButton;
|
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
import java.nio.ByteOrder;
|
import android.widget.EditText;
|
||||||
import java.nio.ByteBuffer;
|
import android.widget.SeekBar;
|
||||||
import java.util.HashMap;
|
import android.widget.TextView;
|
||||||
import java.util.Map;
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
import android.media.audiofx.BassBoost;
|
import java.nio.ByteBuffer;
|
||||||
import android.media.audiofx.AudioEffect;
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
public class BassBoostTest extends Activity implements OnCheckedChangeListener {
|
public class BassBoostTest extends Activity implements OnCheckedChangeListener {
|
||||||
|
|
||||||
@@ -78,6 +73,9 @@ public class BassBoostTest extends Activity implements OnCheckedChangeListener {
|
|||||||
mReleaseButton = (ToggleButton)findViewById(R.id.bbReleaseButton);
|
mReleaseButton = (ToggleButton)findViewById(R.id.bbReleaseButton);
|
||||||
mOnOffButton = (ToggleButton)findViewById(R.id.bassboostOnOff);
|
mOnOffButton = (ToggleButton)findViewById(R.id.bassboostOnOff);
|
||||||
|
|
||||||
|
final Button hammerReleaseTest = (Button) findViewById(R.id.hammer_on_release_bug);
|
||||||
|
hammerReleaseTest.setEnabled(false);
|
||||||
|
|
||||||
getEffect(sSession);
|
getEffect(sSession);
|
||||||
|
|
||||||
if (mBassBoost != null) {
|
if (mBassBoost != null) {
|
||||||
@@ -93,6 +91,14 @@ public class BassBoostTest extends Activity implements OnCheckedChangeListener {
|
|||||||
mStrength = new BassBoostParam(mBassBoost, 0, 1000, seekBar, textView);
|
mStrength = new BassBoostParam(mBassBoost, 0, 1000, seekBar, textView);
|
||||||
seekBar.setOnSeekBarChangeListener(mStrength);
|
seekBar.setOnSeekBarChangeListener(mStrength);
|
||||||
mStrength.setEnabled(mBassBoost.getStrengthSupported());
|
mStrength.setEnabled(mBassBoost.getStrengthSupported());
|
||||||
|
|
||||||
|
hammerReleaseTest.setEnabled(true);
|
||||||
|
hammerReleaseTest.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
runHammerReleaseTest(hammerReleaseTest);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,4 +279,52 @@ public class BassBoostTest extends Activity implements OnCheckedChangeListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stress-tests releasing of AudioEffect by doing repeated creation
|
||||||
|
// and subsequent releasing. Also forces emission of callbacks from
|
||||||
|
// the AudioFlinger by setting a control status listener. Since all
|
||||||
|
// effect instances are bound to the same session, the AF will
|
||||||
|
// notify them about the change in their status. This can reveal racy
|
||||||
|
// behavior w.r.t. releasing.
|
||||||
|
class HammerReleaseTest extends Thread {
|
||||||
|
private static final int NUM_EFFECTS = 10;
|
||||||
|
private static final int NUM_ITERATIONS = 100;
|
||||||
|
private final int mSession;
|
||||||
|
private final Runnable mOnComplete;
|
||||||
|
|
||||||
|
HammerReleaseTest(int session, Runnable onComplete) {
|
||||||
|
mSession = session;
|
||||||
|
mOnComplete = onComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Log.w(TAG, "HammerReleaseTest started");
|
||||||
|
BassBoost[] effects = new BassBoost[NUM_EFFECTS];
|
||||||
|
for (int i = 0; i < NUM_ITERATIONS; i++) {
|
||||||
|
for (int j = 0; j < NUM_EFFECTS; j++) {
|
||||||
|
effects[j] = new BassBoost(0, mSession);
|
||||||
|
effects[j].setControlStatusListener(mEffectListener);
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
for (int j = NUM_EFFECTS - 1; j >= 0; j--) {
|
||||||
|
Log.w(TAG, "HammerReleaseTest releasing effect " + (Object) effects[j]);
|
||||||
|
effects[j].release();
|
||||||
|
effects[j] = null;
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.w(TAG, "HammerReleaseTest ended");
|
||||||
|
runOnUiThread(mOnComplete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runHammerReleaseTest(Button controlButton) {
|
||||||
|
controlButton.setEnabled(false);
|
||||||
|
HammerReleaseTest thread = new HammerReleaseTest(sSession,
|
||||||
|
() -> {
|
||||||
|
controlButton.setEnabled(true);
|
||||||
|
});
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.effectstest;
|
package com.android.effectstest;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.media.audiofx.Visualizer;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
@@ -24,6 +25,8 @@ import android.os.Message;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.Button;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
@@ -74,11 +77,22 @@ public class VisualizerTest extends Activity implements OnCheckedChangeListener
|
|||||||
mCallbackOn = false;
|
mCallbackOn = false;
|
||||||
mCallbackButton.setChecked(mCallbackOn);
|
mCallbackButton.setChecked(mCallbackOn);
|
||||||
|
|
||||||
|
final Button hammerReleaseTest = (Button) findViewById(R.id.hammer_on_release_bug);
|
||||||
|
hammerReleaseTest.setEnabled(false);
|
||||||
|
|
||||||
mMultithreadedButton.setOnCheckedChangeListener(this);
|
mMultithreadedButton.setOnCheckedChangeListener(this);
|
||||||
if (getEffect(sSession) != null) {
|
if (getEffect(sSession) != null) {
|
||||||
mReleaseButton.setOnCheckedChangeListener(this);
|
mReleaseButton.setOnCheckedChangeListener(this);
|
||||||
mOnOffButton.setOnCheckedChangeListener(this);
|
mOnOffButton.setOnCheckedChangeListener(this);
|
||||||
mCallbackButton.setOnCheckedChangeListener(this);
|
mCallbackButton.setOnCheckedChangeListener(this);
|
||||||
|
|
||||||
|
hammerReleaseTest.setEnabled(true);
|
||||||
|
hammerReleaseTest.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
runHammerReleaseTest(hammerReleaseTest);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,4 +228,50 @@ public class VisualizerTest extends Activity implements OnCheckedChangeListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stress-tests releasing of AudioEffect by doing repeated creation
|
||||||
|
// and subsequent releasing. Unlike a similar class in BassBoostTest,
|
||||||
|
// this one doesn't sets a control status listener because Visualizer
|
||||||
|
// doesn't inherit from AudioEffect and doesn't implement this method
|
||||||
|
// by itself.
|
||||||
|
class HammerReleaseTest extends Thread {
|
||||||
|
private static final int NUM_EFFECTS = 10;
|
||||||
|
private static final int NUM_ITERATIONS = 100;
|
||||||
|
private final int mSession;
|
||||||
|
private final Runnable mOnComplete;
|
||||||
|
|
||||||
|
HammerReleaseTest(int session, Runnable onComplete) {
|
||||||
|
mSession = session;
|
||||||
|
mOnComplete = onComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Log.w(TAG, "HammerReleaseTest started");
|
||||||
|
Visualizer[] effects = new Visualizer[NUM_EFFECTS];
|
||||||
|
for (int i = 0; i < NUM_ITERATIONS; i++) {
|
||||||
|
for (int j = 0; j < NUM_EFFECTS; j++) {
|
||||||
|
effects[j] = new Visualizer(mSession);
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
for (int j = NUM_EFFECTS - 1; j >= 0; j--) {
|
||||||
|
Log.w(TAG, "HammerReleaseTest releasing effect " + (Object) effects[j]);
|
||||||
|
effects[j].release();
|
||||||
|
effects[j] = null;
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.w(TAG, "HammerReleaseTest ended");
|
||||||
|
runOnUiThread(mOnComplete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runHammerReleaseTest(Button controlButton) {
|
||||||
|
controlButton.setEnabled(false);
|
||||||
|
HammerReleaseTest thread = new HammerReleaseTest(sSession,
|
||||||
|
() -> {
|
||||||
|
controlButton.setEnabled(true);
|
||||||
|
});
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user