Streams H.264 to the Galahad II LCD via USB (gousb) with: - ffmpeg CLI transcoding (no fragile FFmpeg C-ABI bindings) - pure-Go Annex-B access-unit splitting - framerate pacing with graceful shutdown and USB reconnect - TOML config, cobra CLI, structured slog logging, unit tests
65 lines
1.7 KiB
Go
65 lines
1.7 KiB
Go
// Package protocol builds the USB packets that the Lian Li Galahad II LCD
|
|
// expects. It is a pure, dependency-free implementation of the framing used
|
|
// by the reference Rust/Python drivers.
|
|
//
|
|
// Each H.264 frame is split into one or more 512-byte USB packets. Every
|
|
// packet carries an 11-byte header:
|
|
//
|
|
// [0] report_id (0x02)
|
|
// [1] command (0x0D = send H.264)
|
|
// [2:6] total payload size (big-endian)
|
|
// [6:9] packet index, 3 bytes (big-endian, wraps)
|
|
// [9:11] chunk length in this packet (big-endian)
|
|
//
|
|
// followed by up to 501 bytes of frame data and zero padding.
|
|
package protocol
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
)
|
|
|
|
// Wire-level constants shared with the reference implementation.
|
|
const (
|
|
ReportIDVideo = 0x02
|
|
CmdSendH264 = 0x0D
|
|
HeaderSize = 11
|
|
PacketSize = 512
|
|
MaxPayloadSize = 501
|
|
)
|
|
|
|
// EncodePackets splits frame data into USB packets. It returns an error only
|
|
// when frame is empty, which would otherwise produce a malformed stream.
|
|
func EncodePackets(frame []byte) ([][]byte, error) {
|
|
if len(frame) == 0 {
|
|
return nil, fmt.Errorf("cannot encode empty frame")
|
|
}
|
|
|
|
var packets [][]byte
|
|
for offset, idx := 0, uint32(0); offset < len(frame); offset += MaxPayloadSize {
|
|
end := min(offset+MaxPayloadSize, len(frame))
|
|
chunk := frame[offset:end]
|
|
|
|
pkt := make([]byte, PacketSize)
|
|
pkt[0] = ReportIDVideo
|
|
pkt[1] = CmdSendH264
|
|
binary.BigEndian.PutUint32(pkt[2:6], uint32(len(frame)))
|
|
pkt[6] = byte(idx >> 16)
|
|
pkt[7] = byte(idx >> 8)
|
|
pkt[8] = byte(idx)
|
|
binary.BigEndian.PutUint16(pkt[9:11], uint16(len(chunk)))
|
|
copy(pkt[HeaderSize:HeaderSize+len(chunk)], chunk)
|
|
|
|
packets = append(packets, pkt)
|
|
idx++
|
|
}
|
|
return packets, nil
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|