568 lines
17 KiB
C++
568 lines
17 KiB
C++
/*
|
|
* Copyright 2015, 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.
|
|
*/
|
|
|
|
//#define LOG_NDEBUG 0
|
|
#define LOG_TAG "MediaSync-JNI"
|
|
#include <utils/Log.h>
|
|
|
|
#include "android_media_MediaSync.h"
|
|
|
|
#include "android_media_AudioTrack.h"
|
|
#include "android_media_PlaybackParams.h"
|
|
#include "android_media_SyncParams.h"
|
|
#include "android_runtime/AndroidRuntime.h"
|
|
#include "android_runtime/android_view_Surface.h"
|
|
#include "jni.h"
|
|
#include <nativehelper/JNIHelp.h>
|
|
|
|
#include <gui/Surface.h>
|
|
|
|
#include <media/AudioResamplerPublic.h>
|
|
#include <media/AudioTrack.h>
|
|
#include <media/stagefright/MediaClock.h>
|
|
#include <media/stagefright/MediaSync.h>
|
|
#include <media/stagefright/foundation/ADebug.h>
|
|
#include <media/stagefright/foundation/AString.h>
|
|
|
|
#include <nativehelper/ScopedLocalRef.h>
|
|
|
|
namespace android {
|
|
|
|
struct fields_t {
|
|
jfieldID context;
|
|
jfieldID mediaTimestampMediaTimeUsID;
|
|
jfieldID mediaTimestampNanoTimeID;
|
|
jfieldID mediaTimestampClockRateID;
|
|
};
|
|
|
|
static fields_t gFields;
|
|
static PlaybackParams::fields_t gPlaybackParamsFields;
|
|
static SyncParams::fields_t gSyncParamsFields;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
JMediaSync::JMediaSync() {
|
|
mSync = MediaSync::create();
|
|
}
|
|
|
|
JMediaSync::~JMediaSync() {
|
|
}
|
|
|
|
status_t JMediaSync::setSurface(const sp<IGraphicBufferProducer> &bufferProducer) {
|
|
return mSync->setSurface(bufferProducer);
|
|
}
|
|
|
|
status_t JMediaSync::setAudioTrack(const sp<AudioTrack> &audioTrack) {
|
|
return mSync->setAudioTrack(audioTrack);
|
|
}
|
|
|
|
status_t JMediaSync::createInputSurface(
|
|
sp<IGraphicBufferProducer>* bufferProducer) {
|
|
return mSync->createInputSurface(bufferProducer);
|
|
}
|
|
|
|
sp<const MediaClock> JMediaSync::getMediaClock() {
|
|
return mSync->getMediaClock();
|
|
}
|
|
|
|
status_t JMediaSync::setPlaybackParams(const AudioPlaybackRate& rate) {
|
|
return mSync->setPlaybackSettings(rate);
|
|
}
|
|
|
|
void JMediaSync::getPlaybackParams(AudioPlaybackRate* rate /* nonnull */) {
|
|
mSync->getPlaybackSettings(rate);
|
|
}
|
|
|
|
status_t JMediaSync::setSyncParams(const AVSyncSettings& syncParams) {
|
|
return mSync->setSyncSettings(syncParams);
|
|
}
|
|
|
|
void JMediaSync::getSyncParams(AVSyncSettings* syncParams /* nonnull */) {
|
|
mSync->getSyncSettings(syncParams);
|
|
}
|
|
|
|
status_t JMediaSync::setVideoFrameRateHint(float rate) {
|
|
return mSync->setVideoFrameRateHint(rate);
|
|
}
|
|
|
|
float JMediaSync::getVideoFrameRate() {
|
|
return mSync->getVideoFrameRate();
|
|
}
|
|
|
|
void JMediaSync::flush() {
|
|
mSync->flush();
|
|
}
|
|
|
|
status_t JMediaSync::updateQueuedAudioData(
|
|
int sizeInBytes, int64_t presentationTimeUs) {
|
|
return mSync->updateQueuedAudioData(sizeInBytes, presentationTimeUs);
|
|
}
|
|
|
|
status_t JMediaSync::getPlayTimeForPendingAudioFrames(int64_t *outTimeUs) {
|
|
return mSync->getPlayTimeForPendingAudioFrames(outTimeUs);
|
|
}
|
|
|
|
} // namespace android
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
using namespace android;
|
|
|
|
static sp<JMediaSync> setMediaSync(JNIEnv *env, jobject thiz, const sp<JMediaSync> &sync) {
|
|
sp<JMediaSync> old = (JMediaSync *)env->GetLongField(thiz, gFields.context);
|
|
if (sync != NULL) {
|
|
sync->incStrong(thiz);
|
|
}
|
|
if (old != NULL) {
|
|
old->decStrong(thiz);
|
|
}
|
|
|
|
env->SetLongField(thiz, gFields.context, (jlong)sync.get());
|
|
|
|
return old;
|
|
}
|
|
|
|
static sp<JMediaSync> getMediaSync(JNIEnv *env, jobject thiz) {
|
|
return (JMediaSync *)env->GetLongField(thiz, gFields.context);
|
|
}
|
|
|
|
static void android_media_MediaSync_release(JNIEnv *env, jobject thiz) {
|
|
setMediaSync(env, thiz, NULL);
|
|
}
|
|
|
|
static void throwExceptionAsNecessary(
|
|
JNIEnv *env, status_t err, const char *msg = NULL) {
|
|
switch (err) {
|
|
case NO_ERROR:
|
|
break;
|
|
|
|
case BAD_VALUE:
|
|
jniThrowException(env, "java/lang/IllegalArgumentException", msg);
|
|
break;
|
|
|
|
case NO_INIT:
|
|
case INVALID_OPERATION:
|
|
default:
|
|
if (err > 0) {
|
|
break;
|
|
}
|
|
AString msgWithErrorCode(msg == NULL ? "" : msg);
|
|
msgWithErrorCode.append(" error:");
|
|
msgWithErrorCode.append(err);
|
|
jniThrowException(env, "java/lang/IllegalStateException", msgWithErrorCode.c_str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void android_media_MediaSync_native_setSurface(
|
|
JNIEnv *env, jobject thiz, jobject jsurface) {
|
|
ALOGV("android_media_MediaSync_setSurface");
|
|
|
|
sp<JMediaSync> sync = getMediaSync(env, thiz);
|
|
if (sync == NULL) {
|
|
throwExceptionAsNecessary(env, INVALID_OPERATION);
|
|
return;
|
|
}
|
|
|
|
sp<IGraphicBufferProducer> bufferProducer;
|
|
if (jsurface != NULL) {
|
|
sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
|
|
if (surface != NULL) {
|
|
bufferProducer = surface->getIGraphicBufferProducer();
|
|
} else {
|
|
throwExceptionAsNecessary(env, BAD_VALUE, "The surface has been released");
|
|
return;
|
|
}
|
|
}
|
|
|
|
status_t err = sync->setSurface(bufferProducer);
|
|
|
|
if (err == INVALID_OPERATION) {
|
|
throwExceptionAsNecessary(
|
|
env, INVALID_OPERATION, "Surface has already been configured");
|
|
} if (err != NO_ERROR) {
|
|
AString msg("Failed to connect to surface with error ");
|
|
msg.append(err);
|
|
throwExceptionAsNecessary(env, BAD_VALUE, msg.c_str());
|
|
}
|
|
}
|
|
|
|
static void android_media_MediaSync_native_setAudioTrack(
|
|
JNIEnv *env, jobject thiz, jobject jaudioTrack) {
|
|
ALOGV("android_media_MediaSync_setAudioTrack");
|
|
|
|
sp<JMediaSync> sync = getMediaSync(env, thiz);
|
|
if (sync == NULL) {
|
|
throwExceptionAsNecessary(env, INVALID_OPERATION);
|
|
return;
|
|
}
|
|
|
|
sp<AudioTrack> audioTrack;
|
|
if (jaudioTrack != NULL) {
|
|
audioTrack = android_media_AudioTrack_getAudioTrack(env, jaudioTrack);
|
|
if (audioTrack == NULL) {
|
|
throwExceptionAsNecessary(env, BAD_VALUE, "The audio track has been released");
|
|
return;
|
|
}
|
|
}
|
|
|
|
status_t err = sync->setAudioTrack(audioTrack);
|
|
|
|
if (err == INVALID_OPERATION) {
|
|
throwExceptionAsNecessary(
|
|
env, INVALID_OPERATION, "Audio track has already been configured");
|
|
} if (err != NO_ERROR) {
|
|
AString msg("Failed to configure audio track with error ");
|
|
msg.append(err);
|
|
throwExceptionAsNecessary(env, BAD_VALUE, msg.c_str());
|
|
}
|
|
}
|
|
|
|
static jobject android_media_MediaSync_createInputSurface(
|
|
JNIEnv* env, jobject thiz) {
|
|
ALOGV("android_media_MediaSync_createInputSurface");
|
|
|
|
sp<JMediaSync> sync = getMediaSync(env, thiz);
|
|
if (sync == NULL) {
|
|
throwExceptionAsNecessary(env, INVALID_OPERATION);
|
|
return NULL;
|
|
}
|
|
|
|
// Tell the MediaSync that we want to use a Surface as input.
|
|
sp<IGraphicBufferProducer> bufferProducer;
|
|
status_t err = sync->createInputSurface(&bufferProducer);
|
|
if (err != NO_ERROR) {
|
|
throwExceptionAsNecessary(env, INVALID_OPERATION);
|
|
return NULL;
|
|
}
|
|
|
|
// Wrap the IGBP in a Java-language Surface.
|
|
return android_view_Surface_createFromIGraphicBufferProducer(env,
|
|
bufferProducer);
|
|
}
|
|
|
|
static void android_media_MediaSync_native_updateQueuedAudioData(
|
|
JNIEnv *env, jobject thiz, jint sizeInBytes, jlong presentationTimeUs) {
|
|
sp<JMediaSync> sync = getMediaSync(env, thiz);
|
|
if (sync == NULL) {
|
|
throwExceptionAsNecessary(env, INVALID_OPERATION);
|
|
return;
|
|
}
|
|
|
|
status_t err = sync->updateQueuedAudioData(sizeInBytes, presentationTimeUs);
|
|
if (err != NO_ERROR) {
|
|
throwExceptionAsNecessary(env, err);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static jboolean android_media_MediaSync_native_getTimestamp(
|
|
JNIEnv *env, jobject thiz, jobject timestamp) {
|
|
sp<JMediaSync> sync = getMediaSync(env, thiz);
|
|
if (sync == NULL) {
|
|
throwExceptionAsNecessary(env, INVALID_OPERATION);
|
|
return JNI_FALSE;
|
|
}
|
|
|
|
sp<const MediaClock> mediaClock = sync->getMediaClock();
|
|
if (mediaClock == NULL) {
|
|
return JNI_FALSE;
|
|
}
|
|
|
|
int64_t nowUs = ALooper::GetNowUs();
|
|
int64_t mediaUs = 0;
|
|
if (mediaClock->getMediaTime(nowUs, &mediaUs) != OK) {
|
|
return JNI_FALSE;
|
|
}
|
|
|
|
env->SetLongField(timestamp, gFields.mediaTimestampMediaTimeUsID,
|
|
(jlong)mediaUs);
|
|
env->SetLongField(timestamp, gFields.mediaTimestampNanoTimeID,
|
|
(jlong)(nowUs * 1000));
|
|
env->SetFloatField(timestamp, gFields.mediaTimestampClockRateID,
|
|
(jfloat)mediaClock->getPlaybackRate());
|
|
return JNI_TRUE;
|
|
}
|
|
|
|
static jlong android_media_MediaSync_native_getPlayTimeForPendingAudioFrames(
|
|
JNIEnv *env, jobject thiz) {
|
|
sp<JMediaSync> sync = getMediaSync(env, thiz);
|
|
if (sync == NULL) {
|
|
throwExceptionAsNecessary(env, INVALID_OPERATION);
|
|
}
|
|
|
|
int64_t playTimeUs = 0;
|
|
status_t err = sync->getPlayTimeForPendingAudioFrames(&playTimeUs);
|
|
if (err != NO_ERROR) {
|
|
throwExceptionAsNecessary(env, err);
|
|
}
|
|
return (jlong)playTimeUs;
|
|
}
|
|
|
|
static jfloat android_media_MediaSync_setPlaybackParams(
|
|
JNIEnv *env, jobject thiz, jobject params) {
|
|
sp<JMediaSync> sync = getMediaSync(env, thiz);
|
|
if (sync == NULL) {
|
|
throwExceptionAsNecessary(env, INVALID_OPERATION);
|
|
return (jfloat)0.f;
|
|
}
|
|
|
|
PlaybackParams pbs;
|
|
pbs.fillFromJobject(env, gPlaybackParamsFields, params);
|
|
ALOGV("setPlaybackParams: %d:%f %d:%f %d:%u %d:%u",
|
|
pbs.speedSet, pbs.audioRate.mSpeed,
|
|
pbs.pitchSet, pbs.audioRate.mPitch,
|
|
pbs.audioFallbackModeSet, pbs.audioRate.mFallbackMode,
|
|
pbs.audioStretchModeSet, pbs.audioRate.mStretchMode);
|
|
|
|
AudioPlaybackRate rate;
|
|
sync->getPlaybackParams(&rate);
|
|
bool updatedRate = false;
|
|
if (pbs.speedSet) {
|
|
rate.mSpeed = pbs.audioRate.mSpeed;
|
|
updatedRate = true;
|
|
}
|
|
if (pbs.pitchSet) {
|
|
rate.mPitch = pbs.audioRate.mPitch;
|
|
updatedRate = true;
|
|
}
|
|
if (pbs.audioFallbackModeSet) {
|
|
rate.mFallbackMode = pbs.audioRate.mFallbackMode;
|
|
updatedRate = true;
|
|
}
|
|
if (pbs.audioStretchModeSet) {
|
|
rate.mStretchMode = pbs.audioRate.mStretchMode;
|
|
updatedRate = true;
|
|
}
|
|
if (updatedRate) {
|
|
status_t err = sync->setPlaybackParams(rate);
|
|
if (err != OK) {
|
|
throwExceptionAsNecessary(env, err);
|
|
return (jfloat)0.f;
|
|
}
|
|
}
|
|
|
|
sp<const MediaClock> mediaClock = sync->getMediaClock();
|
|
if (mediaClock == NULL) {
|
|
return (jfloat)0.f;
|
|
}
|
|
|
|
return (jfloat)mediaClock->getPlaybackRate();
|
|
}
|
|
|
|
static jobject android_media_MediaSync_getPlaybackParams(
|
|
JNIEnv *env, jobject thiz) {
|
|
sp<JMediaSync> sync = getMediaSync(env, thiz);
|
|
if (sync == NULL) {
|
|
throwExceptionAsNecessary(env, INVALID_OPERATION);
|
|
return NULL;
|
|
}
|
|
|
|
PlaybackParams pbs;
|
|
AudioPlaybackRate &audioRate = pbs.audioRate;
|
|
sync->getPlaybackParams(&audioRate);
|
|
ALOGV("getPlaybackParams: %f %f %d %d",
|
|
audioRate.mSpeed, audioRate.mPitch, audioRate.mFallbackMode, audioRate.mStretchMode);
|
|
|
|
pbs.speedSet = true;
|
|
pbs.pitchSet = true;
|
|
pbs.audioFallbackModeSet = true;
|
|
pbs.audioStretchModeSet = true;
|
|
|
|
return pbs.asJobject(env, gPlaybackParamsFields);
|
|
}
|
|
|
|
static jfloat android_media_MediaSync_setSyncParams(
|
|
JNIEnv *env, jobject thiz, jobject params) {
|
|
sp<JMediaSync> sync = getMediaSync(env, thiz);
|
|
if (sync == NULL) {
|
|
throwExceptionAsNecessary(env, INVALID_OPERATION);
|
|
return (jfloat)0.f;
|
|
}
|
|
|
|
SyncParams scs;
|
|
scs.fillFromJobject(env, gSyncParamsFields, params);
|
|
ALOGV("setSyncParams: %d:%d %d:%d %d:%f %d:%f",
|
|
scs.syncSourceSet, scs.sync.mSource,
|
|
scs.audioAdjustModeSet, scs.sync.mAudioAdjustMode,
|
|
scs.toleranceSet, scs.sync.mTolerance,
|
|
scs.frameRateSet, scs.frameRate);
|
|
|
|
AVSyncSettings avsync;
|
|
sync->getSyncParams(&avsync);
|
|
bool updatedSync = false;
|
|
status_t err = OK;
|
|
if (scs.syncSourceSet) {
|
|
avsync.mSource = scs.sync.mSource;
|
|
updatedSync = true;
|
|
}
|
|
if (scs.audioAdjustModeSet) {
|
|
avsync.mAudioAdjustMode = scs.sync.mAudioAdjustMode;
|
|
updatedSync = true;
|
|
}
|
|
if (scs.toleranceSet) {
|
|
avsync.mTolerance = scs.sync.mTolerance;
|
|
updatedSync = true;
|
|
}
|
|
if (updatedSync) {
|
|
err = sync->setSyncParams(avsync);
|
|
}
|
|
|
|
if (scs.frameRateSet && err == OK) {
|
|
err = sync->setVideoFrameRateHint(scs.frameRate);
|
|
}
|
|
if (err != OK) {
|
|
throwExceptionAsNecessary(env, err);
|
|
return (jfloat)0.f;
|
|
}
|
|
|
|
sp<const MediaClock> mediaClock = sync->getMediaClock();
|
|
if (mediaClock == NULL) {
|
|
return (jfloat)0.f;
|
|
}
|
|
|
|
return (jfloat)mediaClock->getPlaybackRate();
|
|
}
|
|
|
|
static jobject android_media_MediaSync_getSyncParams(JNIEnv *env, jobject thiz) {
|
|
sp<JMediaSync> sync = getMediaSync(env, thiz);
|
|
if (sync == NULL) {
|
|
throwExceptionAsNecessary(env, INVALID_OPERATION);
|
|
return NULL;
|
|
}
|
|
|
|
SyncParams scs;
|
|
sync->getSyncParams(&scs.sync);
|
|
scs.frameRate = sync->getVideoFrameRate();
|
|
|
|
ALOGV("getSyncParams: %d %d %f %f",
|
|
scs.sync.mSource, scs.sync.mAudioAdjustMode, scs.sync.mTolerance, scs.frameRate);
|
|
|
|
// check params
|
|
if (scs.sync.mSource >= AVSYNC_SOURCE_MAX
|
|
|| scs.sync.mAudioAdjustMode >= AVSYNC_AUDIO_ADJUST_MODE_MAX
|
|
|| scs.sync.mTolerance < 0.f
|
|
|| scs.sync.mTolerance >= AVSYNC_TOLERANCE_MAX) {
|
|
throwExceptionAsNecessary(env, INVALID_OPERATION);
|
|
return NULL;
|
|
}
|
|
|
|
scs.syncSourceSet = true;
|
|
scs.audioAdjustModeSet = true;
|
|
scs.toleranceSet = true;
|
|
scs.frameRateSet = scs.frameRate >= 0.f;
|
|
|
|
return scs.asJobject(env, gSyncParamsFields);
|
|
}
|
|
|
|
static void android_media_MediaSync_native_flush(JNIEnv *env, jobject thiz) {
|
|
sp<JMediaSync> sync = getMediaSync(env, thiz);
|
|
if (sync == NULL) {
|
|
throwExceptionAsNecessary(env, INVALID_OPERATION);
|
|
return;
|
|
}
|
|
|
|
sync->flush();
|
|
}
|
|
|
|
static void android_media_MediaSync_native_init(JNIEnv *env) {
|
|
ScopedLocalRef<jclass> clazz(env, env->FindClass("android/media/MediaSync"));
|
|
CHECK(clazz.get() != NULL);
|
|
|
|
gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J");
|
|
CHECK(gFields.context != NULL);
|
|
|
|
clazz.reset(env->FindClass("android/media/MediaTimestamp"));
|
|
CHECK(clazz.get() != NULL);
|
|
|
|
gFields.mediaTimestampMediaTimeUsID =
|
|
env->GetFieldID(clazz.get(), "mediaTimeUs", "J");
|
|
CHECK(gFields.mediaTimestampMediaTimeUsID != NULL);
|
|
|
|
gFields.mediaTimestampNanoTimeID =
|
|
env->GetFieldID(clazz.get(), "nanoTime", "J");
|
|
CHECK(gFields.mediaTimestampNanoTimeID != NULL);
|
|
|
|
gFields.mediaTimestampClockRateID =
|
|
env->GetFieldID(clazz.get(), "clockRate", "F");
|
|
CHECK(gFields.mediaTimestampClockRateID != NULL);
|
|
|
|
gSyncParamsFields.init(env);
|
|
gPlaybackParamsFields.init(env);
|
|
}
|
|
|
|
static void android_media_MediaSync_native_setup(JNIEnv *env, jobject thiz) {
|
|
sp<JMediaSync> sync = new JMediaSync();
|
|
|
|
setMediaSync(env, thiz, sync);
|
|
}
|
|
|
|
static void android_media_MediaSync_native_finalize(JNIEnv *env, jobject thiz) {
|
|
android_media_MediaSync_release(env, thiz);
|
|
}
|
|
|
|
static JNINativeMethod gMethods[] = {
|
|
{ "native_setSurface",
|
|
"(Landroid/view/Surface;)V",
|
|
(void *)android_media_MediaSync_native_setSurface },
|
|
|
|
{ "native_setAudioTrack",
|
|
"(Landroid/media/AudioTrack;)V",
|
|
(void *)android_media_MediaSync_native_setAudioTrack },
|
|
|
|
{ "createInputSurface", "()Landroid/view/Surface;",
|
|
(void *)android_media_MediaSync_createInputSurface },
|
|
|
|
{ "native_updateQueuedAudioData",
|
|
"(IJ)V",
|
|
(void *)android_media_MediaSync_native_updateQueuedAudioData },
|
|
|
|
{ "native_getTimestamp",
|
|
"(Landroid/media/MediaTimestamp;)Z",
|
|
(void *)android_media_MediaSync_native_getTimestamp },
|
|
|
|
{ "native_getPlayTimeForPendingAudioFrames",
|
|
"()J",
|
|
(void *)android_media_MediaSync_native_getPlayTimeForPendingAudioFrames },
|
|
|
|
{ "native_flush", "()V", (void *)android_media_MediaSync_native_flush },
|
|
|
|
{ "native_init", "()V", (void *)android_media_MediaSync_native_init },
|
|
|
|
{ "native_setup", "()V", (void *)android_media_MediaSync_native_setup },
|
|
|
|
{ "native_release", "()V", (void *)android_media_MediaSync_release },
|
|
|
|
{ "native_setPlaybackParams", "(Landroid/media/PlaybackParams;)F",
|
|
(void *)android_media_MediaSync_setPlaybackParams },
|
|
|
|
{ "getPlaybackParams", "()Landroid/media/PlaybackParams;",
|
|
(void *)android_media_MediaSync_getPlaybackParams },
|
|
|
|
{ "native_setSyncParams", "(Landroid/media/SyncParams;)F",
|
|
(void *)android_media_MediaSync_setSyncParams },
|
|
|
|
{ "getSyncParams", "()Landroid/media/SyncParams;",
|
|
(void *)android_media_MediaSync_getSyncParams },
|
|
|
|
{ "native_finalize", "()V", (void *)android_media_MediaSync_native_finalize },
|
|
};
|
|
|
|
int register_android_media_MediaSync(JNIEnv *env) {
|
|
return AndroidRuntime::registerNativeMethods(
|
|
env, "android/media/MediaSync", gMethods, NELEM(gMethods));
|
|
}
|