Runt
2025-04-13 8c0f96b20f4cf44d230b373f51261566af02de8e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
package com.runt.live.media
 
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.ImageFormat
import android.graphics.Point
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.util.Log
import android.util.Size
import androidx.annotation.OptIn
import androidx.camera.camera2.interop.ExperimentalCamera2Interop
import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.core.resolutionselector.ResolutionSelector
import androidx.camera.core.resolutionselector.ResolutionStrategy
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import com.google.common.util.concurrent.ListenableFuture
import com.runt.live.cpp.LiveMiniView
import com.runt.live.cpp.LiveMiniView.mainStreamCode
import com.runt.live.data.StreamWindow
import com.runt.live.enum.LiveState
import com.runt.live.ui.stream.LiveViewModel
import com.runt.live.util.BitmapUtils
import com.runt.open.mvi.OpenApplication
import java.text.SimpleDateFormat
import java.util.Date
import java.util.concurrent.LinkedBlockingQueue
import kotlin.concurrent.thread
 
 
/**
 * @author Runt(qingingrunt2010 @ qq.com)
 * @purpose
 * @date 9/22/24
 */
class CameraHelper : LifecycleOwner{
 
    val TAG = "CameraHelper";
 
    data class BytesData(val bitmap : Bitmap,val bytes:ByteArray,val timestamp : Long)
    protected var imagesQueue = LinkedBlockingQueue<BytesData>()
 
 
    private var cameraProviderFuture : ListenableFuture<ProcessCameraProvider>? = null
    var camera : Camera? = null
    var cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
    //用于预览
    val preview = Preview.Builder().build()
    //用于拍照
    val imageCapture = ImageCapture.Builder().setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY).build()
    //设置分辨率
    val strategy = ResolutionStrategy(Size(LiveMiniView.FRAME_HEIGHT,LiveMiniView.FRAME_WIDTH) , ResolutionStrategy.FALLBACK_RULE_NONE)
    val resolutionBuilder = ResolutionSelector.Builder().setResolutionStrategy(strategy)
    val imageAnalysis = ImageAnalysis.Builder().setOutputImageRotationEnabled(true) //允许输出流旋转
        .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
        .setResolutionSelector(resolutionBuilder.build()).build()
 
    val mContext:Context;
    var mStreamWindow: StreamWindow? = null;
    var mViewModel:LiveViewModel
    val mLifecycle:LifecycleRegistry;
    var startTime = 0L;
    var dateFmt = SimpleDateFormat("hh:MM:ss");
 
 
 
    constructor(context:Context,viewModel:LiveViewModel){
        mContext = context;
        mViewModel = viewModel;
        mLifecycle = LifecycleRegistry(mContext as LifecycleOwner)
        mLifecycle.currentState = Lifecycle.State.CREATED
        mLifecycle.currentState = Lifecycle.State.STARTED
        val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager;
        for(cameraId in cameraManager.cameraIdList){
            var  outputSizes = cameraManager.getCameraCharacteristics(cameraId).get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!.getOutputSizes(ImageFormat.YUV_420_888)
            val characteristics = cameraManager !!.getCameraCharacteristics(cameraId)
            val lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING)
            Log.d("CameraX" , "Camera ID: $cameraId, Facing: $lensFacing")
            for (size in outputSizes){
                Log.i(TAG , "cameraId: ${cameraId} size: ${size}")
            }
        }
        startPushData();
 
 
        //detectInputDeviceUsb(context)
    }
 
    private fun stopCamera(){
        cameraProviderFuture?.get()?.unbindAll()
        imageAnalysis.clearAnalyzer()
        camera = null;
    }
 
    fun closeCamera(){
        stopCamera()
        //audioRecord!!.stop()
        mLifecycle.currentState = Lifecycle.State.DESTROYED
        imagesQueue.clear()
    }
 
 
    @OptIn(ExperimentalCamera2Interop::class)
    @SuppressLint("RestrictedApi")
    fun swichCamera(){
        Log.i(TAG , "swichCamera: ${cameraSelector }")
        var selector = CameraSelector.LENS_FACING_FRONT;
        /*if(cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA){
            cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_EXTERNAL).build();
        }else */if(cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA){
            selector = CameraSelector.LENS_FACING_BACK;
        }else{
            selector = CameraSelector.LENS_FACING_FRONT;
        }
        cameraSelector = CameraSelector.Builder().requireLensFacing(selector) // 试试 BACK 代替 EXTERNAL
            .build();
        /*cameraSelector = CameraSelector.Builder().addCameraFilter { cameraInfos : List<CameraInfo> ->
            val filteredList : MutableList<CameraInfo> = ArrayList<CameraInfo>()
            for (cameraInfo in cameraInfos) {
                val camera2CameraInfo = cameraInfo as Camera2CameraInfo
                Log.d("CameraX" , "Found Camera ID: " + camera2CameraInfo.cameraId)
                filteredList.add(cameraInfo)
            }
            filteredList
        }.build()*/
        stopCamera()
        Log.i(TAG , "bindCamera: ${cameraSelector}")
        bindCamera(cameraSelector)
    }
 
    fun startPushData(){
        thread {
            while (mLifecycle.currentState != Lifecycle.State.DESTROYED){
                var imageData = imagesQueue.take();
                mStreamWindow?.let { streamWindow ->
                    if(streamWindow.videoDelay > 0){
                        var  tempStamp = Date().time;
                        var c = (imageData.timestamp + streamWindow.videoDelay) - tempStamp;
                        Log.i(TAG , "startPushData: ${streamWindow.videoDelay} ${c}")
                        if(c < streamWindow.videoDelay && c > 0 ){
                            Thread.sleep(c);
                        }
                    }
                }
                if(mStreamWindow!!.sizeState.value.x != imageData.bitmap.width){
                    if (imageData.bitmap.width > imageData.bitmap.height && mStreamWindow!!.viewRateState.value < 0.4) {
                        mStreamWindow!!.viewRateState.value = 0.4f
                    }
                    mStreamWindow!!.sizeState.value = Point(imageData.bitmap.width,imageData.bitmap.height);
                }
                if(mStreamWindow!!.videoState.value == LiveState.IN_LIVE || mStreamWindow!!.id == mainStreamCode){
                    mViewModel?.let {
                        //val bytes = BitmapUtils.instance !!.YUV420toNV21(image) !!
                        it.pushNV21(mStreamWindow!!.id,imageData.bytes)
                    }
                }
                if(OpenApplication.getApplication().isInfront()){
                    mStreamWindow?.surfaceHolder?.let {
                        //Log.i(TAG , "渲染: ${it.surfaceFrame.width()} ${it.surfaceFrame.height()}  ${it.hashCode()}")
                        BitmapUtils.instance!!.cavansSurface(it,imageData.bitmap);
                    }
                    imageData.bitmap.recycle();
                }
            }
        }
    }
 
    fun openCamera(streamWindow : StreamWindow?) {
        mStreamWindow = streamWindow;
        cameraProviderFuture = ProcessCameraProvider.getInstance(mContext)
        cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
        cameraProviderFuture!!.addListener(Runnable {
            bindCamera(cameraSelector)
            mStreamWindow?.listener?.onStarted?.invoke()
        } , ContextCompat.getMainExecutor(mContext))
        startTime = Date().time;
    }
 
    @SuppressLint("UnsafeOptInUsageError")
    private fun bindCamera(selector : CameraSelector){
        val cameraProvider = cameraProviderFuture!!.get()
        cameraProvider.unbindAll()
        var t2 = Date().time;
        var t_fps = 0
        imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(mContext)) { image : ImageProxy ->
            t_fps += 1;
            if (Date().time - t2 > 1000) {
                Log.e(TAG , "bindCamera fps: ${t_fps}" )
                t_fps = 0
                t2 = Date().time
            }
            val bytes : ByteArray = BitmapUtils.instance!!.decodeToNV21(image)!!
            var bitmap = image.toBitmap();
            image.close()
            imagesQueue.put(BytesData(bitmap,bytes,Date().time))
        }
        //preview.setSurfaceProvider(mStreamWindow.surfaceHolder!!)
        camera = cameraProvider.bindToLifecycle(this , selector , preview , imageCapture , imageAnalysis)
        mLifecycle.currentState = Lifecycle.State.RESUMED
    }
 
    interface OnFrameUpdateListener{
        fun onUpdate(image:ImageProxy);
    }
 
    override val lifecycle : Lifecycle
        get() = mLifecycle
 
}