int SDL_Android_AudioTrack_reserve_float_buffer(JNIEnv *env, SDL_Android_AudioTrack *atrack, int size_in_float)
{
    if (atrack->float_buffer && size_in_float <= atrack->float_buffer_capacity)
        return size_in_float;

    if (atrack->float_buffer) {
        (*env)->DeleteGlobalRef(env, atrack->float_buffer);
        atrack->float_buffer = NULL;
        atrack->float_buffer_capacity = 0;
    }

    int capacity = IJKMAX(size_in_float, ((atrack->min_buffer_size + sizeof(jfloat) - 1) / sizeof(jfloat)));
    jbyteArray float_buffer = (*env)->NewFloatArray(env, capacity);
    if (!float_buffer || (*env)->ExceptionCheck(env)) {
        ALOGE("%s: NewFloatArray: Exception:\n", __func__);
        if ((*env)->ExceptionCheck(env)) {
            (*env)->ExceptionDescribe(env);
            (*env)->ExceptionClear(env);
        }
        return -1;
    }

    atrack->float_buffer_capacity = capacity;
    atrack->float_buffer = (*env)->NewGlobalRef(env, float_buffer);
    (*env)->DeleteLocalRef(env, float_buffer);
    return capacity;
}
int SDL_Android_AudioTrack_reserve_byte_buffer(JNIEnv *env, SDL_Android_AudioTrack *atrack, int size_in_byte)
{
    if (atrack->byte_buffer && size_in_byte <= atrack->byte_buffer_capacity)
        return size_in_byte;

    if (atrack->byte_buffer) {
        (*env)->DeleteGlobalRef(env, atrack->byte_buffer);
        atrack->byte_buffer = NULL;
        atrack->byte_buffer_capacity = 0;
    }

    int capacity = IJKMAX(size_in_byte, atrack->min_buffer_size);
    jbyteArray byte_buffer = (*env)->NewByteArray(env, capacity);
    if (!byte_buffer || (*env)->ExceptionCheck(env)) {
        ALOGE("%s: NewByteArray: Exception:", __func__);
        if ((*env)->ExceptionCheck(env)) {
            (*env)->ExceptionDescribe(env);
            (*env)->ExceptionClear(env);
        }
        return -1;
    }

    atrack->byte_buffer_capacity = capacity;
    atrack->byte_buffer = (*env)->NewGlobalRef(env, byte_buffer);
    (*env)->DeleteLocalRef(env, byte_buffer);
    return capacity;
}
int SDL_Android_AudioTrack_reserve_byte_buffer(JNIEnv *env, SDL_Android_AudioTrack *atrack, int size_in_byte)
{
    if (atrack->byte_buffer && size_in_byte <= atrack->byte_buffer_capacity)
        return size_in_byte;

    JJK_DeleteGlobalRef__p(env, &atrack->byte_buffer);
    atrack->byte_buffer_capacity = 0;

    int capacity = IJKMAX(size_in_byte, atrack->min_buffer_size);
    atrack->byte_buffer = JJK_NewByteArray__asGlobalRef__catchAll(env, capacity);
    if (!atrack->byte_buffer)
        return -1;

    atrack->byte_buffer_capacity = capacity;
    return capacity;
}
SDL_Android_AudioTrack *SDL_Android_AudioTrack_new_from_spec(JNIEnv *env, SDL_Android_AudioTrack_Spec *spec)
{
    assert(spec);
    jint sdk_int = SDL_Android_GetApiLevel();

    switch (spec->channel_config) {
    case CHANNEL_OUT_MONO:
        ALOGI("SDL_Android_AudioTrack: %s", "CHANNEL_OUT_MONO");
        break;
    case CHANNEL_OUT_STEREO:
        ALOGI("SDL_Android_AudioTrack: %s", "CHANNEL_OUT_STEREO");
        break;
    default:
        ALOGE("SDL_Android_AudioTrack_new_from_spec: invalid channel %d", spec->channel_config);
        return NULL;
    }

    switch (spec->audio_format) {
    case ENCODING_PCM_16BIT:
        ALOGI("SDL_Android_AudioTrack: %s", "ENCODING_PCM_16BIT");
        break;
    case ENCODING_PCM_8BIT:
        ALOGI("SDL_Android_AudioTrack: %s", "ENCODING_PCM_8BIT");
        break;
    case ENCODING_PCM_FLOAT:
        ALOGI("SDL_Android_AudioTrack: %s", "ENCODING_PCM_FLOAT");
        if (sdk_int < IJK_API_21_LOLLIPOP) {
            ALOGI("SDL_Android_AudioTrack: %s need API 21 or above", "ENCODING_PCM_FLOAT");
            return NULL;
        }
        break;
    default:
        ALOGE("SDL_Android_AudioTrack_new_from_spec: invalid format %d", spec->audio_format);
        return NULL;
    }

    SDL_Android_AudioTrack *atrack = (SDL_Android_AudioTrack*) mallocz(sizeof(SDL_Android_AudioTrack));
    if (!atrack) {
        (*env)->CallVoidMethod(env, atrack->thiz, g_clazz.release);
        return NULL;
    }
    atrack->spec = *spec;

    if (atrack->spec.sample_rate_in_hz < 4000 || atrack->spec.sample_rate_in_hz > 48000) {
        int native_sample_rate_in_hz = audiotrack_get_native_output_sample_rate(env);
        if (native_sample_rate_in_hz > 0) {
            ALOGE("SDL_Android_AudioTrack_new: cast sample rate %d to %d:",
                atrack->spec.sample_rate_in_hz,
                native_sample_rate_in_hz);
            atrack->spec.sample_rate_in_hz = native_sample_rate_in_hz;
        }
    }

    int min_buffer_size = audiotrack_get_min_buffer_size(env, &atrack->spec);
    if (min_buffer_size <= 0) {
        ALOGE("SDL_Android_AudioTrack_new: SDL_Android_AudioTrack_get_min_buffer_size: return %d:", min_buffer_size);
        free(atrack);
        return NULL;
    }

    jobject thiz = (*env)->NewObject(env, g_clazz.clazz, g_clazz.constructor,
        (int) atrack->spec.stream_type,
        (int) atrack->spec.sample_rate_in_hz,
        (int) atrack->spec.channel_config,
        (int) atrack->spec.audio_format,
        (int) min_buffer_size,
        (int) atrack->spec.mode);
    if (!thiz || (*env)->ExceptionCheck(env)) {
        ALOGE("SDL_Android_AudioTrack_new: NewObject: Exception:");
        if ((*env)->ExceptionCheck(env)) {
            (*env)->ExceptionDescribe(env);
            (*env)->ExceptionClear(env);
        }
        free(atrack);
        return NULL;
    }

    atrack->min_buffer_size = min_buffer_size;
    atrack->spec.buffer_size_in_bytes = min_buffer_size;
    atrack->max_volume = audiotrack_get_max_volume(env);
    atrack->min_volume = audiotrack_get_min_volume(env);

    atrack->thiz = (*env)->NewGlobalRef(env, thiz);
    (*env)->DeleteLocalRef(env, thiz);

    // extra init
    float init_volume = 1.0f;
    init_volume = IJKMIN(init_volume, atrack->max_volume);
    init_volume = IJKMAX(init_volume, atrack->min_volume);
    ALOGI("SDL_Android_AudioTrack_new: init volume as %f/(%f,%f)", init_volume, atrack->min_volume, atrack->max_volume);
    audiotrack_set_stereo_volume(env, atrack, init_volume, init_volume);

    return atrack;
}
SDL_Android_AudioTrack *SDL_Android_AudioTrack_new_from_spec(JNIEnv *env, SDL_Android_AudioTrack_Spec *spec)
{
    assert(spec);

    switch (spec->channel_config) {
    case CHANNEL_OUT_MONO:
        ALOGI("SDL_Android_AudioTrack: %s", "CHANNEL_OUT_MONO");
        break;
    case CHANNEL_OUT_STEREO:
        ALOGI("SDL_Android_AudioTrack: %s", "CHANNEL_OUT_STEREO");
        break;
    default:
        ALOGE("%s: invalid channel %d", __func__, spec->channel_config);
        return NULL;
    }

    switch (spec->audio_format) {
    case ENCODING_PCM_16BIT:
        ALOGI("SDL_Android_AudioTrack: %s", "ENCODING_PCM_16BIT");
        break;
    case ENCODING_PCM_8BIT:
        ALOGI("SDL_Android_AudioTrack: %s", "ENCODING_PCM_8BIT");
        break;
#if 0
    case ENCODING_PCM_FLOAT:
        ALOGI("SDL_Android_AudioTrack: %s", "ENCODING_PCM_FLOAT");
        if (sdk_int < IJK_API_21_LOLLIPOP) {
            ALOGI("SDL_Android_AudioTrack: %s need API 21 or above", "ENCODING_PCM_FLOAT");
            return NULL;
        }
        break;
#endif
    default:
        ALOGE("%s: invalid format %d", __func__, spec->audio_format);
        return NULL;
    }

    if (spec->sample_rate_in_hz <= 0) {
        ALOGE("%s: invalid sample rate %d", __func__, spec->sample_rate_in_hz);
        return NULL;
    }

    SDL_Android_AudioTrack *atrack = (SDL_Android_AudioTrack*) mallocz(sizeof(SDL_Android_AudioTrack));
    if (!atrack) {
        ALOGE("%s: mallocz faild.\n", __func__);
        return NULL;
    }
    atrack->spec = *spec;

    // libswresample is ugly, depending on native resampler
    while (atrack->spec.sample_rate_in_hz < 4000) {
        atrack->spec.sample_rate_in_hz *= 2;
    }
    while (atrack->spec.sample_rate_in_hz > 48000) {
        atrack->spec.sample_rate_in_hz /= 2;   
    }

    int min_buffer_size = JJKC_AudioTrack__getMinBufferSize(env,
        atrack->spec.sample_rate_in_hz,
        atrack->spec.channel_config,
        atrack->spec.audio_format);
    if (JJK_ExceptionCheck__catchAll(env) || min_buffer_size <= 0) {
        ALOGE("%s: JJKC_AudioTrack__getMinBufferSize: return %d:", __func__, min_buffer_size);
        free(atrack);
        return NULL;
    }

    if (JJK_GetSystemAndroidApiLevel(env) >= 23) {
        // for fast playback
        min_buffer_size *= 2;
    }

    atrack->thiz = JJKC_AudioTrack__AudioTrack__asGlobalRef__catchAll(env, 
        atrack->spec.stream_type,
        atrack->spec.sample_rate_in_hz,
        atrack->spec.channel_config,
        atrack->spec.audio_format,
        min_buffer_size,
        atrack->spec.mode);
    if (!atrack->thiz) {
        free(atrack);
        return NULL;
    }

    atrack->min_buffer_size = min_buffer_size;
    atrack->spec.buffer_size_in_bytes = min_buffer_size;
    // atrack->max_volume = JJKC_AudioTrack__getMaxVolume__catchAll(env);
    // atrack->min_volume = JJKC_AudioTrack__getMinVolume__catchAll(env);
    atrack->max_volume = 0.0f;
    atrack->min_volume = 1.0f;

    // extra init
    float init_volume = 1.0f;
    init_volume = IJKMIN(init_volume, atrack->max_volume);
    init_volume = IJKMAX(init_volume, atrack->min_volume);
    ALOGI("%s: init volume as %f/(%f,%f)", __func__, init_volume, atrack->min_volume, atrack->max_volume);
    JJKC_AudioTrack__setStereoVolume__catchAll(env, atrack->thiz, init_volume, init_volume);

    return atrack;
}