VolumeShaper: Initial implementation

The VolumeShaper is used to apply a volume
envelope to an AudioTrack or a MediaPlayer.

Test: CTS
Bug: 30920125
Bug: 31015569
Change-Id: If8b4bed29760aa3bd15a4b54cae60e40b4f518ee
diff --git a/media/jni/android_media_VolumeShaper.h b/media/jni/android_media_VolumeShaper.h
new file mode 100644
index 0000000..dbbc478
--- /dev/null
+++ b/media/jni/android_media_VolumeShaper.h
@@ -0,0 +1,221 @@
+/*
+ * 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 _ANDROID_MEDIA_VOLUME_SHAPER_H_
+#define _ANDROID_MEDIA_VOLUME_SHAPER_H_
+
+#include <media/VolumeShaper.h>
+
+namespace android {
+
+// This entire class is inline as it is used from both core and media
+struct VolumeShaperHelper {
+    struct fields_t {
+        // VolumeShaper.Configuration
+        jclass    coClazz;
+        jmethodID coConstructId;
+        jfieldID  coTypeId;
+        jfieldID  coIdId;
+        jfieldID  coInterpolatorTypeId;
+        jfieldID  coOptionFlagsId;
+        jfieldID  coDurationMsId;
+        jfieldID  coTimesId;
+        jfieldID  coVolumesId;
+
+        // VolumeShaper.Operation
+        jclass    opClazz;
+        jmethodID opConstructId;
+        jfieldID  opFlagsId;
+        jfieldID  opReplaceIdId;
+
+        // VolumeShaper.State
+        jclass    stClazz;
+        jmethodID stConstructId;
+        jfieldID  stVolumeId;
+        jfieldID  stXOffsetId;
+
+        void init(JNIEnv *env) {
+            jclass lclazz = env->FindClass("android/media/VolumeShaper$Configuration");
+            if (lclazz == nullptr) {
+                return;
+            }
+            coClazz = (jclass)env->NewGlobalRef(lclazz);
+            if (coClazz == nullptr) {
+                return;
+            }
+            coConstructId = env->GetMethodID(coClazz, "<init>", "(IIIID[F[F)V");
+            coTypeId = env->GetFieldID(coClazz, "mType", "I");
+            coIdId = env->GetFieldID(coClazz, "mId", "I");
+            coInterpolatorTypeId = env->GetFieldID(coClazz, "mInterpolatorType", "I");
+            coOptionFlagsId = env->GetFieldID(coClazz, "mOptionFlags", "I");
+            coDurationMsId = env->GetFieldID(coClazz, "mDurationMs", "D");
+            coTimesId = env->GetFieldID(coClazz, "mTimes", "[F");
+            coVolumesId = env->GetFieldID(coClazz, "mVolumes", "[F");
+            env->DeleteLocalRef(lclazz);
+
+            lclazz = env->FindClass("android/media/VolumeShaper$Operation");
+            if (lclazz == nullptr) {
+                return;
+            }
+            opClazz = (jclass)env->NewGlobalRef(lclazz);
+            if (opClazz == nullptr) {
+                return;
+            }
+            opConstructId = env->GetMethodID(opClazz, "<init>", "(II)V");
+            opFlagsId = env->GetFieldID(opClazz, "mFlags", "I");
+            opReplaceIdId = env->GetFieldID(opClazz, "mReplaceId", "I");
+            env->DeleteLocalRef(lclazz);
+
+            lclazz = env->FindClass("android/media/VolumeShaper$State");
+            if (lclazz == nullptr) {
+                return;
+            }
+            stClazz = (jclass)env->NewGlobalRef(lclazz);
+            if (stClazz == nullptr) {
+                return;
+            }
+            stConstructId = env->GetMethodID(stClazz, "<init>", "(FF)V");
+            stVolumeId = env->GetFieldID(stClazz, "mVolume", "F");
+            stXOffsetId = env->GetFieldID(stClazz, "mXOffset", "F");
+            env->DeleteLocalRef(lclazz);
+        }
+
+        void exit(JNIEnv *env) {
+            env->DeleteGlobalRef(coClazz);
+            coClazz = nullptr;
+        }
+    };
+
+    static sp<VolumeShaper::Configuration> convertJobjectToConfiguration(
+            JNIEnv *env, const fields_t &fields, jobject jshaper) {
+        sp<VolumeShaper::Configuration> configuration = new VolumeShaper::Configuration();
+
+        configuration->setType(
+            (VolumeShaper::Configuration::Type)env->GetIntField(jshaper, fields.coTypeId));
+        configuration->setId(
+            (int)env->GetIntField(jshaper, fields.coIdId));
+        if (configuration->getType() == VolumeShaper::Configuration::TYPE_SCALE) {
+            configuration->setInterpolatorType(
+                (VolumeShaper::Configuration::InterpolatorType)
+                env->GetIntField(jshaper, fields.coInterpolatorTypeId));
+            configuration->setOptionFlags(
+                (VolumeShaper::Configuration::OptionFlag)
+                env->GetIntField(jshaper, fields.coOptionFlagsId));
+            configuration->setDurationMs(
+                    (double)env->GetDoubleField(jshaper, fields.coDurationMsId));
+
+            // convert point arrays
+            jobject xobj = env->GetObjectField(jshaper, fields.coTimesId);
+            jfloatArray *xarray = reinterpret_cast<jfloatArray*>(&xobj);
+            jsize xlen = env->GetArrayLength(*xarray);
+            /* const */ float * const x =
+                    env->GetFloatArrayElements(*xarray, nullptr /* isCopy */);
+            jobject yobj = env->GetObjectField(jshaper, fields.coVolumesId);
+            jfloatArray *yarray = reinterpret_cast<jfloatArray*>(&yobj);
+            jsize ylen = env->GetArrayLength(*yarray);
+            /* const */ float * const y =
+                    env->GetFloatArrayElements(*yarray, nullptr /* isCopy */);
+            if (xlen != ylen) {
+                ALOGE("array size must match");
+                return nullptr;
+            }
+            for (jsize i = 0; i < xlen; ++i) {
+                configuration->emplace(x[i], y[i]);
+            }
+            env->ReleaseFloatArrayElements(*xarray, x, JNI_ABORT); // no need to copy back
+            env->ReleaseFloatArrayElements(*yarray, y, JNI_ABORT);
+        }
+        return configuration;
+    }
+
+    static jobject convertVolumeShaperToJobject(
+            JNIEnv *env, const fields_t &fields,
+            const sp<VolumeShaper::Configuration> &configuration) {
+        jfloatArray xarray = nullptr;
+        jfloatArray yarray = nullptr;
+        if (configuration->getType() == VolumeShaper::Configuration::TYPE_SCALE) {
+            // convert curve arrays
+            jfloatArray xarray = env->NewFloatArray(configuration->size());
+            jfloatArray yarray = env->NewFloatArray(configuration->size());
+            float * const x = env->GetFloatArrayElements(xarray, nullptr /* isCopy */);
+            float * const y = env->GetFloatArrayElements(yarray, nullptr /* isCopy */);
+            float *xptr = x, *yptr = y;
+            for (const auto &pt : *configuration.get()) {
+                *xptr++ = pt.first;
+                *yptr++ = pt.second;
+            }
+            env->ReleaseFloatArrayElements(xarray, x, 0 /* mode */);
+            env->ReleaseFloatArrayElements(yarray, y, 0 /* mode */);
+        }
+
+        // prepare constructor args
+        jvalue args[7];
+        args[0].i = (jint)configuration->getType();
+        args[1].i = (jint)configuration->getId();
+        args[2].i = (jint)configuration->getInterpolatorType();
+        args[3].i = (jint)configuration->getOptionFlags();
+        args[4].d = (jdouble)configuration->getDurationMs();
+        args[5].l = xarray;
+        args[6].l = yarray;
+        jobject jshaper = env->NewObjectA(fields.coClazz, fields.coConstructId, args);
+        return jshaper;
+    }
+
+    static sp<VolumeShaper::Operation> convertJobjectToOperation(
+            JNIEnv *env, const fields_t &fields, jobject joperation) {
+        VolumeShaper::Operation::Flag flags =
+            (VolumeShaper::Operation::Flag)env->GetIntField(joperation, fields.opFlagsId);
+        int replaceId = env->GetIntField(joperation, fields.opReplaceIdId);
+
+        sp<VolumeShaper::Operation> operation = new VolumeShaper::Operation(flags, replaceId);
+        return operation;
+    }
+
+    static jobject convertOperationToJobject(
+            JNIEnv *env, const fields_t &fields, const sp<VolumeShaper::Operation> &operation) {
+        // prepare constructor args
+        jvalue args[2];
+        args[0].i = (jint)operation->getFlags();
+        args[1].i = (jint)operation->getReplaceId();
+
+        jobject joperation = env->NewObjectA(fields.opClazz, fields.opConstructId, args);
+        return joperation;
+    }
+
+    static sp<VolumeShaper::State> convertJobjectToState(
+            JNIEnv *env, const fields_t &fields, jobject jstate) {
+        float volume = env->GetFloatField(jstate, fields.stVolumeId);
+        float xOffset = env->GetFloatField(jstate, fields.stXOffsetId);
+
+        sp<VolumeShaper::State> state = new VolumeShaper::State(volume, xOffset);
+        return state;
+    }
+
+    static jobject convertStateToJobject(
+            JNIEnv *env, const fields_t &fields, const sp<VolumeShaper::State> &state) {
+        // prepare constructor args
+        jvalue args[2];
+        args[0].f = (jfloat)state->getVolume();
+        args[1].f = (jfloat)state->getXOffset();
+
+        jobject jstate = env->NewObjectA(fields.stClazz, fields.stConstructId, args);
+        return jstate;
+    }
+};
+
+}  // namespace android
+
+#endif // _ANDROID_MEDIA_VOLUME_SHAPER_H_