|
@@ -11,8 +11,10 @@ import android.view.SurfaceHolder
|
|
import android.view.SurfaceView
|
|
import android.view.SurfaceView
|
|
import android.view.View
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
import android.view.ViewGroup
|
|
|
|
+import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
|
import android.widget.Button
|
|
import android.widget.Button
|
|
import android.widget.TextView
|
|
import android.widget.TextView
|
|
|
|
+import androidx.constraintlayout.widget.ConstraintLayout
|
|
import androidx.fragment.app.activityViewModels
|
|
import androidx.fragment.app.activityViewModels
|
|
import com.cr.common.CrUnitManager
|
|
import com.cr.common.CrUnitManager
|
|
import com.cr.cruav.CrApplication
|
|
import com.cr.cruav.CrApplication
|
|
@@ -20,7 +22,10 @@ import com.cr.cruav.R
|
|
import com.cr.data.CrUtil
|
|
import com.cr.data.CrUtil
|
|
import com.cr.event.CrCommonAction
|
|
import com.cr.event.CrCommonAction
|
|
import com.cr.event.EventCommon
|
|
import com.cr.event.EventCommon
|
|
|
|
+import com.cr.models.CompletionModel
|
|
|
|
+import com.cr.models.ICompletion
|
|
import com.cr.viewmodel.CrFlightControlVM
|
|
import com.cr.viewmodel.CrFlightControlVM
|
|
|
|
+import com.cr.viewmodel.CrLiveStreamVM
|
|
import com.cr.viewmodel.CrVideoChannelListener
|
|
import com.cr.viewmodel.CrVideoChannelListener
|
|
import com.cr.viewmodel.CrVideoChannelVM
|
|
import com.cr.viewmodel.CrVideoChannelVM
|
|
import com.squareup.otto.Subscribe
|
|
import com.squareup.otto.Subscribe
|
|
@@ -44,7 +49,7 @@ import java.io.OutputStream
|
|
* 创建日期:2023/3/10 08:50
|
|
* 创建日期:2023/3/10 08:50
|
|
* 描述:图传窗口
|
|
* 描述:图传窗口
|
|
*/
|
|
*/
|
|
-class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
|
|
|
|
|
|
+class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback, OnGlobalLayoutListener {
|
|
/**
|
|
/**
|
|
* FPV窗口监听
|
|
* FPV窗口监听
|
|
*/
|
|
*/
|
|
@@ -59,10 +64,14 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
|
|
// define: 2023/3/10 将飞行器视图绑定到模型
|
|
// define: 2023/3/10 将飞行器视图绑定到模型
|
|
private val flightControlVm: CrFlightControlVM by activityViewModels()
|
|
private val flightControlVm: CrFlightControlVM by activityViewModels()
|
|
|
|
|
|
|
|
+ // define: 2023/9/12 绑定直播模型
|
|
|
|
+ private val liveStreamVm: CrLiveStreamVM by activityViewModels()
|
|
|
|
+
|
|
// define: 2023/3/10 延迟初始化一个视频画布
|
|
// define: 2023/3/10 延迟初始化一个视频画布
|
|
private lateinit var surfaceView: SurfaceView
|
|
private lateinit var surfaceView: SurfaceView
|
|
private var btnTrigger: Button? = null
|
|
private var btnTrigger: Button? = null
|
|
private var lblInfo: TextView? = null // define: 2023/8/4 显示信息
|
|
private var lblInfo: TextView? = null // define: 2023/8/4 显示信息
|
|
|
|
+ private var panelFpv: ConstraintLayout? = null // define: 2023/9/14 容器
|
|
|
|
|
|
// define: 2023/3/11 视频通道
|
|
// define: 2023/3/11 视频通道
|
|
private var curVideoChannel: IVideoChannel? = null
|
|
private var curVideoChannel: IVideoChannel? = null
|
|
@@ -77,6 +86,10 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
|
|
private var heightChange: Boolean = false
|
|
private var heightChange: Boolean = false
|
|
private var fps: Int = -1
|
|
private var fps: Int = -1
|
|
|
|
|
|
|
|
+ // todo: 2023/9/14 画布相关参数
|
|
|
|
+ private var surfaceViewWidth: Int = 0
|
|
|
|
+ private var surfaceViewHeight: Int = 0
|
|
|
|
+
|
|
// todo: 2023/3/13 监听
|
|
// todo: 2023/3/13 监听
|
|
lateinit var listener: FPVListener
|
|
lateinit var listener: FPVListener
|
|
|
|
|
|
@@ -101,6 +114,8 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
|
|
surfaceView = view.findViewById(R.id.surface_fpv)
|
|
surfaceView = view.findViewById(R.id.surface_fpv)
|
|
btnTrigger = view.findViewById(R.id.fpv_trigger)
|
|
btnTrigger = view.findViewById(R.id.fpv_trigger)
|
|
btnTrigger?.setOnClickListener(clickListener)
|
|
btnTrigger?.setOnClickListener(clickListener)
|
|
|
|
+ // todo: 2023/9/14 挂接容器
|
|
|
|
+ panelFpv = view.findViewById(R.id.fpv_panel)
|
|
// todo: 2023/8/4 挂接信息显示组件
|
|
// todo: 2023/8/4 挂接信息显示组件
|
|
lblInfo = view.findViewById(R.id.fpv_info)
|
|
lblInfo = view.findViewById(R.id.fpv_info)
|
|
// todo: 2023/3/10 设置视图控件监听
|
|
// todo: 2023/3/10 设置视图控件监听
|
|
@@ -111,6 +126,26 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
|
|
+ * 添加视图尺寸变化监听
|
|
|
|
+ */
|
|
|
|
+ private fun addSizeChangeForSurfaceView() {
|
|
|
|
+ surfaceView.viewTreeObserver!!.addOnGlobalLayoutListener(this)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 画布尺寸变化监听
|
|
|
|
+ */
|
|
|
|
+ override fun onGlobalLayout() {
|
|
|
|
+ if (surfaceViewWidth != surfaceView.width && surfaceViewHeight != surfaceView.height) {
|
|
|
|
+ surfaceViewWidth = surfaceView.width
|
|
|
|
+ surfaceViewHeight = surfaceView.height
|
|
|
|
+ CrUtil.print("宽度:${surfaceViewWidth} 高度:${surfaceViewHeight}")
|
|
|
|
+ // todo: 2023/9/14 移除监听 要不会多次调用
|
|
|
|
+ surfaceView.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
* 视图创建完成后调用
|
|
* 视图创建完成后调用
|
|
* @param view View
|
|
* @param view View
|
|
* @param savedInstanceState Bundle?
|
|
* @param savedInstanceState Bundle?
|
|
@@ -130,9 +165,22 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
|
|
// todo: 2023/3/13 切换视频通道
|
|
// todo: 2023/3/13 切换视频通道
|
|
videoChannelVM.triggerStreamSource()
|
|
videoChannelVM.triggerStreamSource()
|
|
}
|
|
}
|
|
- R.id.fpv_layout -> {
|
|
|
|
|
|
+ R.id.fpv_panel -> {
|
|
// todo: 2023/3/13 切换窗口
|
|
// todo: 2023/3/13 切换窗口
|
|
- listener?.triggerWindow()
|
|
|
|
|
|
+ listener.triggerWindow()
|
|
|
|
+// // todo: 2023/9/14 添加画布尺寸变化监听
|
|
|
|
+// // todo: 2023/9/14 主要是为了在画布变化时可以调整解码器解码的宽度和高度
|
|
|
|
+// addSizeChangeForSurfaceView()
|
|
|
|
+ // todo: 2023/9/15 判断当前是否在直播 如果是 则先停止 再开启
|
|
|
|
+ if (liveStreamVm?.streamStatusInfo.value!!.isStreaming) {
|
|
|
|
+ liveStreamVm.stopStream(object : ICompletion<String> {
|
|
|
|
+ // todo: 2023/9/15 停止完成后回调
|
|
|
|
+ override fun onCompletion(completion: CompletionModel<String>) {
|
|
|
|
+ if (completion.isSuccess == true)
|
|
|
|
+ liveStreamVm?.startStream(null)
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -145,7 +193,7 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
|
|
videoChannelVM.videoChannelInfo.observe(viewLifecycleOwner) {
|
|
videoChannelVM.videoChannelInfo.observe(viewLifecycleOwner) {
|
|
it?.let {
|
|
it?.let {
|
|
val videoStreamInfo =
|
|
val videoStreamInfo =
|
|
- "fps:[${it.fps}] width:[${videoWidth}] height:[${videoHeight}]"
|
|
|
|
|
|
+ "帧率:[${it.fps}] 宽:[${videoWidth}] 高:[${videoHeight}]"
|
|
lblInfo?.text = videoStreamInfo
|
|
lblInfo?.text = videoStreamInfo
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -189,14 +237,27 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
|
|
* 设置视频通道到画布
|
|
* 设置视频通道到画布
|
|
*/
|
|
*/
|
|
private fun setChannelToSurface() {
|
|
private fun setChannelToSurface() {
|
|
|
|
+ // todo: 2023/9/15 释放解码器资源
|
|
|
|
+ videoDecoder?.let {
|
|
|
|
+ videoDecoder?.removeDecoderStateChangeListener(decoderStateChangeListener)
|
|
|
|
+ videoDecoder?.onPause()
|
|
|
|
+ videoDecoder?.destroy()
|
|
|
|
+ videoDecoder = null
|
|
|
|
+ }
|
|
|
|
+ // todo: 2023/9/15 重新判断初始化
|
|
if (videoDecoder == null) {
|
|
if (videoDecoder == null) {
|
|
curVideoChannel?.let {
|
|
curVideoChannel?.let {
|
|
videoDecoder = VideoDecoder(
|
|
videoDecoder = VideoDecoder(
|
|
this@FragmentFPV.context,
|
|
this@FragmentFPV.context,
|
|
it.videoChannelType,
|
|
it.videoChannelType,
|
|
DecoderOutputMode.SURFACE_MODE,
|
|
DecoderOutputMode.SURFACE_MODE,
|
|
- surfaceView.holder
|
|
|
|
|
|
+ surfaceView.holder,
|
|
|
|
+ surfaceView.width,
|
|
|
|
+ surfaceView.height,
|
|
|
|
+ true
|
|
)
|
|
)
|
|
|
|
+ // todo: 2023/9/12 设置直播视频通道
|
|
|
|
+// liveStreamVm.setVideoChannel(it.videoChannelType)
|
|
// todo: 2023/3/10 添加解码状态监听
|
|
// todo: 2023/3/10 添加解码状态监听
|
|
videoDecoder?.addDecoderStateChangeListener(decoderStateChangeListener)
|
|
videoDecoder?.addDecoderStateChangeListener(decoderStateChangeListener)
|
|
}
|
|
}
|
|
@@ -206,7 +267,7 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
- * 编码状态监听
|
|
|
|
|
|
+ * 解码器状态监听
|
|
*/
|
|
*/
|
|
private val decoderStateChangeListener =
|
|
private val decoderStateChangeListener =
|
|
DecoderStateChangeListener { oldState, newState ->
|
|
DecoderStateChangeListener { oldState, newState ->
|
|
@@ -281,7 +342,7 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
|
|
/**
|
|
/**
|
|
* 释放资源
|
|
* 释放资源
|
|
*/
|
|
*/
|
|
- private fun releaseResource(){
|
|
|
|
|
|
+ private fun releaseResource() {
|
|
// todo: 2023/8/7 解除监听
|
|
// todo: 2023/8/7 解除监听
|
|
curVideoChannel?.removeStreamDataListener(streamDataListener)
|
|
curVideoChannel?.removeStreamDataListener(streamDataListener)
|
|
videoDecoder?.removeDecoderStateChangeListener(decoderStateChangeListener)
|
|
videoDecoder?.removeDecoderStateChangeListener(decoderStateChangeListener)
|
|
@@ -375,7 +436,7 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
|
|
// todo: 2023/8/7 执行一次保存 就恢复图传
|
|
// todo: 2023/8/7 执行一次保存 就恢复图传
|
|
handlerYUV(true)
|
|
handlerYUV(true)
|
|
// todo: 2023/8/7 保存图片
|
|
// todo: 2023/8/7 保存图片
|
|
- saveYuvData(mediaFormat,data,width,height)
|
|
|
|
|
|
+ saveYuvData(mediaFormat, data, width, height)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -390,32 +451,34 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
|
|
private fun saveYuvData(mediaFormat: MediaFormat?, data: ByteArray?, width: Int, height: Int) {
|
|
private fun saveYuvData(mediaFormat: MediaFormat?, data: ByteArray?, width: Int, height: Int) {
|
|
data?.let {
|
|
data?.let {
|
|
mediaFormat?.let {
|
|
mediaFormat?.let {
|
|
- when(it.getInteger(MediaFormat.KEY_COLOR_FORMAT)){
|
|
|
|
- MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar->{
|
|
|
|
- newSaveYuvDataToJPEG420P(data,width,height)
|
|
|
|
|
|
+ when (it.getInteger(MediaFormat.KEY_COLOR_FORMAT)) {
|
|
|
|
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar -> {
|
|
|
|
+ newSaveYuvDataToJPEG420P(data, width, height)
|
|
}
|
|
}
|
|
- MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar->{
|
|
|
|
- newSaveYuvDataToJPEG(data,width,height)
|
|
|
|
|
|
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar -> {
|
|
|
|
+ newSaveYuvDataToJPEG(data, width, height)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- }?:sendFailureMessage("数据格式异常")
|
|
|
|
- }?:sendFailureMessage("无图像数据")
|
|
|
|
|
|
+ } ?: sendFailureMessage("数据格式异常")
|
|
|
|
+ } ?: sendFailureMessage("无图像数据")
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
* 发送错误消息事件
|
|
* 发送错误消息事件
|
|
* @param error String 错误消息
|
|
* @param error String 错误消息
|
|
*/
|
|
*/
|
|
- private fun sendFailureMessage(error:String){
|
|
|
|
- CrApplication.getEventBus().post(EventCommon<String>(CrCommonAction.VIDEO_SAVE_YUV_FAILURE,error))
|
|
|
|
|
|
+ private fun sendFailureMessage(error: String) {
|
|
|
|
+ CrApplication.getEventBus()
|
|
|
|
+ .post(EventCommon<String>(CrCommonAction.VIDEO_SAVE_YUV_FAILURE, error))
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
* 发送YUA数据保存成功消息
|
|
* 发送YUA数据保存成功消息
|
|
* @param fileName String 保存的文件名称
|
|
* @param fileName String 保存的文件名称
|
|
*/
|
|
*/
|
|
- private fun sendSaveYUVSuccessMessage(fileName:String){
|
|
|
|
- CrApplication.getEventBus().post(EventCommon<String>(CrCommonAction.VIDEO_SAVE_YUV_SUCCESS,fileName))
|
|
|
|
|
|
+ private fun sendSaveYUVSuccessMessage(fileName: String) {
|
|
|
|
+ CrApplication.getEventBus()
|
|
|
|
+ .post(EventCommon<String>(CrCommonAction.VIDEO_SAVE_YUV_SUCCESS, fileName))
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -475,45 +538,53 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
|
|
height
|
|
height
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* 保存图片
|
|
* 保存图片
|
|
* @param buf ByteArray 数据
|
|
* @param buf ByteArray 数据
|
|
* @param width Int 宽度
|
|
* @param width Int 宽度
|
|
* @param height Int 高度
|
|
* @param height Int 高度
|
|
*/
|
|
*/
|
|
- private fun screenShot(buf:ByteArray,width:Int,height:Int){
|
|
|
|
|
|
+ private fun screenShot(buf: ByteArray, width: Int, height: Int) {
|
|
// todo: 2023/8/7 获取图片
|
|
// todo: 2023/8/7 获取图片
|
|
- val yuvImage = YuvImage(buf,ImageFormat.NV21,width,height,null)
|
|
|
|
|
|
+ val yuvImage = YuvImage(buf, ImageFormat.NV21, width, height, null)
|
|
// todo: 2023/8/7 保存文件
|
|
// todo: 2023/8/7 保存文件
|
|
- val outputFile:OutputStream
|
|
|
|
|
|
+ val outputFile: OutputStream
|
|
var fileName = "uav_${CrUnitManager.toSystemDate()}.jpg"
|
|
var fileName = "uav_${CrUnitManager.toSystemDate()}.jpg"
|
|
var path = "${CrUtil.IMAGE_PATH}${fileName}"
|
|
var path = "${CrUtil.IMAGE_PATH}${fileName}"
|
|
- outputFile = try{
|
|
|
|
|
|
+ outputFile = try {
|
|
FileOutputStream(File(path))
|
|
FileOutputStream(File(path))
|
|
- }catch (e:FileNotFoundException){
|
|
|
|
|
|
+ } catch (e: FileNotFoundException) {
|
|
sendFailureMessage("异常:${e.message}")
|
|
sendFailureMessage("异常:${e.message}")
|
|
return
|
|
return
|
|
}
|
|
}
|
|
// todo: 2023/8/7 压缩保存图片
|
|
// todo: 2023/8/7 压缩保存图片
|
|
- yuvImage.compressToJpeg(Rect(0,0,width,height),100,outputFile)
|
|
|
|
- try{
|
|
|
|
|
|
+ yuvImage.compressToJpeg(Rect(0, 0, width, height), 100, outputFile)
|
|
|
|
+ try {
|
|
outputFile.close()
|
|
outputFile.close()
|
|
// todo: 2023/8/7 发送正确消息
|
|
// todo: 2023/8/7 发送正确消息
|
|
sendSaveYUVSuccessMessage(fileName)
|
|
sendSaveYUVSuccessMessage(fileName)
|
|
- }catch (e:IOException){
|
|
|
|
|
|
+ } catch (e: IOException) {
|
|
sendFailureMessage("IO错误:${e.message}")
|
|
sendFailureMessage("IO错误:${e.message}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * 设置画布尺寸变化
|
|
|
|
+ */
|
|
|
|
+ fun setSurfaceViewSizeChange() {
|
|
|
|
+ var width = surfaceView.width
|
|
|
|
+ var height = surfaceView.height
|
|
|
|
+ CrUtil.print("宽度:${width} 高度:${height}")
|
|
|
|
+ }
|
|
|
|
+
|
|
// todo: 2023/8/7 订阅事件
|
|
// todo: 2023/8/7 订阅事件
|
|
@Subscribe
|
|
@Subscribe
|
|
- fun onChanged(event:EventCommon<String>){
|
|
|
|
- event?.let {
|
|
|
|
- when(it.action){
|
|
|
|
- CrCommonAction.VIDEO_SAVE_YUV->{
|
|
|
|
- // todo: 2023/8/7 切换到YUV模式
|
|
|
|
- handlerYUV(false)
|
|
|
|
- }
|
|
|
|
|
|
+ fun onChanged(event: EventCommon<String>) {
|
|
|
|
+ when (event.action) {
|
|
|
|
+ CrCommonAction.VIDEO_SAVE_YUV -> {
|
|
|
|
+ // todo: 2023/8/7 切换到YUV模式
|
|
|
|
+ handlerYUV(false)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|