diff --git a/lib_ass_kt/src/main/cpp/AssKt.c b/lib_ass_kt/src/main/cpp/AssKt.c index 6110a16..7ecfe72 100644 --- a/lib_ass_kt/src/main/cpp/AssKt.c +++ b/lib_ass_kt/src/main/cpp/AssKt.c @@ -69,10 +69,12 @@ jlong nativeAssTrackInit(JNIEnv* env, jclass clazz, jlong ass) { } jint nativeAssTrackGetWidth(JNIEnv* env, jclass clazz, jlong track) { + if (!track) return 0; return ((ASS_Track *) track)->PlayResX; } jobjectArray nativeAssTrackGetEvents(JNIEnv* env, jclass clazz, jlong track) { + if (!track) return NULL; jclass eventClass = (*env)->FindClass(env, "io/github/peerless2012/ass/AssEvent"); if (eventClass == NULL) { return NULL; @@ -121,6 +123,7 @@ jobjectArray nativeAssTrackGetEvents(JNIEnv* env, jclass clazz, jlong track) { } void nativeAssTrackClearEvents(JNIEnv* env, jclass clazz, jlong track) { + if (!track) return; ASS_Track* tr = (ASS_Track *) track; for (int i = 0; i < tr->n_events; i++) { ass_free_event(tr, i); @@ -129,10 +132,12 @@ void nativeAssTrackClearEvents(JNIEnv* env, jclass clazz, jlong track) { } jint nativeAssTrackGetHeight(JNIEnv* env, jclass clazz, jlong track) { + if (!track) return 0; return ((ASS_Track *) track)->PlayResY; } void nativeAssTrackReadBuffer(JNIEnv* env, jclass clazz, jlong track, jbyteArray buffer, jint offset, jint length) { + if (!track) return; jboolean isCopy; jbyte* elements = (*env)->GetByteArrayElements(env, buffer, &isCopy); if (elements == NULL) { @@ -143,6 +148,7 @@ void nativeAssTrackReadBuffer(JNIEnv* env, jclass clazz, jlong track, jbyteArray } void nativeAssTrackReadChunk(JNIEnv* env, jclass clazz, jlong track, jlong start, jlong duration, jbyteArray buffer, jint offset, jint length) { + if (!track) return; jboolean isCopy; jbyte* elements = (*env)->GetByteArrayElements(env, buffer, &isCopy); if (elements == NULL) { @@ -153,6 +159,7 @@ void nativeAssTrackReadChunk(JNIEnv* env, jclass clazz, jlong track, jlong start } void nativeAssTrackDeinit(JNIEnv* env, jclass clazz, jlong track) { + if (!track) return; ass_free_track((ASS_Track *) track); } @@ -175,18 +182,22 @@ jlong nativeAssRenderInit(JNIEnv* env, jclass clazz, jlong ass) { } void nativeAssRenderSetFontScale(JNIEnv* env, jclass clazz, jlong render, jfloat scale) { + if (!render) return; ass_set_font_scale((ASS_Renderer *) render, scale); } void nativeAssRenderSetCacheLimit(JNIEnv* env, jclass clazz, jlong render, jint glyphMax, jint bitmapMaxSize) { + if (!render) return; ass_set_cache_limits((ASS_Renderer *) render, glyphMax, bitmapMaxSize); } void nativeAssRenderSetFrameSize(JNIEnv* env, jclass clazz, jlong render, jint width, jint height) { + if (!render) return; ass_set_frame_size((ASS_Renderer *) render, width, height); } void nativeAssRenderSetStorageSize(JNIEnv* env, jclass clazz, jlong render, jint width, jint height) { + if (!render) return; ass_set_storage_size((ASS_Renderer *) render, width, height); } @@ -294,6 +305,7 @@ static int count_ass_images(ASS_Image *images) { } jobject nativeAssRenderFrame(JNIEnv* env, jclass clazz, jlong render, jlong track, jlong time, jint type) { + if (!render || !track) return NULL; int changed; ASS_Image *image = ass_render_frame((ASS_Renderer *) render, (ASS_Track *) track, time, &changed); if (image == NULL) { diff --git a/lib_ass_kt/src/main/java/io/github/peerless2012/ass/Ass.kt b/lib_ass_kt/src/main/java/io/github/peerless2012/ass/Ass.kt index c59e13b..bcc56a6 100644 --- a/lib_ass_kt/src/main/java/io/github/peerless2012/ass/Ass.kt +++ b/lib_ass_kt/src/main/java/io/github/peerless2012/ass/Ass.kt @@ -1,5 +1,8 @@ package io.github.peerless2012.ass +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + /** * @Author peerless2012 * @Email peerless2012@126.com @@ -29,26 +32,56 @@ class Ass { } - private val nativeAss: Long = nativeAssInit() + /** Single lock for all libass calls on this library instance. */ + val lock = ReentrantLock() + + private var nativeAss: Long = nativeAssInit() + + @Volatile + var released = false + private set public fun createTrack(): AssTrack { - return AssTrack(nativeAss) + return lock.withLock { + if (released || nativeAss == 0L) throw IllegalStateException("Ass already released") + AssTrack(nativeAss, lock) + } } public fun createRender(): AssRender { - return AssRender(nativeAss) + return lock.withLock { + if (released || nativeAss == 0L) throw IllegalStateException("Ass already released") + AssRender(nativeAss, lock) + } } public fun addFont(name: String, buffer: ByteArray) { - nativeAssAddFont(nativeAss, name, buffer) + lock.withLock { + if (released || nativeAss == 0L) return + nativeAssAddFont(nativeAss, name, buffer) + } } public fun clearFont() { - nativeAssClearFont(nativeAss) + lock.withLock { + if (released || nativeAss == 0L) return + nativeAssClearFont(nativeAss) + } + } + + fun release() { + lock.withLock { + if (released) return + released = true + if (nativeAss != 0L) { + nativeAssDeinit(nativeAss) + nativeAss = 0 + } + } } protected fun finalize() { - nativeAssDeinit(nativeAss) + release() } -} \ No newline at end of file +} diff --git a/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssRender.kt b/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssRender.kt index 4edc60c..877cbdf 100644 --- a/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssRender.kt +++ b/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssRender.kt @@ -1,5 +1,8 @@ package io.github.peerless2012.ass +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + /** * @Author peerless2012 * @Email peerless2012@126.com @@ -7,7 +10,7 @@ package io.github.peerless2012.ass * @Version V1.0 * @Description */ -class AssRender(nativeAss: Long) { +class AssRender(nativeAss: Long, private val lock: ReentrantLock) { companion object { @@ -33,36 +36,71 @@ class AssRender(nativeAss: Long) { external fun nativeAssRenderDeinit(render: Long) } - private val nativeRender: Long = nativeAssRenderInit(nativeAss) + private var nativeRender: Long = nativeAssRenderInit(nativeAss) + + @Volatile + var released = false + private set private var track: AssTrack? = null public fun setTrack(track: AssTrack?) { - this.track = track + lock.withLock { + this.track = track + } } public fun setFontScale(scale: Float) { - nativeAssRenderSetFontScale(nativeRender, scale) + lock.withLock { + if (released || nativeRender == 0L) return + nativeAssRenderSetFontScale(nativeRender, scale) + } } public fun setCacheLimit(glyphMax: Int, bitmapMaxSize: Int) { - nativeAssRenderSetCacheLimit(nativeRender, glyphMax, bitmapMaxSize) + lock.withLock { + if (released || nativeRender == 0L) return + nativeAssRenderSetCacheLimit(nativeRender, glyphMax, bitmapMaxSize) + } } public fun setStorageSize(width: Int, height: Int) { - nativeAssRenderSetStorageSize(nativeRender, width, height) + lock.withLock { + if (released || nativeRender == 0L) return + nativeAssRenderSetStorageSize(nativeRender, width, height) + } } public fun setFrameSize(width: Int, height: Int) { - nativeAssRenderSetFrameSize(nativeRender, width, height) + lock.withLock { + if (released || nativeRender == 0L) return + nativeAssRenderSetFrameSize(nativeRender, width, height) + } } public fun renderFrame(time: Long, type: AssTexType): AssFrame? { - return track?.let { nativeAssRenderFrame(nativeRender, it.nativeAssTrack, time, type.ordinal) } + lock.withLock { + if (released || nativeRender == 0L) return null + val t = track ?: return null + if (t.released || t.nativeAssTrack == 0L) return null + return nativeAssRenderFrame(nativeRender, t.nativeAssTrack, time, type.ordinal) + } + } + + fun release() { + lock.withLock { + if (released) return + released = true + track = null + if (nativeRender != 0L) { + nativeAssRenderDeinit(nativeRender) + nativeRender = 0 + } + } } protected fun finalize() { - nativeAssRenderDeinit(nativeRender) + release() } -} \ No newline at end of file +} diff --git a/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssTrack.kt b/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssTrack.kt index 8ab1497..5500100 100644 --- a/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssTrack.kt +++ b/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssTrack.kt @@ -1,5 +1,8 @@ package io.github.peerless2012.ass +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + /** * @Author peerless2012 * @Email peerless2012@126.com @@ -7,7 +10,7 @@ package io.github.peerless2012.ass * @Version V1.0 * @Description */ -class AssTrack(private val ass: Long) { +class AssTrack(private val ass: Long, private val lock: ReentrantLock) { companion object { @@ -36,34 +39,68 @@ class AssTrack(private val ass: Long) { external fun nativeAssTrackDeinit(track: Long) } - public val nativeAssTrack = nativeAssTrackInit(ass) + var nativeAssTrack = nativeAssTrackInit(ass) + private set + + @Volatile + var released = false + private set public fun getWidth(): Int { - return nativeAssTrackGetWidth(nativeAssTrack) + lock.withLock { + if (released || nativeAssTrack == 0L) return 0 + return nativeAssTrackGetWidth(nativeAssTrack) + } } public fun getHeight(): Int { - return nativeAssTrackGetHeight(nativeAssTrack) + lock.withLock { + if (released || nativeAssTrack == 0L) return 0 + return nativeAssTrackGetHeight(nativeAssTrack) + } } public fun getEvents(): Array? { - return nativeAssTrackGetEvents(nativeAssTrack) + lock.withLock { + if (released || nativeAssTrack == 0L) return null + return nativeAssTrackGetEvents(nativeAssTrack) + } } public fun clearEvent() { - nativeAssTrackClearEvents(nativeAssTrack) + lock.withLock { + if (released || nativeAssTrack == 0L) return + nativeAssTrackClearEvents(nativeAssTrack) + } } public fun readBuffer(array: ByteArray, offset: Int = 0, length : Int = array.size) { - nativeAssTrackReadBuffer(nativeAssTrack, array, offset, length) + lock.withLock { + if (released || nativeAssTrack == 0L) return + nativeAssTrackReadBuffer(nativeAssTrack, array, offset, length) + } } public fun readChunk(start: Long, duration: Long, array: ByteArray, offset: Int = 0, length: Int = array.size) { - nativeAssTrackReadChunk(nativeAssTrack, start, duration, array, offset, length) + lock.withLock { + if (released || nativeAssTrack == 0L) return + nativeAssTrackReadChunk(nativeAssTrack, start, duration, array, offset, length) + } + } + + fun release() { + lock.withLock { + if (released) return + released = true + if (nativeAssTrack != 0L) { + nativeAssTrackDeinit(nativeAssTrack) + nativeAssTrack = 0 + } + } } protected fun finalize() { - nativeAssTrackDeinit(nativeAssTrack) + release() } -} \ No newline at end of file +} diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/AssHandler.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/AssHandler.kt index b074014..b0e9779 100644 --- a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/AssHandler.kt +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/AssHandler.kt @@ -35,7 +35,8 @@ class AssHandler( /** The ASS instance used for creating tracks and renderers. This is lazy to avoid loading * libass if the played media does not have ASS tracks. */ - val ass by lazy { Ass() } + private val assDelegate = lazy { Ass() } + val ass by assDelegate /** The current ASS renderer. It's created as soon as a ASS track is detected. */ var render: AssRender? = null @@ -296,7 +297,7 @@ class AssHandler( /** * Reads a dialogue into the track of the given [trackId]. - * TODO This should move to executor. + * Thread-safe: AssTrack.readChunk internally acquires the shared libass lock. */ fun readTrackDialogue( trackId: String?, @@ -306,7 +307,8 @@ class AssHandler( offset: Int = 0, length: Int = data.size ) { - availableTracks[trackId]?.readChunk(start, duration, data, offset, length) + val t = availableTracks[trackId] ?: return + t.readChunk(start, duration, data, offset, length) } /** @@ -343,6 +345,23 @@ class AssHandler( }?.getTrackFormat(0) } + /** + * Releases all native resources held by this handler. + */ + fun release() { + videoTimeCallback = null + overlayManager?.disable() + render?.release() + render = null + availableTracks.values.forEach { it.release() } + availableTracks.clear() + track = null + pendingFonts.clear() + if (assDelegate.isInitialized()) { + ass.release() + } + } + /** * Checks if the size is valid (both width and height are greater than 0). */