【发布时间】:2021-08-03 04:43:43
【问题描述】:
我的目标是让 Camera2 API 拍摄一张新照片,并使用该照片通过代码实现 exif 数据,使用 ExifInterface。不过,我偶然发现了一个小问题。当我运行该应用程序时,当我按下捕获按钮并保存所需的 JPEG 文件时,它会正确运行,尽管当代码尝试添加 exif 数据时,它会显示 null。我添加了我需要的所有内容,即setAttribute() 来设置新数据并覆盖我需要添加的数据,getAttribute() 用Log.e() 显示它以查看结果。但是 exif 上的结果显示为空。我已经在单独的课程中对 exif 数据进行了一些计算,以确保让我感到舒适,有些人会在同一个课程上做,但分开做。这是 Logcat 中的消息:
E/LATITUDE: null
E/LONGITUDE: null
D/Camera2BasicFragment: /storage/emulated/0/Android/data/com.example.camera2apikotlin4/files/pic.jpg
这是 exif 数据的照片和屏幕截图,实际上是空白且未保存: The picture that took with the app The screenshot that shows with Exif Pilot the exif data
这是file 变量的来源和保存位置:
private lateinit var file: File
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
//val PIC_FILE_NAME = SimpleDateFormat("dd.MM.yyyy-HH:mm:ss", Locale.ENGLISH).format(System.currentTimeMillis()) + ".jpg"
val PIC_FILE_NAME = "pic.jpg"
file = File(requireActivity().getExternalFilesDir(null), PIC_FILE_NAME)
}
从哪里开始捕获过程:
@RequiresApi(Build.VERSION_CODES.Q)
private fun process(result: CaptureResult) {
when (state) {
STATE_PREVIEW -> Unit // Do nothing when the camera preview is working normally.
STATE_WAITING_LOCK -> capturePicture(result)
STATE_WAITING_PRECAPTURE -> {
// CONTROL_AE_STATE can be null on some devices
val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
if (aeState == null ||
aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
state = STATE_WAITING_NON_PRECAPTURE
}
}
STATE_WAITING_NON_PRECAPTURE -> {
// CONTROL_AE_STATE can be null on some devices
val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
state = STATE_PICTURE_TAKEN
captureStillPicture()
}
}
}
}
@RequiresApi(Build.VERSION_CODES.Q)
private fun capturePicture(result: CaptureResult) {
val afState = result.get(CaptureResult.CONTROL_AF_STATE)
if (afState == null) {
captureStillPicture()
} else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
|| afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
// CONTROL_AE_STATE can be null on some devices
val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
state = STATE_PICTURE_TAKEN
captureStillPicture()
} else {
runPrecaptureSequence()
}
}
}
@RequiresApi(Build.VERSION_CODES.Q)
override fun onCaptureProgressed(session: CameraCaptureSession,
request: CaptureRequest,
partialResult: CaptureResult) {
process(partialResult)
}
@RequiresApi(Build.VERSION_CODES.Q)
override fun onCaptureCompleted(session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult) {
process(result)
}
}
这是分隔代码geoDegree():
package com.example.camera2apikotlin4
import androidx.exifinterface.media.ExifInterface
class geoDegree {
private var valid: Boolean = true
var latitudeFloat: Double = 0.0
var longitudeFloat: Double = 0.0
fun geoDegree(exif: ExifInterface) {
val attrLATITUDE = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)
val attrLATITUDE_REF = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF)
val attrLONGITUDE = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)
val attrLONGITUDE_REF = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF)
if (
(attrLATITUDE != null) &&
(attrLATITUDE_REF != null) &&
(attrLONGITUDE != null) &&
(attrLONGITUDE_REF != null))
{
//Telling the code that the validation is true
valid = true
//If the reference of the latitude is equals to the letter N, it will convert it to degrees, else it will be none
if(attrLATITUDE_REF == "N") {
latitudeFloat = convertToDegree(attrLATITUDE)
} else {
0 - convertToDegree(attrLATITUDE)
}
//If the reference of the longitude is equals to the letter E, it will convert it to degrees, else it will be none
if(attrLONGITUDE_REF == "E") {
longitudeFloat = convertToDegree(attrLONGITUDE)
} else {
0 - convertToDegree(attrLONGITUDE)
}
}
}
//The method function that converting the degrees, using the list of strings
private fun convertToDegree(stringDMS: String): Double {
val result: Double?
val DMS: List<String> = stringDMS.split(",")
//Values in degrees
val stringD: List<String> = DMS[0].split("/")
val D0 = stringD[0].toDouble()
val D1 = stringD[1].toDouble()
val FloatD: Double = D0 / D1
//Values in minutes
val stringM = DMS[1].split("/")
val M0 = stringM[0].toDouble()
val M1 = stringM[1].toDouble()
val FloatM: Double = M0 / M1
//Values in seconds
val stringS = DMS[2].split("/")
val S0 = stringS[0].toDouble()
val S1 = stringS[1].toDouble()
val FloatS = S0 / S1
/**Overall results to display on, when combining the Float Degrees
* and calculating with Minutes about 60, Seconds with 3600 and overall to be as Float type
**/
result = (FloatD + (FloatM/60) + (FloatS/3600))
return result
}
fun isValid(): Boolean {
return valid
}
override fun toString(): String {
return ("$latitudeFloat, $longitudeFloat")
}
fun getLatitudeE6(): Int {
return (latitudeFloat.times(1000000)).toInt()
}
fun getLongitudeE6(): Int {
return (longitudeFloat.times(1000000)).toInt()
}
}
这是imageCapture中的主要代码:
/**
* Capture a still picture. This method should be called when we get a response in
* [.captureCallback] from both [.lockFocus].
*/
@RequiresApi(Build.VERSION_CODES.Q)
private fun captureStillPicture() {
try {
if (activity == null || cameraDevice == null) return
val rotation = requireActivity().windowManager.defaultDisplay.rotation
// This is the CaptureRequest.Builder that we use to take a picture.
val captureBuilder = cameraDevice?.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE)?.apply {
imageReader?.surface?.let { addTarget(it) }
// Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
// We have to take that into account and rotate JPEG properly.
// For devices with orientation of 90, we return our mapping from ORIENTATIONS.
// For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
set(CaptureRequest.JPEG_ORIENTATION,
(ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360)
// Use the same AE and AF modes as the preview.
set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
}?.also { setAutoFlash(it) }
val captureCallback = object : CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult) {
Toast.makeText(context, "Saved: $file", Toast.LENGTH_SHORT).show()
Log.d(TAG, file.toString())
unlockFocus()
}
}.apply {
val exif = ExifInterface(file)
geoDegree().geoDegree(exif)
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, "${geoDegree().getLatitudeE6()}")
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, "${geoDegree().getLongitudeE6()}")
exif.saveAttributes()
geoDegree().isValid()
val exifLatitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)
val exifLongitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)
Log.e("LATITUDE", "$exifLatitude")
Log.e("LONGITUDE", "$exifLongitude")
}
captureSession?.apply {
stopRepeating()
abortCaptures()
captureBuilder?.build()?.let { capture(it, captureCallback, null) }
}
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
}
}
任何提示、技巧、建议,以及任何真正有助于解决要解决的小问题的东西。这对你来说可能很容易,但我仍在学习如何去做。提前谢谢你。
【问题讨论】:
-
@blackapps 对此我深表歉意,现在代码在帖子中。
-
我们看不到您在哪里使用了
file变量。那么如何记录保存在该文件中的内容呢? -
@blackapps 好的,所以我再次编辑了代码,所以
file在外部作为文件类型的私有变量,然后在覆盖方法onActivityCreated(savedInstanceState: Bundle?)中使用,使其位于它假设保存文件,因为它在活动片段中使用和转换。 -
我们仍然没有看到您在某处使用该文件实例来拍照。
-
@blackapps 哦,我真的很抱歉浪费了你的时间,我真的还是这个平台的新手,加上如何解释,但我仍然让你感到困惑,等待至少 10 分钟并刷新获取照片拍摄位置的代码。真的很抱歉。