Skip to content

Commit 231c5d7

Browse files
authored
fix(liveness): Multiple Liveness Bug Fixes (#124)
1 parent 0ed949c commit 231c5d7

File tree

7 files changed

+51
-10
lines changed

7 files changed

+51
-10
lines changed

liveness/api/liveness.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ public final class com/amplifyframework/ui/liveness/BuildConfig {
22
public static final field BUILD_TYPE Ljava/lang/String;
33
public static final field DEBUG Z
44
public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
5+
public static final field LIVENESS_VERSION_NAME Ljava/lang/String;
56
public static final field VERSION_NAME Ljava/lang/String;
67
public fun <init> ()V
78
}

liveness/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ android {
1212
arguments += "-DCMAKE_VERBOSE_MAKEFILE=ON"
1313
}
1414
}
15+
buildConfigField(
16+
"String",
17+
"LIVENESS_VERSION_NAME",
18+
"\"${project.properties["VERSION_NAME"]}\""
19+
)
1520
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
1621
}
1722

liveness/src/main/java/com/amplifyframework/ui/liveness/camera/FrameAnalyzer.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import android.graphics.Bitmap
2020
import android.util.Size
2121
import androidx.camera.core.ImageAnalysis
2222
import androidx.camera.core.ImageProxy
23+
import com.amplifyframework.core.Amplify
2324
import com.amplifyframework.ui.liveness.ml.FaceDetector
2425
import com.amplifyframework.ui.liveness.ml.FaceOval
2526
import com.amplifyframework.ui.liveness.state.LivenessState
@@ -42,7 +43,20 @@ internal class FrameAnalyzer(
4243
private var cachedBitmap: Bitmap? = null
4344
private var faceDetector = FaceDetector(livenessState)
4445

46+
private val logger = Amplify.Logging.forNamespace("Liveness")
47+
4548
override fun analyze(image: ImageProxy) {
49+
try {
50+
attemptAnalyze(image)
51+
} catch (e: Exception) {
52+
// We've seen a few instances of exceptions thrown by copyPixelsFromBuffer.
53+
// This indicates the image received may have been in an unexpected format.
54+
// We discard this frame, in hopes that the next frame is readable.
55+
logger.error("Failed to analyze frame", e)
56+
}
57+
}
58+
59+
private fun attemptAnalyze(image: ImageProxy) {
4660
if (cachedBitmap == null) {
4761
cachedBitmap = Bitmap.createBitmap(
4862
image.width,

liveness/src/main/java/com/amplifyframework/ui/liveness/camera/LivenessCoordinator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ internal class LivenessCoordinator(
180180
sessionId,
181181
faceLivenessSessionInformation,
182182
faceLivenessSessionOptions,
183-
BuildConfig.VERSION_NAME,
183+
BuildConfig.LIVENESS_VERSION_NAME,
184184
{ livenessState.onLivenessSessionReady(it) },
185185
{
186186
disconnectEventReceived = true

liveness/src/main/java/com/amplifyframework/ui/liveness/camera/LivenessMuxer.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import android.media.MediaCodec
1919
import android.media.MediaFormat
2020
import android.media.MediaMuxer
2121
import androidx.annotation.WorkerThread
22+
import com.amplifyframework.core.Amplify
2223
import java.io.File
2324
import java.io.RandomAccessFile
2425
import java.nio.ByteBuffer
@@ -48,6 +49,8 @@ internal class LivenessMuxer(
4849
currentVideoStartTime = System.currentTimeMillis()
4950
}
5051

52+
private val logger = Amplify.Logging.forNamespace("Liveness")
53+
5154
/*
5255
Attempt to notify listener that chunked data is available if minimum chunk interval has exceeded
5356
Write new frame to muxer
@@ -61,7 +64,13 @@ internal class LivenessMuxer(
6164
}
6265
}
6366

64-
muxer.writeSampleData(videoTrack, byteBuf, bufferInfo)
67+
try {
68+
muxer.writeSampleData(videoTrack, byteBuf, bufferInfo)
69+
} catch (e: Exception) {
70+
// writeSampleData can throw for various reasons, such as an empty byte buffer.
71+
// If this happens, we discard the frame, in hopes that future frames are valid.
72+
logger.error("Failed to write encoded chunk to muxer", e)
73+
}
6574
}
6675

6776
@WorkerThread

liveness/src/main/java/com/amplifyframework/ui/liveness/camera/LivenessVideoEncoder.kt

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import android.os.Handler
2424
import android.os.HandlerThread
2525
import android.util.Log
2626
import androidx.annotation.WorkerThread
27+
import com.amplifyframework.core.Amplify
2728
import com.amplifyframework.ui.liveness.util.isKeyFrame
2829
import java.io.File
2930

@@ -124,6 +125,7 @@ internal class LivenessVideoEncoder private constructor(
124125

125126
private var encoding = false
126127
private var livenessMuxer: LivenessMuxer? = null
128+
private val logger = Amplify.Logging.forNamespace("Liveness")
127129

128130
init {
129131
encoder.start()
@@ -152,11 +154,18 @@ internal class LivenessVideoEncoder private constructor(
152154

153155
if (info.isKeyFrame()) {
154156
if (livenessMuxer == null) {
155-
livenessMuxer = LivenessMuxer(
156-
outputFile,
157-
encoder.outputFormat,
158-
onMuxedSegment
159-
)
157+
try {
158+
val muxer = LivenessMuxer(
159+
outputFile,
160+
encoder.outputFormat,
161+
onMuxedSegment
162+
)
163+
livenessMuxer = muxer
164+
} catch (e: Exception) {
165+
// This is likely an unrecoverable error, such as file creation failing.
166+
// However, if it fails, we will allow another attempt at the next keyframe.
167+
logger.error("Failed to create liveness muxer", e)
168+
}
160169
}
161170
framesSinceSyncRequest = 0 // reset keyframe request on keyframe receipt
162171
} else {

liveness/src/main/java/com/amplifyframework/ui/liveness/ui/FaceLivenessDetector.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,12 @@ fun FaceLivenessDetector(
134134
},
135135
onChallengeFailed = {
136136
scope.launch {
137-
isFinished = true
138-
resetOrientation()
139-
currentOnError.accept(it)
137+
// if we are already finished, we already provided a result in complete or failed
138+
if (!isFinished) {
139+
isFinished = true
140+
resetOrientation()
141+
currentOnError.accept(it)
142+
}
140143
}
141144
}
142145
)

0 commit comments

Comments
 (0)