|  | @@ -1,5 +1,10 @@
 | 
	
		
			
				|  |  |  package com.cr.pages
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +import android.graphics.ImageFormat
 | 
	
		
			
				|  |  | +import android.graphics.Rect
 | 
	
		
			
				|  |  | +import android.graphics.YuvImage
 | 
	
		
			
				|  |  | +import android.media.MediaCodecInfo
 | 
	
		
			
				|  |  | +import android.media.MediaFormat
 | 
	
		
			
				|  |  |  import android.os.Bundle
 | 
	
		
			
				|  |  |  import android.view.LayoutInflater
 | 
	
		
			
				|  |  |  import android.view.SurfaceHolder
 | 
	
	
		
			
				|  | @@ -9,15 +14,28 @@ import android.view.ViewGroup
 | 
	
		
			
				|  |  |  import android.widget.Button
 | 
	
		
			
				|  |  |  import android.widget.TextView
 | 
	
		
			
				|  |  |  import androidx.fragment.app.activityViewModels
 | 
	
		
			
				|  |  | +import com.cr.common.CrUnitManager
 | 
	
		
			
				|  |  | +import com.cr.cruav.CrApplication
 | 
	
		
			
				|  |  |  import com.cr.cruav.R
 | 
	
		
			
				|  |  | +import com.cr.data.CrUtil
 | 
	
		
			
				|  |  | +import com.cr.event.CrCommonAction
 | 
	
		
			
				|  |  | +import com.cr.event.EventCommon
 | 
	
		
			
				|  |  |  import com.cr.viewmodel.CrFlightControlVM
 | 
	
		
			
				|  |  |  import com.cr.viewmodel.CrVideoChannelListener
 | 
	
		
			
				|  |  |  import com.cr.viewmodel.CrVideoChannelVM
 | 
	
		
			
				|  |  | +import com.squareup.otto.Subscribe
 | 
	
		
			
				|  |  |  import dji.v5.common.video.channel.VideoChannelType
 | 
	
		
			
				|  |  |  import dji.v5.common.video.decoder.DecoderOutputMode
 | 
	
		
			
				|  |  |  import dji.v5.common.video.decoder.DecoderState
 | 
	
		
			
				|  |  |  import dji.v5.common.video.decoder.VideoDecoder
 | 
	
		
			
				|  |  |  import dji.v5.common.video.interfaces.*
 | 
	
		
			
				|  |  | +import kotlinx.coroutines.GlobalScope
 | 
	
		
			
				|  |  | +import kotlinx.coroutines.launch
 | 
	
		
			
				|  |  | +import java.io.File
 | 
	
		
			
				|  |  | +import java.io.FileNotFoundException
 | 
	
		
			
				|  |  | +import java.io.FileOutputStream
 | 
	
		
			
				|  |  | +import java.io.IOException
 | 
	
		
			
				|  |  | +import java.io.OutputStream
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /**
 | 
	
	
		
			
				|  | @@ -30,10 +48,11 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * FPV窗口监听
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  | -    interface FPVListener{
 | 
	
		
			
				|  |  | +    interface FPVListener {
 | 
	
		
			
				|  |  |          // todo: 2023/3/13 切换窗口
 | 
	
		
			
				|  |  |          fun triggerWindow()
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      // define: 2023/3/10 延迟初始化一个通道模型
 | 
	
		
			
				|  |  |      private val videoChannelVM: CrVideoChannelVM by activityViewModels()
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -43,7 +62,7 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
 | 
	
		
			
				|  |  |      // define: 2023/3/10 延迟初始化一个视频画布
 | 
	
		
			
				|  |  |      private lateinit var surfaceView: SurfaceView
 | 
	
		
			
				|  |  |      private var btnTrigger: Button? = null
 | 
	
		
			
				|  |  | -    private var lblInfo:TextView?=null // define: 2023/8/4 显示信息
 | 
	
		
			
				|  |  | +    private var lblInfo: TextView? = null // define: 2023/8/4 显示信息
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // define: 2023/3/11 视频通道
 | 
	
		
			
				|  |  |      private var curVideoChannel: IVideoChannel? = null
 | 
	
	
		
			
				|  | @@ -52,14 +71,14 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
 | 
	
		
			
				|  |  |      private var videoDecoder: IVideoDecoder? = null
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // todo: 2023/8/4 视频相关参数
 | 
	
		
			
				|  |  | -    private var videoWidth:Int = -1
 | 
	
		
			
				|  |  | -    private var videoHeight:Int = -1
 | 
	
		
			
				|  |  | -    private var widthChanged:Boolean = false
 | 
	
		
			
				|  |  | -    private var heightChange:Boolean = false
 | 
	
		
			
				|  |  | -    private var fps:Int = -1
 | 
	
		
			
				|  |  | +    private var videoWidth: Int = -1
 | 
	
		
			
				|  |  | +    private var videoHeight: Int = -1
 | 
	
		
			
				|  |  | +    private var widthChanged: Boolean = false
 | 
	
		
			
				|  |  | +    private var heightChange: Boolean = false
 | 
	
		
			
				|  |  | +    private var fps: Int = -1
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // todo: 2023/3/13 监听
 | 
	
		
			
				|  |  | -    lateinit var listener:FPVListener
 | 
	
		
			
				|  |  | +    lateinit var listener: FPVListener
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * 创建视图
 | 
	
	
		
			
				|  | @@ -75,7 +94,7 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
 | 
	
		
			
				|  |  |      ): View? {
 | 
	
		
			
				|  |  |          var view = inflater.inflate(R.layout.frag_fpv, container, false)
 | 
	
		
			
				|  |  |          // todo: 2023/8/4 关闭 View Layer。 View Layer 可以加速无 invalidate() 时的刷新效率,但对于需要调用 invalidate() 的刷新无法加速
 | 
	
		
			
				|  |  | -        view.setLayerType(View.LAYER_TYPE_NONE,null)
 | 
	
		
			
				|  |  | +        view.setLayerType(View.LAYER_TYPE_NONE, null)
 | 
	
		
			
				|  |  |          // todo: 2023/3/13 给View挂接事件
 | 
	
		
			
				|  |  |          view.setOnClickListener(clickListener)
 | 
	
		
			
				|  |  |          // todo: 2023/3/10 挂接图传视图控件
 | 
	
	
		
			
				|  | @@ -86,6 +105,8 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
 | 
	
		
			
				|  |  |          lblInfo = view.findViewById(R.id.fpv_info)
 | 
	
		
			
				|  |  |          // todo: 2023/3/10 设置视图控件监听
 | 
	
		
			
				|  |  |          surfaceView.holder.addCallback(this)
 | 
	
		
			
				|  |  | +        // todo: 2023/8/7 注册事件监听
 | 
	
		
			
				|  |  | +        CrApplication.getEventBus().register(this)
 | 
	
		
			
				|  |  |          return view
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -104,12 +125,12 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
 | 
	
		
			
				|  |  |       * 点击事件
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      private var clickListener = View.OnClickListener {
 | 
	
		
			
				|  |  | -        when(it.id){
 | 
	
		
			
				|  |  | +        when (it.id) {
 | 
	
		
			
				|  |  |              R.id.fpv_trigger -> {
 | 
	
		
			
				|  |  |                  // todo: 2023/3/13 切换视频通道
 | 
	
		
			
				|  |  |                  videoChannelVM.triggerStreamSource()
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | -            R.id.fpv_layout ->{
 | 
	
		
			
				|  |  | +            R.id.fpv_layout -> {
 | 
	
		
			
				|  |  |                  // todo: 2023/3/13 切换窗口
 | 
	
		
			
				|  |  |                  listener?.triggerWindow()
 | 
	
		
			
				|  |  |              }
 | 
	
	
		
			
				|  | @@ -121,9 +142,10 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      private fun init() {
 | 
	
		
			
				|  |  |          // todo: 2023/8/4 注册订阅
 | 
	
		
			
				|  |  | -        videoChannelVM.videoChannelInfo.observe(viewLifecycleOwner){
 | 
	
		
			
				|  |  | +        videoChannelVM.videoChannelInfo.observe(viewLifecycleOwner) {
 | 
	
		
			
				|  |  |              it?.let {
 | 
	
		
			
				|  |  | -                val videoStreamInfo = "fps:[${it.fps}] width:[${videoWidth}] height:[${videoHeight}]"
 | 
	
		
			
				|  |  | +                val videoStreamInfo =
 | 
	
		
			
				|  |  | +                    "fps:[${it.fps}] width:[${videoWidth}] height:[${videoHeight}]"
 | 
	
		
			
				|  |  |                  lblInfo?.text = videoStreamInfo
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
	
		
			
				|  | @@ -158,7 +180,7 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
 | 
	
		
			
				|  |  |       * 变更通道类型
 | 
	
		
			
				|  |  |       * @param channelType VideoChannelType
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  | -    private fun changeVideoDecoder(channelType:VideoChannelType){
 | 
	
		
			
				|  |  | +    private fun changeVideoDecoder(channelType: VideoChannelType) {
 | 
	
		
			
				|  |  |          videoDecoder?.videoChannelType = channelType
 | 
	
		
			
				|  |  |          surfaceView.invalidate()
 | 
	
		
			
				|  |  |      }
 | 
	
	
		
			
				|  | @@ -201,26 +223,27 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      private val streamDataListener = StreamDataListener {
 | 
	
		
			
				|  |  |          it?.let {
 | 
	
		
			
				|  |  | -            if(fps != it.fps){
 | 
	
		
			
				|  |  | +            if (fps != it.fps) {
 | 
	
		
			
				|  |  |                  fps = it.fps
 | 
	
		
			
				|  |  | -                mainHandler.post{
 | 
	
		
			
				|  |  | +                mainHandler.post {
 | 
	
		
			
				|  |  |                      videoChannelVM.videoChannelInfo.value?.fps = fps
 | 
	
		
			
				|  |  |                      videoChannelVM.refreshVideoChannelInfo()
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | -            if(videoWidth != it.width){
 | 
	
		
			
				|  |  | +            if (videoWidth != it.width) {
 | 
	
		
			
				|  |  |                  videoWidth = it.width
 | 
	
		
			
				|  |  |                  widthChanged = true
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | -            if(videoHeight != it.height){
 | 
	
		
			
				|  |  | +            if (videoHeight != it.height) {
 | 
	
		
			
				|  |  |                  videoHeight = it.height
 | 
	
		
			
				|  |  |                  heightChange = true
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | -            if(widthChanged || heightChange){
 | 
	
		
			
				|  |  | +            if (widthChanged || heightChange) {
 | 
	
		
			
				|  |  |                  widthChanged = false
 | 
	
		
			
				|  |  |                  heightChange = false
 | 
	
		
			
				|  |  | -                mainHandler.post{
 | 
	
		
			
				|  |  | -                    videoChannelVM.videoChannelInfo.value?.resolution = "${videoWidth}*${videoHeight}"
 | 
	
		
			
				|  |  | +                mainHandler.post {
 | 
	
		
			
				|  |  | +                    videoChannelVM.videoChannelInfo.value?.resolution =
 | 
	
		
			
				|  |  | +                        "${videoWidth}*${videoHeight}"
 | 
	
		
			
				|  |  |                      videoChannelVM.refreshVideoChannelInfo()
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
	
		
			
				|  | @@ -256,15 +279,239 @@ class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  | -     * 覆写视图释放
 | 
	
		
			
				|  |  | +     * 释放资源
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  | -    override fun onDestroyView() {
 | 
	
		
			
				|  |  | -        super.onDestroyView()
 | 
	
		
			
				|  |  | +    private fun releaseResource(){
 | 
	
		
			
				|  |  | +        // todo: 2023/8/7 解除监听
 | 
	
		
			
				|  |  |          curVideoChannel?.removeStreamDataListener(streamDataListener)
 | 
	
		
			
				|  |  | +        videoDecoder?.removeDecoderStateChangeListener(decoderStateChangeListener)
 | 
	
		
			
				|  |  | +        videoDecoder?.removeYuvDataListener(yuvDataListener)
 | 
	
		
			
				|  |  |          // todo: 2023/3/10 释放编码资源
 | 
	
		
			
				|  |  |          if (videoDecoder != null) {
 | 
	
		
			
				|  |  |              videoDecoder?.destroy()
 | 
	
		
			
				|  |  |              videoDecoder = null
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 覆写视图释放
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    override fun onDestroyView() {
 | 
	
		
			
				|  |  | +        super.onDestroyView()
 | 
	
		
			
				|  |  | +        // todo: 2023/8/7 释放资源
 | 
	
		
			
				|  |  | +        releaseResource()
 | 
	
		
			
				|  |  | +        // todo: 2023/8/7 解除事件监听
 | 
	
		
			
				|  |  | +        CrApplication.getEventBus().unregister(this)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 生命周期函数
 | 
	
		
			
				|  |  | +     * 转入前台可见
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    override fun onResume() {
 | 
	
		
			
				|  |  | +        super.onResume()
 | 
	
		
			
				|  |  | +        // todo: 2023/8/7 添加监听
 | 
	
		
			
				|  |  | +        curVideoChannel?.addStreamDataListener(streamDataListener)
 | 
	
		
			
				|  |  | +        // todo: 2023/8/7 启动图传
 | 
	
		
			
				|  |  | +        handlerYUV(true)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 转入后台不可见
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    override fun onStop() {
 | 
	
		
			
				|  |  | +        super.onStop()
 | 
	
		
			
				|  |  | +        // todo: 2023/8/7 释放资源
 | 
	
		
			
				|  |  | +        releaseResource()
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 模式切换 true表示切换到图传模式 false表示切换到YUV模式
 | 
	
		
			
				|  |  | +     * @param isSelected Boolean
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun handlerYUV(isSelected: Boolean) {
 | 
	
		
			
				|  |  | +        if (!isSelected) {
 | 
	
		
			
				|  |  | +            // todo: 2023/8/7 如果解码器存在 则停用并置null
 | 
	
		
			
				|  |  | +            videoDecoder?.let {
 | 
	
		
			
				|  |  | +                videoDecoder!!.onPause()
 | 
	
		
			
				|  |  | +                videoDecoder!!.destroy()
 | 
	
		
			
				|  |  | +                videoDecoder = null
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            // todo: 2023/8/7 重新定义解码器
 | 
	
		
			
				|  |  | +            videoDecoder =
 | 
	
		
			
				|  |  | +                VideoDecoder(this@FragmentFPV.context, curVideoChannel!!.videoChannelType)
 | 
	
		
			
				|  |  | +            videoDecoder?.addDecoderStateChangeListener(decoderStateChangeListener)
 | 
	
		
			
				|  |  | +            videoDecoder?.addYuvDataListener(yuvDataListener)
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            // todo: 2023/8/7 如果解码器存在 则停用并置null
 | 
	
		
			
				|  |  | +            videoDecoder?.let {
 | 
	
		
			
				|  |  | +                videoDecoder!!.onPause()
 | 
	
		
			
				|  |  | +                videoDecoder!!.destroy()
 | 
	
		
			
				|  |  | +                videoDecoder = null
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            // todo: 2023/8/7 重新定义解码器
 | 
	
		
			
				|  |  | +            videoDecoder = VideoDecoder(
 | 
	
		
			
				|  |  | +                this@FragmentFPV.context,
 | 
	
		
			
				|  |  | +                curVideoChannel!!.videoChannelType,
 | 
	
		
			
				|  |  | +                DecoderOutputMode.SURFACE_MODE,
 | 
	
		
			
				|  |  | +                surfaceView.holder
 | 
	
		
			
				|  |  | +            )
 | 
	
		
			
				|  |  | +            videoDecoder?.addDecoderStateChangeListener(decoderStateChangeListener)
 | 
	
		
			
				|  |  | +            videoDecoder?.removeYuvDataListener(yuvDataListener)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * yuv数据监听
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private val yuvDataListener =
 | 
	
		
			
				|  |  | +        YuvDataListener { mediaFormat, data, width, height ->
 | 
	
		
			
				|  |  | +            // todo: 2023/8/7 创建立即执行的协程
 | 
	
		
			
				|  |  | +            data?.let {
 | 
	
		
			
				|  |  | +                GlobalScope.launch {
 | 
	
		
			
				|  |  | +                    // todo: 2023/8/7 执行一次保存 就恢复图传
 | 
	
		
			
				|  |  | +                    handlerYUV(true)
 | 
	
		
			
				|  |  | +                    // todo: 2023/8/7 保存图片
 | 
	
		
			
				|  |  | +                    saveYuvData(mediaFormat,data,width,height)
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 保存当前帧Yuv数据
 | 
	
		
			
				|  |  | +     * @param mediaFormat MediaFormat? 媒体格式
 | 
	
		
			
				|  |  | +     * @param data ByteArray? 数据
 | 
	
		
			
				|  |  | +     * @param width Int 宽度
 | 
	
		
			
				|  |  | +     * @param height Int 高度
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun saveYuvData(mediaFormat: MediaFormat?, data: ByteArray?, width: Int, height: Int) {
 | 
	
		
			
				|  |  | +        data?.let {
 | 
	
		
			
				|  |  | +            mediaFormat?.let {
 | 
	
		
			
				|  |  | +                when(it.getInteger(MediaFormat.KEY_COLOR_FORMAT)){
 | 
	
		
			
				|  |  | +                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar->{
 | 
	
		
			
				|  |  | +                        newSaveYuvDataToJPEG420P(data,width,height)
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar->{
 | 
	
		
			
				|  |  | +                        newSaveYuvDataToJPEG(data,width,height)
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }?:sendFailureMessage("数据格式异常")
 | 
	
		
			
				|  |  | +        }?:sendFailureMessage("无图像数据")
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 发送错误消息事件
 | 
	
		
			
				|  |  | +     * @param error String 错误消息
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun sendFailureMessage(error:String){
 | 
	
		
			
				|  |  | +        CrApplication.getEventBus().post(EventCommon<String>(CrCommonAction.VIDEO_SAVE_YUV_FAILURE,error))
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 发送YUA数据保存成功消息
 | 
	
		
			
				|  |  | +     * @param fileName String 保存的文件名称
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun sendSaveYUVSuccessMessage(fileName:String){
 | 
	
		
			
				|  |  | +        CrApplication.getEventBus().post(EventCommon<String>(CrCommonAction.VIDEO_SAVE_YUV_SUCCESS,fileName))
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 保存yuv数据到JPEG420P
 | 
	
		
			
				|  |  | +     * @param yuvFrame ByteArray 帧数据
 | 
	
		
			
				|  |  | +     * @param width Int 宽度
 | 
	
		
			
				|  |  | +     * @param height Int 高度
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun newSaveYuvDataToJPEG420P(yuvFrame: ByteArray, width: Int, height: Int) {
 | 
	
		
			
				|  |  | +        if (yuvFrame.size < width * height) {
 | 
	
		
			
				|  |  | +            sendFailureMessage("帧数据异常")
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        val length = width * height
 | 
	
		
			
				|  |  | +        val u = ByteArray(width * height / 4)
 | 
	
		
			
				|  |  | +        val v = ByteArray(width * height / 4)
 | 
	
		
			
				|  |  | +        for (i in u.indices) {
 | 
	
		
			
				|  |  | +            u[i] = yuvFrame[length + i]
 | 
	
		
			
				|  |  | +            v[i] = yuvFrame[length + u.size + i]
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        for (i in u.indices) {
 | 
	
		
			
				|  |  | +            yuvFrame[length + 2 * i] = v[i]
 | 
	
		
			
				|  |  | +            yuvFrame[length + 2 * i + 1] = u[i]
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        screenShot(
 | 
	
		
			
				|  |  | +            yuvFrame,
 | 
	
		
			
				|  |  | +            width,
 | 
	
		
			
				|  |  | +            height
 | 
	
		
			
				|  |  | +        )
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 保存Yuv数据到JPEG
 | 
	
		
			
				|  |  | +     * @param yuvFrame ByteArray 帧数据
 | 
	
		
			
				|  |  | +     * @param width Int 宽度
 | 
	
		
			
				|  |  | +     * @param height Int 高度
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun newSaveYuvDataToJPEG(yuvFrame: ByteArray, width: Int, height: Int) {
 | 
	
		
			
				|  |  | +        if (yuvFrame.size < width * height) {
 | 
	
		
			
				|  |  | +            sendFailureMessage("帧数据异常")
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        val length = width * height
 | 
	
		
			
				|  |  | +        val u = ByteArray(width * height / 4)
 | 
	
		
			
				|  |  | +        val v = ByteArray(width * height / 4)
 | 
	
		
			
				|  |  | +        for (i in u.indices) {
 | 
	
		
			
				|  |  | +            v[i] = yuvFrame[length + 2 * i]
 | 
	
		
			
				|  |  | +            u[i] = yuvFrame[length + 2 * i + 1]
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        for (i in u.indices) {
 | 
	
		
			
				|  |  | +            yuvFrame[length + 2 * i] = u[i]
 | 
	
		
			
				|  |  | +            yuvFrame[length + 2 * i + 1] = v[i]
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        screenShot(
 | 
	
		
			
				|  |  | +            yuvFrame,
 | 
	
		
			
				|  |  | +            width,
 | 
	
		
			
				|  |  | +            height
 | 
	
		
			
				|  |  | +        )
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 保存图片
 | 
	
		
			
				|  |  | +     * @param buf ByteArray 数据
 | 
	
		
			
				|  |  | +     * @param width Int 宽度
 | 
	
		
			
				|  |  | +     * @param height Int 高度
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private fun screenShot(buf:ByteArray,width:Int,height:Int){
 | 
	
		
			
				|  |  | +        // todo: 2023/8/7 获取图片
 | 
	
		
			
				|  |  | +        val yuvImage = YuvImage(buf,ImageFormat.NV21,width,height,null)
 | 
	
		
			
				|  |  | +        // todo: 2023/8/7 保存文件
 | 
	
		
			
				|  |  | +        val outputFile:OutputStream
 | 
	
		
			
				|  |  | +        var fileName = "uav_${CrUnitManager.toSystemDate()}.jpg"
 | 
	
		
			
				|  |  | +        var path = "${CrUtil.IMAGE_PATH}${fileName}"
 | 
	
		
			
				|  |  | +        outputFile = try{
 | 
	
		
			
				|  |  | +            FileOutputStream(File(path))
 | 
	
		
			
				|  |  | +        }catch (e:FileNotFoundException){
 | 
	
		
			
				|  |  | +            sendFailureMessage("异常:${e.message}")
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        // todo: 2023/8/7 压缩保存图片
 | 
	
		
			
				|  |  | +        yuvImage.compressToJpeg(Rect(0,0,width,height),100,outputFile)
 | 
	
		
			
				|  |  | +        try{
 | 
	
		
			
				|  |  | +            outputFile.close()
 | 
	
		
			
				|  |  | +            // todo: 2023/8/7 发送正确消息
 | 
	
		
			
				|  |  | +            sendSaveYUVSuccessMessage(fileName)
 | 
	
		
			
				|  |  | +        }catch (e:IOException){
 | 
	
		
			
				|  |  | +            sendFailureMessage("IO错误:${e.message}")
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // todo: 2023/8/7 订阅事件
 | 
	
		
			
				|  |  | +    @Subscribe
 | 
	
		
			
				|  |  | +    fun onChanged(event:EventCommon<String>){
 | 
	
		
			
				|  |  | +        event?.let {
 | 
	
		
			
				|  |  | +            when(it.action){
 | 
	
		
			
				|  |  | +                CrCommonAction.VIDEO_SAVE_YUV->{
 | 
	
		
			
				|  |  | +                    // todo: 2023/8/7 切换到YUV模式
 | 
	
		
			
				|  |  | +                    handlerYUV(false)
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 |