Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package io.checkyou.android.camera
- import android.graphics.*
- import android.hardware.Camera
- import android.os.Handler
- import android.os.HandlerThread
- import android.util.Log
- import com.jakewharton.rxrelay2.BehaviorRelay
- import io.checkyou.android.utils.*
- import io.checkyou.android.utils.Optional
- import io.reactivex.android.schedulers.AndroidSchedulers
- import io.reactivex.disposables.CompositeDisposable
- import io.reactivex.rxkotlin.plusAssign
- import io.reactivex.rxkotlin.withLatestFrom
- import java.util.*
- import io.checkyou.android.Application
- import io.checkyou.android.BuildConfig
- import io.reactivex.disposables.Disposable
- import io.reactivex.rxkotlin.Observables
- import jp.co.cyberagent.android.gpuimage.*
- import jp.co.cyberagent.android.gpuimage.context.GPUImageGlContext
- import jp.co.cyberagent.android.gpuimage.context.GPUImageTexture2d
- import java.io.File
- import kotlin.math.roundToInt
- class CameraServiceLegacyImpl(private val disposable: CompositeDisposable = CompositeDisposable())
- : CameraService<GPUImageTexture2d>, Disposable by disposable {
- companion object {
- private fun producePhoto(bitmap: Bitmap, mode: CameraService.Mode, zoom: Float) : Bitmap {
- val matrix = Matrix()
- matrix.postRotate(90f)
- if (mode == CameraService.Mode.FRONT) {
- matrix.postScale(1f, -1f)
- }
- /*val ratio = HEIGHT / WIDTH.toFloat()
- val bitmapRatio = bitmap.height / bitmap.width.toFloat()
- var w = 0
- var h = 0
- var xDelta = 0
- var yDelta = 0
- if (ratio < bitmapRatio) {
- w = (bitmap.width / zoom).roundToInt()
- h = (w * ratio).roundToInt()
- } else {
- h = (bitmap.height / zoom).roundToInt()
- w = (h / ratio).roundToInt()
- }
- xDelta = (bitmap.width - w) / 2
- yDelta = (bitmap.height - h) / 2*/
- val (p1, p2) = clampRect(WIDTH, HEIGHT, bitmap.width, bitmap.height, zoom)
- val (w, h) = p1
- val (xDelta, yDelta) = p2
- return Bitmap.createBitmap(bitmap, xDelta, yDelta, w, h, matrix, true)
- }
- protected fun setUpFlashMode(parameters: Camera.Parameters, flashEnabled: Boolean, videoMode: Boolean = false) {
- val supportedModes = parameters.supportedFlashModes
- if (supportedModes != null) {
- if (flashEnabled) {
- if (videoMode) {
- if (supportedModes.contains(Camera.Parameters.FLASH_MODE_TORCH)) {
- parameters.flashMode = Camera.Parameters.FLASH_MODE_TORCH
- }
- } else {
- if (supportedModes.contains(Camera.Parameters.FLASH_MODE_ON)) {
- if (BuildConfig.DEBUG) Log.e("kek4es", "sw to on")
- parameters.flashMode = Camera.Parameters.FLASH_MODE_ON
- }
- }
- } else {
- if (supportedModes.contains(Camera.Parameters.FLASH_MODE_OFF)) {
- parameters.flashMode = Camera.Parameters.FLASH_MODE_OFF
- }
- }
- }
- }
- protected fun pickSizeThatFits(width: Int, height: Int, sizes: List<Camera.Size>): Camera.Size? {
- var result: Camera.Size? = null
- var drawback: Camera.Size? = null
- for (option in sizes) {
- if (option.height >= option.width * height / width &&
- option.width >= width &&
- option.height >= height) {
- if (result == null || option.width * option.height < result.width * result.height) {
- result = option
- }
- }
- if (drawback == null || option.width * option.height > drawback.width * drawback.height) {
- drawback = option
- }
- }
- return result ?: drawback
- }
- protected fun clampRect(width: Int, height: Int, previewWidth: Int, previewHeight: Int, zoom: Float)
- : Pair<Pair<Int, Int>, Pair<Int, Int>> {
- val ratio = height / width.toFloat()
- val bitmapRatio = previewHeight / previewWidth.toFloat()
- var w = 0
- var h = 0
- var xDelta = 0
- var yDelta = 0
- if (ratio < bitmapRatio) {
- w = (previewWidth / zoom).roundToInt()
- h = (w * ratio).roundToInt()
- } else {
- h = (previewHeight / zoom).roundToInt()
- w = (h / ratio).roundToInt()
- }
- xDelta = (previewWidth - w) / 2
- yDelta = (previewHeight - h) / 2
- return (w to h) to (xDelta to yDelta)
- }
- protected fun getFocusArea(
- width: Int,
- height: Int,
- previewWidth: Int,
- previewHeight: Int,
- zoom: Float,
- point: Pair<Float, Float>) : Camera.Area {
- val (p1, p2) = clampRect(width, height, previewWidth, previewHeight, zoom)
- val (w, h) = p1
- val (xDelta, yDelta) = p2
- // TODO доделать
- return Camera.Area(Rect(-200, -200, 200, 200), 1000)
- }
- const val WIDTH = 1920
- const val HEIGHT = 1080
- }
- private val cameraThread = HandlerThread("Camera Thread").apply { start() }
- private val cameraScheduler = AndroidSchedulers.from(cameraThread.looper)
- private val rawPhotoThread = HandlerThread("Raw Photo Thread").apply { start() }
- private val rawPhotoScheduler = AndroidSchedulers.from(rawPhotoThread.looper)
- private val recordingRelay = publishRelayOf<Boolean>()
- override val lastPhoto = publishRelayOf<Bitmap>()
- override val lastVideo = publishRelayOf<String>()
- override val recording = recordingRelay.distinctUntilChanged()
- override val previewSnapshotProcessing = publishRelayOf<Unit>()
- override val lastPreviewSnapshot = publishRelayOf<Bitmap>()
- override val prePhotoSnapshot = publishRelayOf<GPUImageTexture2d>()
- override val cameraMode = behaviourRelayOf(CameraService.Mode.BACK)
- private val cameraModeRelay = behaviourRelayOf(CameraService.Mode.BACK)
- override val flashEnabled: BehaviorRelay<Boolean> = behaviourRelayOf(false)
- private val zoom = behaviourRelayOf(1f)
- private val focus = publishRelayOf<Pair<Float, Float>>()
- private val cameraRequest = publishRelayOf<Boolean>()
- private val cameraLock = behaviourRelayOf(false)
- //private val takeSnapshotRequest = publishRelayOf<Unit>()
- private val takePhotoRequest = publishRelayOf<Unit>()
- override val cameraAvailable = publishRelayOf<Boolean>()
- private val _pauseLock = behaviourRelayOf(false)
- private val pauseLock = _pauseLock.distinctUntilChanged()
- private val _camera = behaviourOptionalRelayOf<Camera>()
- // действует такой инвариант: если camera не null, то она доступна ко всем функциям, иначе ее нельзя юзать
- private val camera = Observables.combineLatest(_camera, pauseLock).map { if (it.second) Optional.of(null) else it.first }
- private val cameraRelay = publishOptionalRelayOf<Camera>()
- private val previewSize = publishRelayOf<Camera.Size>()
- private val textureUpdate = publishRelayOf<Unit>()
- private val texture = behaviourOptionalRelayOf<GPUImageTextureRenderer.SurfaceTextureHolder>()
- private val textureRelay = publishOptionalRelayOf<GPUImageTextureRenderer.SurfaceTextureHolder>()
- private val recordRenderer = behaviourOptionalRelayOf<GPUImageRecordTextureRenderer>()
- private val glContext = behaviourOptionalRelayOf<GPUImageGlContext>()
- private val renderingEnabled = behaviourRelayOf(true)
- //private val rawPreview = publishRelayOf<ByteArray>()
- private val rawPhoto = publishRelayOf<ByteArray>()
- private val postPhoto = publishRelayOf<Bitmap>()
- private val file = publishOptionalRelayOf<String>()
- init {
- disposable += cameraRequest
- .observeOn(cameraScheduler)
- .subscribeOn(cameraScheduler)
- .withLatestFrom(cameraLock)
- .filter { (_, lock) -> !lock }
- .map { it.first }
- .withLatestFrom(_camera, cameraModeRelay)
- .subscribe { (on, oldCamera, mode) ->
- cameraLock.accept(true)
- oldCamera.request()?.let { camera ->
- this._camera.acceptNull()
- if (BuildConfig.DEBUG) Log.e("kek4es", "camera stop")
- camera.stopPreview()
- camera.setPreviewCallback(null)
- camera.release()
- }
- val mNumberOfCameras = Camera.getNumberOfCameras()
- val cameraInfo = Camera.CameraInfo()
- var cameraId = -1
- if (on) {
- for (i in 0 until mNumberOfCameras) {
- Camera.getCameraInfo(i, cameraInfo)
- if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT && mode == CameraService.Mode.FRONT) {
- cameraId = i
- break
- }
- if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK && mode == CameraService.Mode.BACK) {
- cameraId = i
- break
- }
- }
- }
- if (BuildConfig.DEBUG) Log.e("kek4es", "cam ${cameraId}")
- try {
- if (cameraId != -1) {
- cameraMode.accept(mode)
- }
- cameraRelay.accept(Optional.of(if (cameraId != -1) Camera.open(cameraId) else null))
- } catch (e: Exception) {
- cameraAvailable.accept(false)
- }
- }
- disposable += cameraModeRelay
- .observeOn(cameraScheduler)
- .distinctUntilChanged()
- .withLatestFrom(camera, cameraRequest)
- .subscribeOn(cameraScheduler)
- .subscribe {(mode, camera, shouldEnable) ->
- if (BuildConfig.DEBUG) Log.e("kek4es", "qwq ${mode}")
- camera.request()?.let {
- cameraRequest.accept(shouldEnable)
- }
- }
- disposable += cameraRelay
- .observeOn(cameraScheduler)
- .withLatestFrom(texture)
- .subscribeOn(cameraScheduler)
- .subscribe { (camera, texture) ->
- camera.request()?.let { camera -> texture.request()?.let { texture ->
- camera.setDisplayOrientation(90)
- val parameters = camera.parameters
- if (parameters.supportedFocusModes.contains(
- Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
- parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
- }
- val previewSize = pickSizeThatFits(WIDTH, HEIGHT, parameters.supportedPreviewSizes)
- val pictureSize = pickSizeThatFits(WIDTH, HEIGHT, parameters.supportedPictureSizes)
- previewSize?.let {
- parameters.setPreviewSize(it.width, it.height)
- this.previewSize.accept(it)
- /*val desiredSize = "${previewSize.width}x${previewSize.height}"
- if (parameters.get("video-size-values").split(",").contains(desiredSize)) {
- parameters.set("video-size", desiredSize)
- parameters.setRecordingHint(true)
- }*/
- }
- pictureSize?.let {
- parameters.setPictureSize(it.width, it.height)
- }
- camera.parameters = parameters
- camera.setPreviewTexture(texture.surfaceTexture)
- texture.surfaceTexture.setOnFrameAvailableListener({ textureUpdate.accept(Unit) })
- camera.startPreview()
- if (BuildConfig.DEBUG) Log.e("kek4es", "preview started")
- _camera.accept(Optional.of(camera))
- }}
- cameraLock.accept(false)
- }
- disposable += glContext
- .subscribe { context ->
- context.request()?.let { context ->
- context.enqueue {
- val textureHolder = GPUImageTextureRenderer.SurfaceTextureHolder(context)
- textureHolder.init()
- val renderer = GPUImageRecordTextureRenderer(context, textureHolder)
- renderer.setFilter(null)
- recordRenderer.accept(Optional.of(renderer))
- textureRelay.accept(Optional.of(textureHolder))
- }
- }
- }
- disposable += Observables.combineLatest(recordRenderer, previewSize)
- .subscribe { (recordRenderer, previewSize) ->
- recordRenderer.request()?.let {
- it.setRotation(Rotation.ROTATION_90)
- it.setImageSize(previewSize.width, previewSize.height)
- }
- }
- disposable += textureRelay
- .subscribeOn(cameraScheduler)
- .observeOn(cameraScheduler)
- .withLatestFrom(camera)
- .subscribe {(texture, camera) ->
- camera.request()?.let { camera ->
- camera.setPreviewTexture(null)
- camera.setPreviewCallback(null)
- camera.stopPreview()
- texture.request()?.let { texture ->
- camera.setPreviewTexture(texture.surfaceTexture)
- texture.surfaceTexture.setOnFrameAvailableListener({ textureUpdate.accept(Unit) }, Handler(cameraThread.looper))
- camera.startPreview()
- }}
- if (BuildConfig.DEBUG) Log.e("kek4es", "kek1")
- this.texture.accept(texture)
- }
- /*disposable += takeSnapshotRequest
- .observeOn(cameraScheduler)
- .withLatestFrom(camera)
- .subscribe { (_, camera) ->
- camera.request()?.let {
- it.setOneShotPreviewCallback { data, _ ->
- previewSnapshotProcessing.accept(Unit)
- rawPreview.accept(data)
- }
- }
- }*/
- disposable += textureUpdate
- .observeOn(cameraScheduler)
- .withLatestFrom(recordRenderer, renderingEnabled)
- .subscribe { (_, recordRenderer, renderingEnabled) ->
- recordRenderer.request()?.let {
- it.setClearMode(!renderingEnabled)
- }
- }
- disposable += takePhotoRequest
- .subscribeOn(cameraScheduler)
- .observeOn(cameraScheduler)
- .withLatestFrom(camera, recordRenderer)
- .subscribe { (_, camera, renderer) ->
- camera.request()?.let {
- _pauseLock.accept(true)
- renderer.request()?.let { it.requestSnapshot { prePhotoSnapshot.accept(it) } }
- it.takePicture(null, null, Camera.PictureCallback { data, _ ->
- rawPhoto.accept(data)
- it.startPreview()
- _pauseLock.accept(false)
- })
- }
- }
- /*disposable += rawPreview
- .subscribeOn(cameraScheduler)
- .observeOn(cameraScheduler)
- .withLatestFrom(cameraMode, previewSize)
- .withLatestFrom(zoom)
- .observeOn(rawPhotoScheduler)
- .subscribe { (triple, zoom) ->
- val (data, mode, previewSize) = triple
- converter.onSizeChange(previewSize.width, previewSize.height)
- converter.updateData(data)
- converter.resultBitmap?.let {
- val res = producePhoto(it, mode, zoom)
- lastPreviewSnapshot.accept(res)
- }
- }*/
- disposable += rawPhoto
- .observeOn(rawPhotoScheduler)
- .withLatestFrom(cameraMode, zoom)
- .subscribe { (bytes, mode, zoom) ->
- val t = System.currentTimeMillis()
- val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
- if (BuildConfig.DEBUG) Log.e("kek4es", "decode time: " + (System.currentTimeMillis() - t))
- val resultBitmap = producePhoto(bitmap, mode, zoom)
- if (BuildConfig.DEBUG) Log.e("kek4es", "produce time: " + (System.currentTimeMillis() - t))
- postPhoto.accept(resultBitmap)
- }
- disposable += postPhoto
- .observeOn(cameraScheduler)
- .withLatestFrom(camera)
- .subscribeOn(cameraScheduler)
- .subscribe {
- lastPhoto.accept(it.first)
- }
- disposable += recording
- .observeOn(cameraScheduler)
- .withLatestFrom(recordRenderer, camera)
- .withLatestFrom(flashEnabled)
- .subscribeOn(cameraScheduler)
- .subscribe { (triple, flashEnabled) ->
- val (recording, recordRenderer, camera) = triple
- if (!recording) {
- recordRenderer.request()?.stopRecording {
- file.acceptNull()
- }
- camera.request()?.let { camera ->
- val parameters = camera.parameters
- if (flashEnabled) {
- setUpFlashMode(parameters, false)
- camera.parameters = parameters
- setUpFlashMode(parameters, flashEnabled)
- camera.parameters = parameters
- }
- }
- return@subscribe
- }
- val file = File.createTempFile(UUID.randomUUID().toString(), ".mp4", Application.INSTANCE.applicationContext.cacheDir)
- if (file == null) {
- return@subscribe
- }
- this.file.accept(Optional.of(file.absolutePath))
- }
- disposable += file
- .observeOn(cameraScheduler)
- .distinctUntilChanged()
- .scan { oldFile, newFile ->
- oldFile.request()?.let {
- lastVideo.accept(it)
- }
- newFile
- }
- .withLatestFrom(recordRenderer, camera)
- .withLatestFrom(flashEnabled)
- .subscribe { (triple, flashEnabled) ->
- val (file, recordRenderer, camera) = triple
- camera.request()?.let {camera -> file.request()?.let { file ->
- if (flashEnabled) {
- val parameters = camera.parameters
- setUpFlashMode(parameters, flashEnabled, true)
- camera.parameters = parameters
- }
- val width = camera.parameters.previewSize.width
- val height = camera.parameters.previewSize.height
- recordRenderer.request()?.startRecording(file, height, width, 0)
- return@subscribe
- }}
- }
- disposable += renderingEnabled
- .withLatestFrom(recordRenderer)
- .subscribe { (renderingEnabled, recordRenderer) ->
- if (renderingEnabled) {
- this.recordRenderer.accept(recordRenderer)
- }
- }
- disposable += focus
- .observeOn(cameraScheduler)
- .withLatestFrom(camera, glContext)
- .withLatestFrom(previewSize, zoom)
- .subscribeOn(cameraScheduler)
- .subscribe { (triple, previewSize, zoom) ->
- val (point, camera, glContext) = triple
- glContext.request()?.let { glContext -> camera.request()?.let { camera ->
- val parameters = camera.parameters
- parameters.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
- parameters.focusAreas = listOf(getFocusArea(glContext.width, glContext.height, previewSize.width, previewSize.height, zoom, point))
- camera.cancelAutoFocus()
- camera.parameters = parameters
- camera.autoFocus { _, _ ->
- camera.cancelAutoFocus()
- val parameters = camera.parameters
- parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
- camera.parameters = parameters
- }
- }}
- }
- disposable += Observables.combineLatest(glContext, recordRenderer)
- .subscribe { (glContext, recordRenderer) ->
- glContext.request()?.let {
- it.setMainRenderer(recordRenderer.request())
- }
- }
- disposable += flashEnabled
- .withLatestFrom(camera)
- .observeOn(cameraScheduler)
- .subscribe { (flashEnabled, camera) ->
- camera.request()?.let {
- val parameters = it.parameters
- setUpFlashMode(parameters, flashEnabled)
- it.parameters = parameters
- }
- }
- disposable += zoom
- .observeOn(cameraScheduler)
- .withLatestFrom(recordRenderer)
- .subscribe { (zoom, renderer) ->
- renderer.request()?.let { renderer ->
- renderer.setScale(zoom)
- }
- }
- }
- override fun notifyStartVideoRecordingRequested() {
- recordingRelay.accept(true)
- }
- override fun notifyFinishVideoRecordingRequested() {
- recordingRelay.accept(false)
- }
- override fun notifyToggleCameraMode(mode: CameraService.Mode) {
- cameraModeRelay.accept(mode)
- }
- override fun notifyFlashEnabled(enabled: Boolean) {
- flashEnabled.accept(enabled)
- }
- override fun notifyFocusChanged(point: Pair<Float, Float>) {
- focus.accept(point)
- }
- override fun notifyZoomChanged(zoom: Float) {
- this.zoom.accept(zoom)
- }
- override fun notifyTakeSnapshotRequested() {
- //takeSnapshotRequest.accept(Unit)
- }
- override fun notifyTakePhotoRequested() {
- takePhotoRequest.accept(Unit)
- }
- override fun notifyCameraStart() {
- //rendererUpdateRequest.accept(true)
- cameraRequest.accept(true)
- }
- override fun notifyCameraEnd() {
- //rendererUpdateRequest.accept(true)
- cameraRequest.accept(false)
- }
- override fun notifyDrawEnabled(enabled: Boolean) {
- renderingEnabled.accept(enabled)
- }
- override fun attachGlContext(context: GPUImageGlContext) {
- glContext.accept(Optional.of(context))
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement