Skip to content

Commit

Permalink
Save Camera Properties to external file when in debug mode (#261)
Browse files Browse the repository at this point in the history
* Save Camera Properties to external file

* address comments

* update

* Update DebugCameraInfoUtil.kt

* address comments

* Update DebugCameraInfoUtil.kt

* Update CameraXCameraUseCase.kt

* update

* Update CameraXCameraUseCase.kt
  • Loading branch information
davidjiagoogle authored Sep 11, 2024
1 parent 8e90b3e commit 575ffcf
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ interface CameraUseCase {
*
* @return list of available lenses.
*/
suspend fun initialize(cameraAppSettings: CameraAppSettings, useCaseMode: UseCaseMode)
suspend fun initialize(
cameraAppSettings: CameraAppSettings,
useCaseMode: UseCaseMode,
isDebugMode: Boolean = false
)

/**
* Starts the camera.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import android.app.Application
import android.content.ContentResolver
import android.content.ContentValues
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.Environment.DIRECTORY_DOCUMENTS
import android.provider.MediaStore
import android.util.Log
import androidx.camera.core.CameraInfo
Expand All @@ -33,6 +35,10 @@ import androidx.camera.core.takePicture
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.lifecycle.awaitInstance
import androidx.camera.video.Recorder
import com.google.jetpackcamera.core.camera.DebugCameraInfoUtil.getAllCamerasPropertiesJSONArray
import com.google.jetpackcamera.core.camera.DebugCameraInfoUtil.writeFileExternalStorage
import com.google.jetpackcamera.core.common.DefaultDispatcher
import com.google.jetpackcamera.core.common.IODispatcher
import com.google.jetpackcamera.settings.SettableConstraintsRepository
import com.google.jetpackcamera.settings.model.AspectRatio
import com.google.jetpackcamera.settings.model.CameraAppSettings
Expand All @@ -49,6 +55,7 @@ import com.google.jetpackcamera.settings.model.Stabilization
import com.google.jetpackcamera.settings.model.SupportedStabilizationMode
import com.google.jetpackcamera.settings.model.SystemConstraints
import dagger.hilt.android.scopes.ViewModelScoped
import java.io.File
import java.io.FileNotFoundException
import java.text.SimpleDateFormat
import java.util.Calendar
Expand All @@ -67,6 +74,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext

private const val TAG = "CameraXCameraUseCase"
const val TARGET_FPS_AUTO = 0
Expand All @@ -82,7 +90,8 @@ class CameraXCameraUseCase
@Inject
constructor(
private val application: Application,
private val defaultDispatcher: CoroutineDispatcher,
@DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher,
@IODispatcher private val iODispatcher: CoroutineDispatcher,
private val constraintsRepository: SettableConstraintsRepository
) : CameraUseCase {
private lateinit var cameraProvider: ProcessCameraProvider
Expand All @@ -109,7 +118,8 @@ constructor(

override suspend fun initialize(
cameraAppSettings: CameraAppSettings,
useCaseMode: CameraUseCase.UseCaseMode
useCaseMode: CameraUseCase.UseCaseMode,
isDebugMode: Boolean
) {
this.useCaseMode = useCaseMode
cameraProvider = ProcessCameraProvider.awaitInstance(application)
Expand Down Expand Up @@ -186,6 +196,18 @@ constructor(
.tryApplyFrameRateConstraints()
.tryApplyStabilizationConstraints()
.tryApplyConcurrentCameraModeConstraints()
if (isDebugMode && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
withContext(iODispatcher) {
val cameraProperties =
getAllCamerasPropertiesJSONArray(cameraProvider.availableCameraInfos).toString()
val file = File(
Environment.getExternalStoragePublicDirectory(DIRECTORY_DOCUMENTS),
"JCACameraProperties.json"
)
writeFileExternalStorage(file, cameraProperties)
Log.d(TAG, "JCACameraProperties written to ${file.path}. \n$cameraProperties")
}
}
}

override suspend fun runCamera() = coroutineScope {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright (C) 2024 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.
*/
package com.google.jetpackcamera.core.camera

import android.hardware.camera2.CameraCharacteristics
import android.os.Build
import android.os.Environment
import androidx.annotation.OptIn
import androidx.annotation.RequiresApi
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.camera2.interop.ExperimentalCamera2Interop
import androidx.camera.core.CameraInfo
import java.io.File
import java.io.FileOutputStream
import org.json.JSONArray
import org.json.JSONObject

private const val TAG = "DebugCameraInfoUtil"
object DebugCameraInfoUtil {
@OptIn(ExperimentalCamera2Interop::class)
@RequiresApi(Build.VERSION_CODES.P)
fun getAllCamerasPropertiesJSONArray(cameraInfos: List<CameraInfo>): JSONArray {
val result = JSONArray()
for (cameraInfo in cameraInfos) {
var camera2CameraInfo = Camera2CameraInfo.from(cameraInfo)
val logicalCameraId = camera2CameraInfo.cameraId
val logicalCameraData = JSONObject()
logicalCameraData.put(
"logical-$logicalCameraId",
getCameraPropertiesJSONObject(camera2CameraInfo)
)
for (physicalCameraInfo in cameraInfo.physicalCameraInfos) {
camera2CameraInfo = Camera2CameraInfo.from(physicalCameraInfo)
val physicalCameraId = camera2CameraInfo.cameraId
logicalCameraData.put(
"physical-$physicalCameraId",
getCameraPropertiesJSONObject(camera2CameraInfo)
)
}
result.put(logicalCameraData)
}
return result
}

@OptIn(ExperimentalCamera2Interop::class)
private fun getCameraPropertiesJSONObject(cameraInfo: Camera2CameraInfo): JSONObject {
val jsonObject = JSONObject()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
cameraInfo.getCameraCharacteristic(CameraCharacteristics.LENS_POSE_ROTATION)
?.let {
jsonObject.put(
CameraCharacteristics.LENS_POSE_ROTATION.name,
it.contentToString()
)
}
cameraInfo.getCameraCharacteristic(CameraCharacteristics.LENS_POSE_TRANSLATION)
?.let {
jsonObject.put(
CameraCharacteristics.LENS_POSE_TRANSLATION.name,
it.contentToString()
)
}
cameraInfo.getCameraCharacteristic(CameraCharacteristics.LENS_INTRINSIC_CALIBRATION)
?.let {
jsonObject.put(
CameraCharacteristics.LENS_INTRINSIC_CALIBRATION.name,
it.contentToString()
)
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
cameraInfo.getCameraCharacteristic(CameraCharacteristics.LENS_DISTORTION)
?.let {
jsonObject.put(
CameraCharacteristics.LENS_DISTORTION.name,
it.contentToString()
)
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
cameraInfo.getCameraCharacteristic(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE)
?.let { jsonObject.put(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE.name, it) }
}
cameraInfo.getCameraCharacteristic(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)
?.let {
jsonObject.put(
CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS.name,
it.contentToString()
)
}
cameraInfo.getCameraCharacteristic(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE)
?.let {
jsonObject.put(
CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE.name,
it
)
}
cameraInfo.getCameraCharacteristic(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
?.let {
jsonObject.put(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES.name,
it.contentToString()
)
}

return jsonObject
}

fun writeFileExternalStorage(file: File, textToWrite: String) {
// Checking the availability state of the External Storage.
val state = Environment.getExternalStorageState()
if (Environment.MEDIA_MOUNTED != state) {
// If it isn't mounted - we can't write into it.
return
}

file.createNewFile()
FileOutputStream(file).use { outputStream ->
outputStream.write(textToWrite.toByteArray())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ class FakeCameraUseCase(

override suspend fun initialize(
cameraAppSettings: CameraAppSettings,
useCaseMode: CameraUseCase.UseCaseMode
useCaseMode: CameraUseCase.UseCaseMode,
isDebugMode: Boolean
) {
initialized = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Qualifier
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
Expand All @@ -32,9 +33,22 @@ import kotlinx.coroutines.SupervisorJob
@InstallIn(SingletonComponent::class)
class CommonModule {
@Provides
@DefaultDispatcher
fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default

@Provides
@IODispatcher
fun provideIODispatcher(): CoroutineDispatcher = Dispatchers.IO

@Singleton
@Provides
fun providesCoroutineScope() = CoroutineScope(SupervisorJob() + Dispatchers.Default)
}

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class DefaultDispatcher

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class IODispatcher
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ class PreviewViewModel @AssistedInject constructor(
private var initializationDeferred: Deferred<Unit> = viewModelScope.async {
cameraUseCase.initialize(
cameraAppSettings = settingsRepository.defaultCameraAppSettings.first(),
previewMode.toUseCaseMode()
previewMode.toUseCaseMode(),
isDebugMode
)
}

Expand Down

0 comments on commit 575ffcf

Please sign in to comment.