Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save Camera Properties to external file when in debug mode #261

Merged
merged 14 commits into from
Sep 11, 2024
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
Loading