feat: sync group call livestream support

This commit is contained in:
A 2026-07-06 14:31:19 +08:00
parent 56d995474c
commit f1a27996d3
37 changed files with 2219 additions and 83 deletions

View file

@ -0,0 +1,70 @@
package livestream
import "encoding/binary"
// tgcalls broadcast part 打包(消费方 tgcalls VideoStreamingPart.cpp
// consumeVideoStreamInfo)。unified(RTMP)模式的 part 结构:
//
// int32(LE) 签名 0xa12e810d
// TL 风格短字符串:容器名(本实现恒 "mp4")
// int32 activeMask(单一 unified 轨恒 1)
// int32 eventCount(消费方只读第一个 event,恒写 1)
// event: int32 offset(=0,相对头部之后的数据) + 字符串 endpointId("unified")
// + int32 rotation(0) + int32 extra(0)
// 随后紧跟容器数据(音视频同容器,客户端分别按 Video/Audio content type 解)。
const partSignature uint32 = 0xa12e810d
// partContainer 必须落在 TDesktop 裁剪版 ffmpeg 的 demuxer 白名单内
// (Telegram/build/prepare/prepare.py --enable-demuxer=...,含 mov/mp4、无 mpegts)。
// "mp4" 由 mov demuxer 别名匹配;tgcalls AVIO 支持 seek,完整 mp4(moov 在尾)可解。
const (
partContainer = "mp4"
partEndpointID = "unified"
)
// appendTLString 按 tgcalls readSerializedString 的逆操作写入字符串:
// 长度 <254 用 1 字节长度 + 数据,整体(含长度字节)补齐到 4 字节;
// 否则 0xFE + 3 字节小端长度 + 数据,数据补齐到 4 字节。
func appendTLString(dst []byte, s string) []byte {
n := len(s)
if n < 254 {
dst = append(dst, byte(n))
dst = append(dst, s...)
for (n+1)%4 != 0 {
dst = append(dst, 0)
n++
}
return dst
}
dst = append(dst, 0xFE, byte(n), byte(n>>8), byte(n>>16))
dst = append(dst, s...)
for n%4 != 0 {
dst = append(dst, 0)
n++
}
return dst
}
func appendUint32(dst []byte, v uint32) []byte {
var buf [4]byte
binary.LittleEndian.PutUint32(buf[:], v)
return append(dst, buf[:]...)
}
func appendInt32(dst []byte, v int32) []byte {
return appendUint32(dst, uint32(v))
}
// packUnifiedPart 把一段自包含 MPEG-TS 数据包成 tgcalls broadcast part。
func packUnifiedPart(tsData []byte) []byte {
out := make([]byte, 0, len(tsData)+48)
out = appendUint32(out, partSignature)
out = appendTLString(out, partContainer)
out = appendInt32(out, 1) // activeMask
out = appendInt32(out, 1) // eventCount
out = appendInt32(out, 0) // event.offset
out = appendTLString(out, partEndpointID)
out = appendInt32(out, 0) // event.rotation
out = appendInt32(out, 0) // event.extra
return append(out, tsData...)
}