FragmentFPV.kt 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. package com.cr.pages
  2. import android.graphics.ImageFormat
  3. import android.graphics.Rect
  4. import android.graphics.YuvImage
  5. import android.media.MediaCodecInfo
  6. import android.media.MediaFormat
  7. import android.os.Bundle
  8. import android.view.LayoutInflater
  9. import android.view.SurfaceHolder
  10. import android.view.SurfaceView
  11. import android.view.View
  12. import android.view.ViewGroup
  13. import android.view.ViewTreeObserver.OnGlobalLayoutListener
  14. import android.widget.Button
  15. import android.widget.TextView
  16. import androidx.constraintlayout.widget.ConstraintLayout
  17. import androidx.fragment.app.activityViewModels
  18. import com.cr.common.CrUnitManager
  19. import com.cr.cruav.CrApplication
  20. import com.cr.cruav.R
  21. import com.cr.data.CrUtil
  22. import com.cr.event.CrCommonAction
  23. import com.cr.event.EventCommon
  24. import com.cr.models.CompletionModel
  25. import com.cr.models.ICompletion
  26. import com.cr.viewmodel.CrFlightControlVM
  27. import com.cr.viewmodel.CrLiveStreamVM
  28. import com.cr.viewmodel.CrVideoChannelListener
  29. import com.cr.viewmodel.CrVideoChannelVM
  30. import com.squareup.otto.Subscribe
  31. import dji.v5.common.video.channel.VideoChannelType
  32. import dji.v5.common.video.decoder.DecoderOutputMode
  33. import dji.v5.common.video.decoder.DecoderState
  34. import dji.v5.common.video.decoder.VideoDecoder
  35. import dji.v5.common.video.interfaces.*
  36. import kotlinx.coroutines.GlobalScope
  37. import kotlinx.coroutines.launch
  38. import java.io.File
  39. import java.io.FileNotFoundException
  40. import java.io.FileOutputStream
  41. import java.io.IOException
  42. import java.io.OutputStream
  43. /**
  44. * 操作系统:MAC系统
  45. * 创建者:王成
  46. * 创建日期:2023/3/10 08:50
  47. * 描述:图传窗口
  48. */
  49. class FragmentFPV : CrAnimationFragment(), SurfaceHolder.Callback, OnGlobalLayoutListener {
  50. /**
  51. * FPV窗口监听
  52. */
  53. interface FPVListener {
  54. // todo: 2023/3/13 切换窗口
  55. fun triggerWindow()
  56. }
  57. // define: 2023/3/10 延迟初始化一个通道模型
  58. private val videoChannelVM: CrVideoChannelVM by activityViewModels()
  59. // define: 2023/3/10 将飞行器视图绑定到模型
  60. private val flightControlVm: CrFlightControlVM by activityViewModels()
  61. // define: 2023/9/12 绑定直播模型
  62. private val liveStreamVm: CrLiveStreamVM by activityViewModels()
  63. // define: 2023/3/10 延迟初始化一个视频画布
  64. private lateinit var surfaceView: SurfaceView
  65. private var btnTrigger: Button? = null
  66. private var lblInfo: TextView? = null // define: 2023/8/4 显示信息
  67. private var panelFpv: ConstraintLayout? = null // define: 2023/9/14 容器
  68. // define: 2023/3/11 视频通道
  69. private var curVideoChannel: IVideoChannel? = null
  70. // define: 2023/3/10 定义视频解码器
  71. private var videoDecoder: IVideoDecoder? = null
  72. // todo: 2023/8/4 视频相关参数
  73. private var videoWidth: Int = -1
  74. private var videoHeight: Int = -1
  75. private var widthChanged: Boolean = false
  76. private var heightChange: Boolean = false
  77. private var fps: Int = -1
  78. // todo: 2023/9/14 画布相关参数
  79. private var surfaceViewWidth: Int = 0
  80. private var surfaceViewHeight: Int = 0
  81. // todo: 2023/3/13 监听
  82. lateinit var listener: FPVListener
  83. /**
  84. * 创建视图
  85. * @param inflater LayoutInflater
  86. * @param container ViewGroup?
  87. * @param savedInstanceState Bundle?
  88. * @return View?
  89. */
  90. override fun onCreateView(
  91. inflater: LayoutInflater,
  92. container: ViewGroup?,
  93. savedInstanceState: Bundle?
  94. ): View? {
  95. var view = inflater.inflate(R.layout.frag_fpv, container, false)
  96. // todo: 2023/8/4 关闭 View Layer。 View Layer 可以加速无 invalidate() 时的刷新效率,但对于需要调用 invalidate() 的刷新无法加速
  97. view.setLayerType(View.LAYER_TYPE_NONE, null)
  98. // todo: 2023/3/13 给View挂接事件
  99. view.setOnClickListener(clickListener)
  100. // todo: 2023/3/10 挂接图传视图控件
  101. surfaceView = view.findViewById(R.id.surface_fpv)
  102. btnTrigger = view.findViewById(R.id.fpv_trigger)
  103. btnTrigger?.setOnClickListener(clickListener)
  104. // todo: 2023/9/14 挂接容器
  105. panelFpv = view.findViewById(R.id.fpv_panel)
  106. // todo: 2023/8/4 挂接信息显示组件
  107. lblInfo = view.findViewById(R.id.fpv_info)
  108. // todo: 2023/3/10 设置视图控件监听
  109. surfaceView.holder.addCallback(this)
  110. // todo: 2023/8/7 注册事件监听
  111. CrApplication.getEventBus().register(this)
  112. return view
  113. }
  114. /**
  115. * 添加视图尺寸变化监听
  116. */
  117. private fun addSizeChangeForSurfaceView() {
  118. surfaceView.viewTreeObserver!!.addOnGlobalLayoutListener(this)
  119. }
  120. /**
  121. * 画布尺寸变化监听
  122. */
  123. override fun onGlobalLayout() {
  124. if (surfaceViewWidth != surfaceView.width && surfaceViewHeight != surfaceView.height) {
  125. surfaceViewWidth = surfaceView.width
  126. surfaceViewHeight = surfaceView.height
  127. CrUtil.print("宽度:${surfaceViewWidth} 高度:${surfaceViewHeight}")
  128. // todo: 2023/9/14 移除监听 要不会多次调用
  129. surfaceView.viewTreeObserver.removeOnGlobalLayoutListener(this)
  130. }
  131. }
  132. /**
  133. * 视图创建完成后调用
  134. * @param view View
  135. * @param savedInstanceState Bundle?
  136. */
  137. override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  138. super.onViewCreated(view, savedInstanceState)
  139. // todo: 2023/3/11 初始化
  140. init()
  141. }
  142. /**
  143. * 点击事件
  144. */
  145. private var clickListener = View.OnClickListener {
  146. when (it.id) {
  147. R.id.fpv_trigger -> {
  148. // todo: 2023/3/13 切换视频通道
  149. videoChannelVM.triggerStreamSource()
  150. }
  151. R.id.fpv_panel -> {
  152. // todo: 2023/3/13 切换窗口
  153. listener.triggerWindow()
  154. // // todo: 2023/9/14 添加画布尺寸变化监听
  155. // // todo: 2023/9/14 主要是为了在画布变化时可以调整解码器解码的宽度和高度
  156. // addSizeChangeForSurfaceView()
  157. // todo: 2023/9/15 判断当前是否在直播 如果是 则先停止 再开启
  158. if (liveStreamVm?.streamStatusInfo.value!!.isStreaming) {
  159. liveStreamVm.stopStream(object : ICompletion<String> {
  160. // todo: 2023/9/15 停止完成后回调
  161. override fun onCompletion(completion: CompletionModel<String>) {
  162. if (completion.isSuccess == true)
  163. liveStreamVm?.startStream(null)
  164. }
  165. })
  166. }
  167. }
  168. }
  169. }
  170. /**
  171. * 初始化
  172. */
  173. private fun init() {
  174. // todo: 2023/8/4 注册订阅
  175. videoChannelVM.videoChannelInfo.observe(viewLifecycleOwner) {
  176. it?.let {
  177. val videoStreamInfo =
  178. "帧率:[${it.fps}] 宽:[${videoWidth}] 高:[${videoHeight}]"
  179. lblInfo?.text = videoStreamInfo
  180. }
  181. }
  182. // todo: 2023/3/11 绑定视频通道变化监听
  183. videoChannelVM.listener = object : CrVideoChannelListener {
  184. // todo: 2023/3/13 变更通道
  185. override fun initVideoChannel(videoChannel: IVideoChannel) {
  186. curVideoChannel = videoChannel
  187. // todo: 2023/8/4 添加组针变化监听
  188. curVideoChannel?.addStreamDataListener(streamDataListener)
  189. setChannelToSurface()
  190. }
  191. // todo: 2023/3/13 更新通道类型
  192. override fun updateVideoChannelType(channelType: VideoChannelType) {
  193. changeVideoDecoder(channelType)
  194. }
  195. }
  196. // todo: 2023/3/11 订阅飞行器
  197. flightControlVm.flightControlInfo.observe(requireActivity()) {
  198. it?.let {
  199. if (it.isConnection) {
  200. videoChannelVM.beginVideoChannel()
  201. } else {
  202. videoChannelVM.endVideoChannel()
  203. }
  204. }
  205. }
  206. }
  207. /**
  208. * 变更通道类型
  209. * @param channelType VideoChannelType
  210. */
  211. private fun changeVideoDecoder(channelType: VideoChannelType) {
  212. videoDecoder?.videoChannelType = channelType
  213. surfaceView.invalidate()
  214. }
  215. /**
  216. * 设置视频通道到画布
  217. */
  218. private fun setChannelToSurface() {
  219. // todo: 2023/9/15 释放解码器资源
  220. videoDecoder?.let {
  221. videoDecoder?.removeDecoderStateChangeListener(decoderStateChangeListener)
  222. videoDecoder?.onPause()
  223. videoDecoder?.destroy()
  224. videoDecoder = null
  225. }
  226. // todo: 2023/9/15 重新判断初始化
  227. if (videoDecoder == null) {
  228. curVideoChannel?.let {
  229. videoDecoder = VideoDecoder(
  230. this@FragmentFPV.context,
  231. it.videoChannelType,
  232. DecoderOutputMode.SURFACE_MODE,
  233. surfaceView.holder,
  234. surfaceView.width,
  235. surfaceView.height,
  236. true
  237. )
  238. // todo: 2023/9/12 设置直播视频通道
  239. // liveStreamVm.setVideoChannel(it.videoChannelType)
  240. // todo: 2023/3/10 添加解码状态监听
  241. videoDecoder?.addDecoderStateChangeListener(decoderStateChangeListener)
  242. }
  243. } else if (videoDecoder?.decoderStatus == DecoderState.PAUSED) {
  244. videoDecoder?.onResume()
  245. }
  246. }
  247. /**
  248. * 解码器状态监听
  249. */
  250. private val decoderStateChangeListener =
  251. DecoderStateChangeListener { oldState, newState ->
  252. mainHandler.post {
  253. // todo: 2023/3/10 更新编码状态
  254. videoChannelVM.videoChannelInfo.value?.decoderState = newState
  255. // todo: 2023/3/10 刷新数据
  256. videoChannelVM.refreshVideoChannelInfo()
  257. }
  258. }
  259. /**
  260. * 组针后数据的监听
  261. */
  262. private val streamDataListener = StreamDataListener {
  263. it?.let {
  264. if (fps != it.fps) {
  265. fps = it.fps
  266. mainHandler.post {
  267. videoChannelVM.videoChannelInfo.value?.fps = fps
  268. videoChannelVM.refreshVideoChannelInfo()
  269. }
  270. }
  271. if (videoWidth != it.width) {
  272. videoWidth = it.width
  273. widthChanged = true
  274. }
  275. if (videoHeight != it.height) {
  276. videoHeight = it.height
  277. heightChange = true
  278. }
  279. if (widthChanged || heightChange) {
  280. widthChanged = false
  281. heightChange = false
  282. mainHandler.post {
  283. videoChannelVM.videoChannelInfo.value?.resolution =
  284. "${videoWidth}*${videoHeight}"
  285. videoChannelVM.refreshVideoChannelInfo()
  286. }
  287. }
  288. }
  289. }
  290. /**
  291. * surfaceView 创建监听
  292. * @param p0 SurfaceHolder
  293. */
  294. override fun surfaceCreated(p0: SurfaceHolder) {
  295. setChannelToSurface()
  296. }
  297. /**
  298. * surfaceView变化监听
  299. * @param p0 SurfaceHolder
  300. * @param p1 Int
  301. * @param p2 Int
  302. * @param p3 Int
  303. */
  304. override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
  305. setChannelToSurface()
  306. }
  307. /**
  308. * surfaceView释放监听
  309. * @param p0 SurfaceHolder
  310. */
  311. override fun surfaceDestroyed(p0: SurfaceHolder) {
  312. // todo: 2023/3/10 停止解码
  313. videoDecoder?.onPause()
  314. }
  315. /**
  316. * 释放资源
  317. */
  318. private fun releaseResource() {
  319. // todo: 2023/8/7 解除监听
  320. curVideoChannel?.removeStreamDataListener(streamDataListener)
  321. videoDecoder?.removeDecoderStateChangeListener(decoderStateChangeListener)
  322. videoDecoder?.removeYuvDataListener(yuvDataListener)
  323. // todo: 2023/3/10 释放编码资源
  324. if (videoDecoder != null) {
  325. videoDecoder?.destroy()
  326. videoDecoder = null
  327. }
  328. }
  329. /**
  330. * 覆写视图释放
  331. */
  332. override fun onDestroyView() {
  333. super.onDestroyView()
  334. // todo: 2023/8/7 释放资源
  335. releaseResource()
  336. // todo: 2023/8/7 解除事件监听
  337. CrApplication.getEventBus().unregister(this)
  338. }
  339. /**
  340. * 生命周期函数
  341. * 转入前台可见
  342. */
  343. override fun onResume() {
  344. super.onResume()
  345. curVideoChannel?.let {
  346. // todo: 2023/8/7 添加监听
  347. it.addStreamDataListener(streamDataListener)
  348. // todo: 2023/8/14 启用图传
  349. handlerYUV(true)
  350. }
  351. }
  352. /**
  353. * 转入后台不可见
  354. */
  355. override fun onStop() {
  356. super.onStop()
  357. // todo: 2023/8/7 释放资源
  358. releaseResource()
  359. }
  360. /**
  361. * 模式切换 true表示切换到图传模式 false表示切换到YUV模式
  362. * @param isSelected Boolean
  363. */
  364. private fun handlerYUV(isSelected: Boolean) {
  365. if (!isSelected) {
  366. // todo: 2023/8/7 如果解码器存在 则停用并置null
  367. videoDecoder?.let {
  368. videoDecoder!!.onPause()
  369. videoDecoder!!.destroy()
  370. videoDecoder = null
  371. }
  372. // todo: 2023/8/7 重新定义解码器
  373. videoDecoder =
  374. VideoDecoder(this@FragmentFPV.context, curVideoChannel!!.videoChannelType)
  375. videoDecoder?.addDecoderStateChangeListener(decoderStateChangeListener)
  376. videoDecoder?.addYuvDataListener(yuvDataListener)
  377. } else {
  378. // todo: 2023/8/7 如果解码器存在 则停用并置null
  379. videoDecoder?.let {
  380. videoDecoder!!.onPause()
  381. videoDecoder!!.destroy()
  382. videoDecoder = null
  383. }
  384. // todo: 2023/8/7 重新定义解码器
  385. videoDecoder = VideoDecoder(
  386. this@FragmentFPV.context,
  387. curVideoChannel!!.videoChannelType,
  388. DecoderOutputMode.SURFACE_MODE,
  389. surfaceView.holder
  390. )
  391. videoDecoder?.addDecoderStateChangeListener(decoderStateChangeListener)
  392. videoDecoder?.removeYuvDataListener(yuvDataListener)
  393. }
  394. }
  395. /**
  396. * yuv数据监听
  397. */
  398. private val yuvDataListener =
  399. YuvDataListener { mediaFormat, data, width, height ->
  400. // todo: 2023/8/7 创建立即执行的协程
  401. data?.let {
  402. GlobalScope.launch {
  403. // todo: 2023/8/7 执行一次保存 就恢复图传
  404. handlerYUV(true)
  405. // todo: 2023/8/7 保存图片
  406. saveYuvData(mediaFormat, data, width, height)
  407. }
  408. }
  409. }
  410. /**
  411. * 保存当前帧Yuv数据
  412. * @param mediaFormat MediaFormat? 媒体格式
  413. * @param data ByteArray? 数据
  414. * @param width Int 宽度
  415. * @param height Int 高度
  416. */
  417. private fun saveYuvData(mediaFormat: MediaFormat?, data: ByteArray?, width: Int, height: Int) {
  418. data?.let {
  419. mediaFormat?.let {
  420. when (it.getInteger(MediaFormat.KEY_COLOR_FORMAT)) {
  421. MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar -> {
  422. newSaveYuvDataToJPEG420P(data, width, height)
  423. }
  424. MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar -> {
  425. newSaveYuvDataToJPEG(data, width, height)
  426. }
  427. }
  428. } ?: sendFailureMessage("数据格式异常")
  429. } ?: sendFailureMessage("无图像数据")
  430. }
  431. /**
  432. * 发送错误消息事件
  433. * @param error String 错误消息
  434. */
  435. private fun sendFailureMessage(error: String) {
  436. CrApplication.getEventBus()
  437. .post(EventCommon<String>(CrCommonAction.VIDEO_SAVE_YUV_FAILURE, error))
  438. }
  439. /**
  440. * 发送YUA数据保存成功消息
  441. * @param fileName String 保存的文件名称
  442. */
  443. private fun sendSaveYUVSuccessMessage(fileName: String) {
  444. CrApplication.getEventBus()
  445. .post(EventCommon<String>(CrCommonAction.VIDEO_SAVE_YUV_SUCCESS, fileName))
  446. }
  447. /**
  448. * 保存yuv数据到JPEG420P
  449. * @param yuvFrame ByteArray 帧数据
  450. * @param width Int 宽度
  451. * @param height Int 高度
  452. */
  453. private fun newSaveYuvDataToJPEG420P(yuvFrame: ByteArray, width: Int, height: Int) {
  454. if (yuvFrame.size < width * height) {
  455. sendFailureMessage("帧数据异常")
  456. return
  457. }
  458. val length = width * height
  459. val u = ByteArray(width * height / 4)
  460. val v = ByteArray(width * height / 4)
  461. for (i in u.indices) {
  462. u[i] = yuvFrame[length + i]
  463. v[i] = yuvFrame[length + u.size + i]
  464. }
  465. for (i in u.indices) {
  466. yuvFrame[length + 2 * i] = v[i]
  467. yuvFrame[length + 2 * i + 1] = u[i]
  468. }
  469. screenShot(
  470. yuvFrame,
  471. width,
  472. height
  473. )
  474. }
  475. /**
  476. * 保存Yuv数据到JPEG
  477. * @param yuvFrame ByteArray 帧数据
  478. * @param width Int 宽度
  479. * @param height Int 高度
  480. */
  481. private fun newSaveYuvDataToJPEG(yuvFrame: ByteArray, width: Int, height: Int) {
  482. if (yuvFrame.size < width * height) {
  483. sendFailureMessage("帧数据异常")
  484. return
  485. }
  486. val length = width * height
  487. val u = ByteArray(width * height / 4)
  488. val v = ByteArray(width * height / 4)
  489. for (i in u.indices) {
  490. v[i] = yuvFrame[length + 2 * i]
  491. u[i] = yuvFrame[length + 2 * i + 1]
  492. }
  493. for (i in u.indices) {
  494. yuvFrame[length + 2 * i] = u[i]
  495. yuvFrame[length + 2 * i + 1] = v[i]
  496. }
  497. screenShot(
  498. yuvFrame,
  499. width,
  500. height
  501. )
  502. }
  503. /**
  504. * 保存图片
  505. * @param buf ByteArray 数据
  506. * @param width Int 宽度
  507. * @param height Int 高度
  508. */
  509. private fun screenShot(buf: ByteArray, width: Int, height: Int) {
  510. // todo: 2023/8/7 获取图片
  511. val yuvImage = YuvImage(buf, ImageFormat.NV21, width, height, null)
  512. // todo: 2023/8/7 保存文件
  513. val outputFile: OutputStream
  514. var fileName = "uav_${CrUnitManager.toSystemDate()}.jpg"
  515. var path = "${CrUtil.IMAGE_PATH}${fileName}"
  516. outputFile = try {
  517. FileOutputStream(File(path))
  518. } catch (e: FileNotFoundException) {
  519. sendFailureMessage("异常:${e.message}")
  520. return
  521. }
  522. // todo: 2023/8/7 压缩保存图片
  523. yuvImage.compressToJpeg(Rect(0, 0, width, height), 100, outputFile)
  524. try {
  525. outputFile.close()
  526. // todo: 2023/8/7 发送正确消息
  527. sendSaveYUVSuccessMessage(fileName)
  528. } catch (e: IOException) {
  529. sendFailureMessage("IO错误:${e.message}")
  530. }
  531. }
  532. /**
  533. * 设置画布尺寸变化
  534. */
  535. fun setSurfaceViewSizeChange() {
  536. var width = surfaceView.width
  537. var height = surfaceView.height
  538. CrUtil.print("宽度:${width} 高度:${height}")
  539. }
  540. // todo: 2023/8/7 订阅事件
  541. @Subscribe
  542. fun onChanged(event: EventCommon<String>) {
  543. when (event.action) {
  544. CrCommonAction.VIDEO_SAVE_YUV -> {
  545. // todo: 2023/8/7 切换到YUV模式
  546. handlerYUV(false)
  547. }
  548. }
  549. }
  550. }