Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fd328170 authored by Hyundo Moon's avatar Hyundo Moon
Browse files

JAudioTrack: Add 6 more methods

This CL adds following methods in JAudioTrack:
 - getTimeStamp
 - setPlaybackRate / getPlaybackRate
 - applyVolumeShaper
 - write
 - getBufferDuartionInUs

Test: build successfully
Change-Id: I30cb80e7f1c7597c583941966a4cfa0a6a2b5f4b
parent 9b26e946
Loading
Loading
Loading
Loading
+280 −1
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@

#include <android_media_AudioErrors.h>
#include <android_runtime/AndroidRuntime.h>
#include <media/AudioResamplerPublic.h>

namespace android {

@@ -126,6 +125,131 @@ status_t JAudioTrack::getPosition(uint32_t *position) {
    return NO_ERROR;
}

bool JAudioTrack::getTimeStamp(AudioTimestamp& timestamp) {
    JNIEnv *env = AndroidRuntime::getJNIEnv();

    jclass jAudioTimeStampCls = env->FindClass("android/media/AudioTimestamp");
    jobject jAudioTimeStampObj = env->AllocObject(jAudioTimeStampCls);

    jfieldID jFramePosition = env->GetFieldID(jAudioTimeStampCls, "framePosition", "L");
    jfieldID jNanoTime = env->GetFieldID(jAudioTimeStampCls, "nanoTime", "L");

    jmethodID jGetTimestamp = env->GetMethodID(mAudioTrackCls,
            "getTimestamp", "(Landroid/media/AudioTimestamp)B");
    bool success = env->CallBooleanMethod(mAudioTrackObj, jGetTimestamp, jAudioTimeStampObj);

    if (!success) {
        return false;
    }

    long long framePosition = env->GetLongField(jAudioTimeStampObj, jFramePosition);
    long long nanoTime = env->GetLongField(jAudioTimeStampObj, jNanoTime);

    struct timespec ts;
    const long long secondToNano = 1000000000LL; // 1E9
    ts.tv_sec = nanoTime / secondToNano;
    ts.tv_nsec = nanoTime % secondToNano;
    timestamp.mTime = ts;
    timestamp.mPosition = (uint32_t) framePosition;

    return true;
}

status_t JAudioTrack::setPlaybackRate(const AudioPlaybackRate &playbackRate) {
    // TODO: existing native AudioTrack returns INVALID_OPERATION on offload/direct/fast tracks.
    // Should we do the same thing?
    JNIEnv *env = AndroidRuntime::getJNIEnv();

    jclass jPlaybackParamsCls = env->FindClass("android/media/PlaybackParams");
    jmethodID jPlaybackParamsCtor = env->GetMethodID(jPlaybackParamsCls, "<init>", "()V");
    jobject jPlaybackParamsObj = env->NewObject(jPlaybackParamsCls, jPlaybackParamsCtor);

    jmethodID jSetAudioFallbackMode = env->GetMethodID(
            jPlaybackParamsCls, "setAudioFallbackMode", "(I)Landroid/media/PlaybackParams;");
    jPlaybackParamsObj = env->CallObjectMethod(
            jPlaybackParamsObj, jSetAudioFallbackMode, playbackRate.mFallbackMode);

    jmethodID jSetAudioStretchMode = env->GetMethodID(
                jPlaybackParamsCls, "setAudioStretchMode", "(I)Landroid/media/PlaybackParams;");
    jPlaybackParamsObj = env->CallObjectMethod(
            jPlaybackParamsObj, jSetAudioStretchMode, playbackRate.mStretchMode);

    jmethodID jSetPitch = env->GetMethodID(
            jPlaybackParamsCls, "setPitch", "(F)Landroid/media/PlaybackParams;");
    jPlaybackParamsObj = env->CallObjectMethod(jPlaybackParamsObj, jSetPitch, playbackRate.mPitch);

    jmethodID jSetSpeed = env->GetMethodID(
            jPlaybackParamsCls, "setSpeed", "(F)Landroid/media/PlaybackParams;");
    jPlaybackParamsObj = env->CallObjectMethod(jPlaybackParamsObj, jSetSpeed, playbackRate.mSpeed);


    // Set this Java PlaybackParams object into Java AudioTrack.
    jmethodID jSetPlaybackParams = env->GetMethodID(
            mAudioTrackCls, "setPlaybackParams", "(Landroid/media/PlaybackParams;)V");
    env->CallVoidMethod(mAudioTrackObj, jSetPlaybackParams, jPlaybackParamsObj);
    // TODO: Should we catch the Java IllegalArgumentException?

    return NO_ERROR;
}

const AudioPlaybackRate JAudioTrack::getPlaybackRate() {
    JNIEnv *env = AndroidRuntime::getJNIEnv();

    jmethodID jGetPlaybackParams = env->GetMethodID(
            mAudioTrackCls, "getPlaybackParams", "()Landroid/media/PlaybackParams;");
    jobject jPlaybackParamsObj = env->CallObjectMethod(mAudioTrackObj, jGetPlaybackParams);

    AudioPlaybackRate playbackRate;
    jclass jPlaybackParamsCls = env->FindClass("android/media/PlaybackParams");

    jmethodID jGetAudioFallbackMode = env->GetMethodID(
            jPlaybackParamsCls, "getAudioFallbackMode", "()I");
    // TODO: Should we enable passing AUDIO_TIMESTRETCH_FALLBACK_CUT_REPEAT?
    //       The enum is internal only, so it is not defined in PlaybackParmas.java.
    // TODO: Is this right way to convert an int to an enum?
    playbackRate.mFallbackMode = static_cast<AudioTimestretchFallbackMode>(
            env->CallIntMethod(jPlaybackParamsObj, jGetAudioFallbackMode));

    jmethodID jGetAudioStretchMode = env->GetMethodID(
            jPlaybackParamsCls, "getAudioStretchMode", "()I");
    playbackRate.mStretchMode = static_cast<AudioTimestretchStretchMode>(
            env->CallIntMethod(jPlaybackParamsObj, jGetAudioStretchMode));

    jmethodID jGetPitch = env->GetMethodID(jPlaybackParamsCls, "getPitch", "()F");
    playbackRate.mPitch = env->CallFloatMethod(jPlaybackParamsObj, jGetPitch);

    jmethodID jGetSpeed = env->GetMethodID(jPlaybackParamsCls, "getSpeed", "()F");
    playbackRate.mSpeed = env->CallFloatMethod(jPlaybackParamsObj, jGetSpeed);

    return playbackRate;
}

media::VolumeShaper::Status JAudioTrack::applyVolumeShaper(
        const sp<media::VolumeShaper::Configuration>& configuration,
        const sp<media::VolumeShaper::Operation>& operation) {

    jobject jConfigurationObj = createVolumeShaperConfigurationObj(configuration);
    jobject jOperationObj = createVolumeShaperOperationObj(operation);

    if (jConfigurationObj == NULL || jOperationObj == NULL) {
        return media::VolumeShaper::Status(BAD_VALUE);
    }

    JNIEnv *env = AndroidRuntime::getJNIEnv();

    jmethodID jCreateVolumeShaper = env->GetMethodID(mAudioTrackCls, "createVolumeShaper",
            "(Landroid/media/VolumeShaper$Configuration;)Landroid/media/VolumeShaper;");
    jobject jVolumeShaperObj = env->CallObjectMethod(
            mAudioTrackObj, jCreateVolumeShaper, jConfigurationObj);

    jclass jVolumeShaperCls = env->FindClass("android/media/VolumeShaper");
    jmethodID jApply = env->GetMethodID(jVolumeShaperCls, "apply",
            "(Landroid/media/VolumeShaper$Operation;)V");
    env->CallVoidMethod(jVolumeShaperObj, jApply, jOperationObj);

    return media::VolumeShaper::Status(NO_ERROR);
}

status_t JAudioTrack::setAuxEffectSendLevel(float level) {
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    jmethodID jSetAuxEffectSendLevel = env->GetMethodID(
@@ -164,6 +288,39 @@ status_t JAudioTrack::start() {
    return NO_ERROR;
}

ssize_t JAudioTrack::write(const void* buffer, size_t size, bool blocking) {
    if (buffer == NULL) {
        return BAD_VALUE;
    }

    JNIEnv *env = AndroidRuntime::getJNIEnv();
    jbyteArray jAudioData = env->NewByteArray(size);
    env->SetByteArrayRegion(jAudioData, 0, size, (jbyte *) buffer);

    jclass jByteBufferCls = env->FindClass("java/nio/ByteBuffer");
    jmethodID jWrap = env->GetStaticMethodID(jByteBufferCls, "wrap", "([B)Ljava/nio/ByteBuffer;");
    jobject jByteBufferObj = env->CallStaticObjectMethod(jByteBufferCls, jWrap, jAudioData);

    int writeMode = 0;
    if (blocking) {
        jfieldID jWriteBlocking = env->GetStaticFieldID(mAudioTrackCls, "WRITE_BLOCKING", "I");
        writeMode = env->GetStaticIntField(mAudioTrackCls, jWriteBlocking);
    } else {
        jfieldID jWriteNonBlocking = env->GetStaticFieldID(
                mAudioTrackCls, "WRITE_NON_BLOCKING", "I");
        writeMode = env->GetStaticIntField(mAudioTrackCls, jWriteNonBlocking);
    }

    jmethodID jWrite = env->GetMethodID(mAudioTrackCls, "write", "(Ljava/nio/ByteBuffer;II)I");
    int result = env->CallIntMethod(mAudioTrackObj, jWrite, jByteBufferObj, size, writeMode);

    if (result >= 0) {
        return result;
    } else {
        return javaToNativeStatus(result);
    }
}

void JAudioTrack::stop() {
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    jmethodID jStop = env->GetMethodID(mAudioTrackCls, "stop", "()V");
@@ -210,6 +367,24 @@ uint32_t JAudioTrack::getSampleRate() {
    return env->CallIntMethod(mAudioTrackObj, jGetSampleRate);
}

status_t JAudioTrack::getBufferDurationInUs(int64_t *duration) {
    if (duration == nullptr) {
        return BAD_VALUE;
    }

    JNIEnv *env = AndroidRuntime::getJNIEnv();
    jmethodID jGetBufferSizeInFrames = env->GetMethodID(
            mAudioTrackCls, "getBufferSizeInFrames", "()I");
    int bufferSizeInFrames = env->CallIntMethod(mAudioTrackObj, jGetBufferSizeInFrames);

    const double secondToMicro = 1000000LL; // 1E6
    int sampleRate = JAudioTrack::getSampleRate();
    float speed = JAudioTrack::getPlaybackRate().mSpeed;

    *duration = (int64_t) (bufferSizeInFrames * secondToMicro / (sampleRate * speed));
    return NO_ERROR;
}

audio_format_t JAudioTrack::format() {
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    jmethodID jGetAudioFormat = env->GetMethodID(mAudioTrackCls, "getAudioFormat", "()I");
@@ -217,6 +392,110 @@ audio_format_t JAudioTrack::format() {
    return audioFormatToNative(javaFormat);
}

jobject JAudioTrack::createVolumeShaperConfigurationObj(
        const sp<media::VolumeShaper::Configuration>& config) {

    // TODO: Java VolumeShaper's setId() / setOptionFlags() are hidden.
    if (config == NULL || config->getType() == media::VolumeShaper::Configuration::TYPE_ID) {
        return NULL;
    }

    JNIEnv *env = AndroidRuntime::getJNIEnv();

    // Referenced "android_media_VolumeShaper.h".
    jfloatArray xarray = nullptr;
    jfloatArray yarray = nullptr;
    if (config->getType() == media::VolumeShaper::Configuration::TYPE_SCALE) {
        // convert curve arrays
        xarray = env->NewFloatArray(config->size());
        yarray = env->NewFloatArray(config->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 : *config.get()) {
            *xptr++ = pt.first;
            *yptr++ = pt.second;
        }
        env->ReleaseFloatArrayElements(xarray, x, 0 /* mode */);
        env->ReleaseFloatArrayElements(yarray, y, 0 /* mode */);
    }

    jclass jBuilderCls = env->FindClass("android/media/VolumeShaper$Configuration$Builder");
    jmethodID jBuilderCtor = env->GetMethodID(jBuilderCls, "<init>", "()V");
    jobject jBuilderObj = env->NewObject(jBuilderCls, jBuilderCtor);

    jmethodID jSetDuration = env->GetMethodID(jBuilderCls, "setDuration",
            "(L)Landroid/media/VolumeShaper$Configuration$Builder;");
    jBuilderObj = env->CallObjectMethod(jBuilderCls, jSetDuration, (jlong) config->getDurationMs());

    jmethodID jSetInterpolatorType = env->GetMethodID(jBuilderCls, "setInterpolatorType",
            "(I)Landroid/media/VolumeShaper$Configuration$Builder;");
    jBuilderObj = env->CallObjectMethod(jBuilderCls, jSetInterpolatorType,
            config->getInterpolatorType());

    jmethodID jSetCurve = env->GetMethodID(jBuilderCls, "setCurve",
            "([F[F)Landroid/media/VolumeShaper$Configuration$Builder;");
    jBuilderObj = env->CallObjectMethod(jBuilderCls, jSetCurve, xarray, yarray);

    jmethodID jBuild = env->GetMethodID(jBuilderCls, "build",
            "()Landroid/media/VolumeShaper$Configuration;");
    return env->CallObjectMethod(jBuilderObj, jBuild);
}

jobject JAudioTrack::createVolumeShaperOperationObj(
        const sp<media::VolumeShaper::Operation>& operation) {

    JNIEnv *env = AndroidRuntime::getJNIEnv();

    jclass jBuilderCls = env->FindClass("android/media/VolumeShaper$Operation$Builder");
    jmethodID jBuilderCtor = env->GetMethodID(jBuilderCls, "<init>", "()V");
    jobject jBuilderObj = env->NewObject(jBuilderCls, jBuilderCtor);

    // Set XOffset
    jmethodID jSetXOffset = env->GetMethodID(jBuilderCls, "setXOffset",
            "(F)Landroid/media/VolumeShaper$Operation$Builder;");
    jBuilderObj = env->CallObjectMethod(jBuilderCls, jSetXOffset, operation->getXOffset());

    int32_t flags = operation->getFlags();

    if (operation->getReplaceId() >= 0) {
        jmethodID jReplace = env->GetMethodID(jBuilderCls, "replace",
                "(IB)Landroid/media/VolumeShaper$Operation$Builder;");
        bool join = (flags | media::VolumeShaper::Operation::FLAG_JOIN) != 0;
        jBuilderObj = env->CallObjectMethod(jBuilderCls, jReplace, operation->getReplaceId(), join);
    }

    if (flags | media::VolumeShaper::Operation::FLAG_REVERSE) {
        jmethodID jReverse = env->GetMethodID(jBuilderCls, "reverse",
                "()Landroid/media/VolumeShaper$Operation$Builder;");
        jBuilderObj = env->CallObjectMethod(jBuilderCls, jReverse);
    }

    // TODO: VolumeShaper Javadoc says "Do not call terminate() directly". Can we call this?
    if (flags | media::VolumeShaper::Operation::FLAG_TERMINATE) {
        jmethodID jTerminate = env->GetMethodID(jBuilderCls, "terminate",
                "()Landroid/media/VolumeShaper$Operation$Builder;");
        jBuilderObj = env->CallObjectMethod(jBuilderCls, jTerminate);
    }

    if (flags | media::VolumeShaper::Operation::FLAG_DELAY) {
        jmethodID jDefer = env->GetMethodID(jBuilderCls, "defer",
                "()Landroid/media/VolumeShaper$Operation$Builder;");
        jBuilderObj = env->CallObjectMethod(jBuilderCls, jDefer);
    }

    if (flags | media::VolumeShaper::Operation::FLAG_CREATE_IF_NECESSARY) {
        jmethodID jCreateIfNeeded = env->GetMethodID(jBuilderCls, "createIfNeeded",
                "()Landroid/media/VolumeShaper$Operation$Builder;");
        jBuilderObj = env->CallObjectMethod(jBuilderCls, jCreateIfNeeded);
    }

    // TODO: Handle error case (can it be NULL?)
    jmethodID jBuild = env->GetMethodID(jBuilderCls, "build",
            "()Landroid/media/VolumeShaper$Operation;");
    return env->CallObjectMethod(jBuilderObj, jBuild);
}

status_t JAudioTrack::javaToNativeStatus(int javaStatus) {
    switch (javaStatus) {
    case AUDIO_JAVA_SUCCESS:
+77 −3
Original line number Diff line number Diff line
@@ -18,7 +18,13 @@
#define ANDROID_JAUDIOTRACK_H

#include <jni.h>
#include <media/AudioResamplerPublic.h>
#include <media/VolumeShaper.h>
#include <system/audio.h>
#include <utils/Errors.h>

#include <media/AudioTimestamp.h>   // It has dependency on audio.h/Errors.h, but doesn't
                                    // include them in it. Therefore it is included here at last.

namespace android {

@@ -112,8 +118,42 @@ public:
     */
    status_t getPosition(uint32_t *position);

    // TODO: Does this comment apply same to Java AudioTrack::getTimestamp?
    // Changed the return type from status_t to bool, since Java AudioTrack::getTimestamp returns
    // boolean. Will Java getTimestampWithStatus() be public?
    /* Poll for a timestamp on demand.
     * Use if EVENT_NEW_TIMESTAMP is not delivered often enough for your needs,
     * or if you need to get the most recent timestamp outside of the event callback handler.
     * Caution: calling this method too often may be inefficient;
     * if you need a high resolution mapping between frame position and presentation time,
     * consider implementing that at application level, based on the low resolution timestamps.
     * Returns true if timestamp is valid.
     * The timestamp parameter is undefined on return, if false is returned.
     */
    bool getTimeStamp(AudioTimestamp& timestamp);

    /* Set source playback rate for timestretch
     * 1.0 is normal speed: < 1.0 is slower, > 1.0 is faster
     * 1.0 is normal pitch: < 1.0 is lower pitch, > 1.0 is higher pitch
     *
     * AUDIO_TIMESTRETCH_SPEED_MIN <= speed <= AUDIO_TIMESTRETCH_SPEED_MAX
     * AUDIO_TIMESTRETCH_PITCH_MIN <= pitch <= AUDIO_TIMESTRETCH_PITCH_MAX
     *
     * Speed increases the playback rate of media, but does not alter pitch.
     * Pitch increases the "tonal frequency" of media, but does not affect the playback rate.
     */
    status_t setPlaybackRate(const AudioPlaybackRate &playbackRate);

    /* Return current playback rate */
    const AudioPlaybackRate getPlaybackRate();

    /* Sets the volume shaper object */
    media::VolumeShaper::Status applyVolumeShaper(
            const sp<media::VolumeShaper::Configuration>& configuration,
            const sp<media::VolumeShaper::Operation>& operation);

    /* Set the send level for this track. An auxiliary effect should be attached
     * to the track with attachAuxEffect(). Level must be >= 0.0 and <= 1.0.
     * to the track with attachEffect(). Level must be >= 0.0 and <= 1.0.
     */
    status_t setAuxEffectSendLevel(float level);

@@ -126,8 +166,8 @@ public:
     *
     * Returned status (from utils/Errors.h) can be:
     *  - NO_ERROR: successful operation
     *  - INVALID_OPERATION: the effect is not an auxiliary effect.
     *  - BAD_VALUE: The specified effect ID is invalid
     *  - INVALID_OPERATION: The effect is not an auxiliary effect.
     *  - BAD_VALUE: The specified effect ID is invalid.
     */
    status_t attachAuxEffect(int effectId);

@@ -149,6 +189,29 @@ public:
     */
    status_t start();

    // TODO: Does this comment still applies? It seems not. (obtainBuffer, AudioFlinger, ...)
    /* As a convenience we provide a write() interface to the audio buffer.
     * Input parameter 'size' is in byte units.
     * This is implemented on top of obtainBuffer/releaseBuffer. For best
     * performance use callbacks. Returns actual number of bytes written >= 0,
     * or one of the following negative status codes:
     *      INVALID_OPERATION   AudioTrack is configured for static buffer or streaming mode
     *      BAD_VALUE           size is invalid
     *      WOULD_BLOCK         when obtainBuffer() returns same, or
     *                          AudioTrack was stopped during the write
     *      DEAD_OBJECT         when AudioFlinger dies or the output device changes and
     *                          the track cannot be automatically restored.
     *                          The application needs to recreate the AudioTrack
     *                          because the audio device changed or AudioFlinger died.
     *                          This typically occurs for direct or offload tracks
     *                          or if mDoNotReconnect is true.
     *      or any other error code returned by IAudioTrack::start() or restoreTrack_l().
     * Default behavior is to only return when all data has been transferred. Set 'blocking' to
     * false for the method to return immediately without waiting to try multiple times to write
     * the full content of the buffer.
     */
    ssize_t write(const void* buffer, size_t size, bool blocking = true);

    // TODO: Does this comment equally apply to the Java AudioTrack::stop()?
    /* Stop a track.
     * In static buffer mode, the track is stopped immediately.
@@ -185,12 +248,23 @@ public:
     */
    uint32_t getSampleRate();

    /* Returns the buffer duration in microseconds at current playback rate. */
    status_t getBufferDurationInUs(int64_t *duration);

    audio_format_t format();

private:
    jclass mAudioTrackCls;
    jobject mAudioTrackObj;

    /* Creates a Java VolumeShaper.Configuration object from VolumeShaper::Configuration */
    jobject createVolumeShaperConfigurationObj(
            const sp<media::VolumeShaper::Configuration>& config);

    /* Creates a Java VolumeShaper.Operation object from VolumeShaper::Operation */
    jobject createVolumeShaperOperationObj(
            const sp<media::VolumeShaper::Operation>& operation);

    status_t javaToNativeStatus(int javaStatus);
};