70 lines
2.3 KiB
Go
70 lines
2.3 KiB
Go
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...)
|
||
}
|