app更新

This commit is contained in:
flowerstonezl
2026-01-20 17:24:15 +08:00
parent 182520f858
commit adc8f0569a
829 changed files with 421 additions and 78849 deletions

View File

@@ -213,28 +213,30 @@ data class FrameBuffer(
* 检查是否所有分片都已接收
*/
fun isComplete(): Boolean {
if (totalChunks <= 0) return false
for (i in 0 until totalChunks) {
if (chunks[i.toInt()] == null) {
return false
}
}
return true
// 使用计数器而不是遍历O(1) 复杂度
return chunks.size == totalChunks.toInt()
}
/**
* 将所有分片合并为完整的 JPEG 数据
*/
fun merge(): ByteArray {
val result = mutableListOf<Byte>()
var totalLen = 0
for (i in 0 until totalChunks) {
chunks[i.toInt()]?.let {
for (byte in it) {
result.add(byte)
}
val chunkLen = chunks[i.toInt()]?.size ?: 0
totalLen += chunkLen
}
val result = ByteArray(totalLen)
var offset = 0
for (i in 0 until totalChunks) {
val chunk = chunks[i.toInt()]
if (chunk != null) {
System.arraycopy(chunk, 0, result, offset, chunk.size)
offset += chunk.size
}
}
return result.toByteArray()
return result
}
/**

View File

@@ -45,6 +45,9 @@ class UdpReceiver(
private var receiverScope: CoroutineScope? = null
private var isRunning = false
// UDP 接收缓冲区大小 (2MB) - 增加缓冲区可以有效防止 UDP 丢包
private val SO_RCVBUF_SIZE = 2 * 1024 * 1024
// ========================================================================
// 帧缓冲区管理
// ========================================================================
@@ -79,6 +82,12 @@ class UdpReceiver(
try {
socket = DatagramSocket(listenPort)
socket!!.soTimeout = 5000
// 关键优化:增加 Socket 接收缓冲区
if (socket!!.receiveBufferSize < SO_RCVBUF_SIZE) {
socket!!.receiveBufferSize = SO_RCVBUF_SIZE
Log.i(TAG, "UDP 接收缓冲区大小已设置为: ${socket!!.receiveBufferSize}")
}
Log.i(TAG, "✓ UDP 接收器启动,监听端口 $listenPort")
@@ -106,19 +115,22 @@ class UdpReceiver(
*/
private suspend fun startReceiving() {
val buffer = ByteArray(MAX_UDP_PACKET_SIZE)
val packet = DatagramPacket(buffer, buffer.size)
while (isRunning) {
try {
val packet = DatagramPacket(buffer, buffer.size)
// 重置 packet 长度,以便接收下一个包
packet.length = buffer.size
socket?.receive(packet)
val data = packet.data.slice(0 until packet.length).toByteArray()
processPacket(data)
// 直接处理,避免创建新的 ByteArray 副本
processPacket(buffer, packet.length)
// 定期清理和报告统计
val currentTime = System.currentTimeMillis()
if (currentTime - lastStatsTime >= 1000) {
reportStats()
cleanupOldFrames() // 移到定时任务中执行,避免每包检查
lastStatsTime = currentTime
}
} catch (e: Exception) {
@@ -132,30 +144,36 @@ class UdpReceiver(
/**
* 处理接收到的 UDP 数据包
*/
private fun processPacket(data: ByteArray) {
if (data.size < PACKET_HEADER_SIZE) {
private fun processPacket(buffer: ByteArray, length: Int) {
if (length < PACKET_HEADER_SIZE) {
return
}
// 解析包头
val header = SkyLinkPacketHeader.fromByteArray(data) ?: return
// 注意:这里传入的是复用的 bufferfromByteArray 只读取前 50 字节
val header = SkyLinkPacketHeader.fromByteArray(buffer) ?: return
// 验证
if (!header.isValid()) {
return
}
// 计算载荷大小
val payloadSize = length - PACKET_HEADER_SIZE
// 简单校验:实际载荷大小应与包头声明一致(或至少不小于)
if (payloadSize != header.dataLen.toInt()) {
return
}
// 提取载荷
val payload = if (data.size > PACKET_HEADER_SIZE) {
data.slice(PACKET_HEADER_SIZE until data.size).toByteArray()
} else {
ByteArray(0)
}
// 必须拷贝,因为 buffer 会在下一次接收时被覆盖
val payload = ByteArray(payloadSize)
System.arraycopy(buffer, PACKET_HEADER_SIZE, payload, 0, payloadSize)
// 更新统计
totalPackets++
totalBytes += data.size
bytesThisSecond += data.size
totalBytes += length
bytesThisSecond += length
packetsThisSecond++
// 处理分片
@@ -179,8 +197,14 @@ class UdpReceiver(
handleCompleteFrame(buffer)
frameBuffers.remove(header.frameId.toInt())
}
}
}
// 清理超时的帧
/**
* 清理超时的帧
*/
private fun cleanupOldFrames() {
synchronized(frameBuffers) {
frameBuffers.entries.removeIf { (_, fb) ->
if (fb.isTimeout()) {
droppedFrames++
@@ -231,7 +255,7 @@ class UdpReceiver(
0.0
}
Log.d(TAG, "📊 FPS: $fps, KB/s: $kbps, 总帧: $totalFrames, 丢弃: $droppedFrames")
Log.d(TAG, "FPS: $fps, KB/s: $kbps, 总帧: $totalFrames, 丢弃: $droppedFrames")
MainScope().launch {
listener.onStatsUpdated(fps, kbps, packetLoss)

View File

@@ -20,11 +20,15 @@ import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.skylink.app.R
import com.skylink.app.network.DecodedFrame
import com.skylink.app.network.UdpReceiver
import com.skylink.app.network.UdpReceiverListener
import com.skylink.app.utils.BitmapUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.*
@@ -123,13 +127,17 @@ class HomeFragment : Fragment(), UdpReceiverListener {
override fun onFrameDecoded(frame: DecodedFrame) {
currentFrame = frame
// 解码图像
val bitmap = BitmapUtils.decodeBitmap(frame.imageData)
if (bitmap != null) {
view?.let {
videoImageView?.setImageBitmap(bitmap)
updateGpsInfo(frame)
}
// 移到后台线程执行解码,避免阻塞 UI 线程,进而影响 UDP 接收协程(如果是单核或高负载)
lifecycleScope.launch(Dispatchers.Default) {
val bitmap = BitmapUtils.decodeBitmap(frame.imageData)
if (bitmap != null) {
withContext(Dispatchers.Main) {
view?.let {
videoImageView?.setImageBitmap(bitmap)
updateGpsInfo(frame)
}
}
}
}
}

View File

@@ -44,7 +44,7 @@ object BitmapUtils {
// 实际解码
val bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.size, options)
Log.i(TAG, "✓ Bitmap 解码成功: ${bitmap?.width}x${bitmap?.height}")
// Log.i(TAG, "✓ Bitmap 解码成功: ${bitmap?.width}x${bitmap?.height}") // 移除高频日志
bitmap
} catch (e: Exception) {
Log.e(TAG, "Bitmap 解码失败", e)