Files
flyLink/protocol/Protocol.kt
flowerstonezl 056289625c init
2026-01-19 17:08:49 +08:00

295 lines
7.7 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Protocol.kt
* SkyLink 多平台实时视频传输 - Kotlin 协议定义
*
* 这是对应 protocol.h 的 Kotlin 实现版本,用于 Android 端解析 UDP 数据包。
* 使用 ByteBuffer 进行二进制数据的解析和序列化。
*
* @author SkyLink Team
* @date 2026-01-19
*/
package com.skylink.protocol
import java.nio.ByteBuffer
import java.nio.ByteOrder
/**
* SkyLink 协议版本号
*/
const val SKYLINK_VERSION = "1.0"
/**
* 数据包魔数
*/
const val SKYLINK_MAGIC: Short = 0xAA55.toShort()
/**
* UDP 最大包大小 (MTU - IP - UDP)
*/
const val MAX_UDP_PACKET_SIZE = 1472
/**
* 最大载荷大小
*/
const val MAX_PAYLOAD_SIZE = MAX_UDP_PACKET_SIZE - PacketHeaderSize
/**
* 包头大小 (字节)
*/
const val PacketHeaderSize = 50
/**
* UDP 接收缓冲区大小
*/
const val RX_BUFFER_SIZE = 65536
/**
* 最大分片数
*/
const val MAX_CHUNKS = 1000
/**
* 帧超时时间 (毫秒)
*/
const val FRAME_TIMEOUT_MS = 5000L
/**
* JPEG 图像格式 ID
*/
const val IMAGE_FORMAT_JPEG: Byte = 0x01
/**
* JPEG 最大质量
*/
const val JPEG_MAX_QUALITY = 100
/**
* JPEG 最小质量
*/
const val JPEG_MIN_QUALITY = 10
// ============================================================================
// 数据类定义
// ============================================================================
/**
* SkyLink 数据包头结构
*
* 完整映射 C++ protocol.h 的 PacketHeader
* - Magic (2 bytes)
* - FrameID (4 bytes)
* - TotalChunks (2 bytes)
* - ChunkIndex (2 bytes)
* - DataLen (2 bytes)
* - Timestamp (8 bytes)
* - Latitude (8 bytes)
* - Longitude (8 bytes)
* - Altitude (8 bytes)
* - CRC16 (2 bytes)
* - Reserve (4 bytes)
* 总计: 50 字节
*/
data class SkyLinkPacketHeader(
val magic: Short, // 0xAA55
val frameId: Int, // 帧ID
val totalChunks: Short, // 总分片数
val chunkIndex: Short, // 当前分片索引
val dataLen: Short, // 载荷长度
val timestamp: Double, // 时间戳
val lat: Double, // 纬度
val lon: Double, // 经度
val alt: Double, // 海拔
val crc16: Short, // 校验码
val reserve: Int // 预留字段
) {
companion object {
/**
* 从 ByteArray 中解析包头
* @param data 字节数组
* @return 解析成功返回 SkyLinkPacketHeader否则返回 null
*/
fun fromByteArray(data: ByteArray): SkyLinkPacketHeader? {
if (data.size < PacketHeaderSize) {
return null
}
val buffer = ByteBuffer.wrap(data, 0, PacketHeaderSize)
buffer.order(ByteOrder.LITTLE_ENDIAN) // ARM 平台使用小端序
return try {
val magic = buffer.short
val frameId = buffer.int
val totalChunks = buffer.short
val chunkIndex = buffer.short
val dataLen = buffer.short
val timestamp = buffer.double
val lat = buffer.double
val lon = buffer.double
val alt = buffer.double
val crc16 = buffer.short
val reserve = buffer.int
SkyLinkPacketHeader(
magic = magic,
frameId = frameId,
totalChunks = totalChunks,
chunkIndex = chunkIndex,
dataLen = dataLen,
timestamp = timestamp,
lat = lat,
lon = lon,
alt = alt,
crc16 = crc16,
reserve = reserve
)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
/**
* 将包头序列化为 ByteArray
*/
fun toByteArray(header: SkyLinkPacketHeader): ByteArray {
val buffer = ByteBuffer.allocate(PacketHeaderSize)
buffer.order(ByteOrder.LITTLE_ENDIAN)
buffer.putShort(header.magic)
buffer.putInt(header.frameId)
buffer.putShort(header.totalChunks)
buffer.putShort(header.chunkIndex)
buffer.putShort(header.dataLen)
buffer.putDouble(header.timestamp)
buffer.putDouble(header.lat)
buffer.putDouble(header.lon)
buffer.putDouble(header.alt)
buffer.putShort(header.crc16)
buffer.putInt(header.reserve)
return buffer.array()
}
}
/**
* 验证包头魔数是否有效
*/
fun isValid(): Boolean {
return magic == SKYLINK_MAGIC
}
/**
* 获取此包头对应的载荷部分
*/
fun getPayloadSize(): Int {
return dataLen.toInt()
}
}
/**
* 完整的 SkyLink 数据包
*/
data class SkyLinkPacket(
val header: SkyLinkPacketHeader,
val payload: ByteArray // 实际的图像数据
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SkyLinkPacket
if (header != other.header) return false
if (!payload.contentEquals(other.payload)) return false
return true
}
override fun hashCode(): Int {
var result = header.hashCode()
result = 31 * result + payload.contentHashCode()
return result
}
/**
* 获取总的包大小
*/
fun getTotalSize(): Int {
return PacketHeaderSize + payload.size
}
}
/**
* 图像帧的组装结果
*/
data class DecodedFrame(
val frameId: Int,
val imageData: ByteArray, // JPEG 图像数据
val timestamp: Double,
val lat: Double,
val lon: Double,
val alt: Double,
val receivedTime: Long = System.currentTimeMillis() // 接收时刻
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as DecodedFrame
if (frameId != other.frameId) return false
if (!imageData.contentEquals(other.imageData)) return false
if (timestamp != other.timestamp) return false
if (lat != other.lat) return false
if (lon != other.lon) return false
if (alt != other.alt) return false
if (receivedTime != other.receivedTime) return false
return true
}
override fun hashCode(): Int {
var result = frameId
result = 31 * result + imageData.contentHashCode()
result = 31 * result + timestamp.hashCode()
result = 31 * result + lat.hashCode()
result = 31 * result + lon.hashCode()
result = 31 * result + alt.hashCode()
result = 31 * result + receivedTime.hashCode()
return result
}
}
// ============================================================================
// 辅助函数
// ============================================================================
/**
* 验证 CRC16 校验 (CCITT 多项式)
*/
fun calculateCrc16(data: ByteArray): Short {
var crc: Int = 0xFFFF
for (byte in data) {
crc = crc xor ((byte.toInt() and 0xFF) shl 8)
for (j in 0 until 8) {
crc = if (crc and 0x8000 != 0) {
((crc shl 1) xor 0x1021) and 0xFFFF
} else {
(crc shl 1) and 0xFFFF
}
}
}
return crc.toShort()
}
/**
* 验证包头合法性
*/
fun isValidSkyLinkPacket(header: SkyLinkPacketHeader): Boolean {
return header.isValid() &&
header.chunkIndex < header.totalChunks &&
header.dataLen > 0 &&
header.dataLen <= MAX_PAYLOAD_SIZE
}