@@ -134,66 +134,67 @@ class CameraCaptureManager(
134134 val selector = resolveCameraSelector(provider, facing, deviceId)
135135
136136 provider.unbindAll()
137- try {
138- // Bind only the still capture use case for this invocation, then release
139- // it immediately so the camera is not left active after a one-shot snap.
140- provider.bindToLifecycle(owner, selector, capture)
141-
142- val (bytes, orientation) = capture.takeJpegWithExif(context.mainExecutor(), context.cacheDir)
143- val decoded =
144- BitmapFactory .decodeByteArray(bytes, 0 , bytes.size)
145- ? : throw IllegalStateException (" UNAVAILABLE: failed to decode captured image" )
146- val rotated = rotateBitmapByExif(decoded, orientation)
147- val scaled =
148- if (maxWidth > 0 && rotated.width > maxWidth) {
149- val h =
150- (rotated.height.toDouble() * (maxWidth.toDouble() / rotated.width.toDouble()))
151- .toInt()
152- .coerceAtLeast(1 )
153- val s = rotated.scale(maxWidth, h)
154- if (s != = rotated) rotated.recycle()
155- s
156- } else {
157- rotated
158- }
137+ // Bind only the still capture use case; CameraX owns camera open/close through the lifecycle owner.
138+ provider.bindToLifecycle(owner, selector, capture)
159139
140+ val (bytes, orientation) =
160141 try {
161- val maxPayloadBytes = 5 * 1024 * 1024
162- // Base64 inflates payloads by ~4/3; cap encoded bytes so the payload stays under 5MB (API limit).
163- val maxEncodedBytes = (maxPayloadBytes / 4 ) * 3
164- val result =
165- JpegSizeLimiter .compressToLimit(
166- initialWidth = scaled.width,
167- initialHeight = scaled.height,
168- startQuality = (quality * 100.0 ).roundToInt().coerceIn(10 , 100 ),
169- maxBytes = maxEncodedBytes,
170- encode = { width, height, q ->
171- val bitmap =
172- if (width == scaled.width && height == scaled.height) {
173- scaled
174- } else {
175- scaled.scale(width, height)
176- }
177- val out = ByteArrayOutputStream ()
178- if (! bitmap.compress(Bitmap .CompressFormat .JPEG , q, out )) {
179- if (bitmap != = scaled) bitmap.recycle()
180- throw IllegalStateException (" UNAVAILABLE: failed to encode JPEG" )
181- }
182- if (bitmap != = scaled) {
183- bitmap.recycle()
184- }
185- out .toByteArray()
186- },
187- )
188- val base64 = Base64 .encodeToString(result.bytes, Base64 .NO_WRAP )
189- Payload (
190- """ {"format":"jpg","base64":"$base64 ","width":${result.width} ,"height":${result.height} }""" ,
191- )
142+ capture.takeJpegWithExif(context.mainExecutor(), context.cacheDir)
192143 } finally {
193- scaled.recycle()
144+ // The JPEG bytes are self-contained; release CameraX before decoding and recompressing them.
145+ provider.unbind(capture)
146+ }
147+ val decoded =
148+ BitmapFactory .decodeByteArray(bytes, 0 , bytes.size)
149+ ? : throw IllegalStateException (" UNAVAILABLE: failed to decode captured image" )
150+ val rotated = rotateBitmapByExif(decoded, orientation)
151+ val scaled =
152+ if (maxWidth > 0 && rotated.width > maxWidth) {
153+ val h =
154+ (rotated.height.toDouble() * (maxWidth.toDouble() / rotated.width.toDouble()))
155+ .toInt()
156+ .coerceAtLeast(1 )
157+ val s = rotated.scale(maxWidth, h)
158+ if (s != = rotated) rotated.recycle()
159+ s
160+ } else {
161+ rotated
194162 }
163+
164+ try {
165+ val maxPayloadBytes = 5 * 1024 * 1024
166+ // Base64 inflates payloads by ~4/3; cap encoded bytes so the payload stays under 5MB (API limit).
167+ val maxEncodedBytes = (maxPayloadBytes / 4 ) * 3
168+ val result =
169+ JpegSizeLimiter .compressToLimit(
170+ initialWidth = scaled.width,
171+ initialHeight = scaled.height,
172+ startQuality = (quality * 100.0 ).roundToInt().coerceIn(10 , 100 ),
173+ maxBytes = maxEncodedBytes,
174+ encode = { width, height, q ->
175+ val bitmap =
176+ if (width == scaled.width && height == scaled.height) {
177+ scaled
178+ } else {
179+ scaled.scale(width, height)
180+ }
181+ val out = ByteArrayOutputStream ()
182+ if (! bitmap.compress(Bitmap .CompressFormat .JPEG , q, out )) {
183+ if (bitmap != = scaled) bitmap.recycle()
184+ throw IllegalStateException (" UNAVAILABLE: failed to encode JPEG" )
185+ }
186+ if (bitmap != = scaled) {
187+ bitmap.recycle()
188+ }
189+ out .toByteArray()
190+ },
191+ )
192+ val base64 = Base64 .encodeToString(result.bytes, Base64 .NO_WRAP )
193+ Payload (
194+ """ {"format":"jpg","base64":"$base64 ","width":${result.width} ,"height":${result.height} }""" ,
195+ )
195196 } finally {
196- provider.unbind(capture )
197+ scaled.recycle( )
197198 }
198199 }
199200
0 commit comments