
第一流媒体的幕后英雄在直播服务器的世界里每一帧画面、每一段声音都像是一封封加急信需要在毫秒之间送达千万观众的手中。这些“信”的格式叫做RTMPReal-Time Messaging Protocol。而今天故事的主角是一个看似不起眼、却肩负重任的 Go 结构体——RTMPData。第二信封上的标签—常量定义const ( PacketTypeSequenceStart byte iota PacketTypeCodedFrames PacketTypeSequenceEnd PacketTypeCodedFramesX PacketTypeMetadata PacketTypeMPEG2TSSequenceStart )这六个常量就像是邮局贴在信封上的不同标签常量名值iota含义PacketTypeSequenceStart0序列开始告诉接收端“好戏要开场了”PacketTypeCodedFrames1编码后的音视频帧真正的“内容”PacketTypeSequenceEnd2序列结束演出落幕PacketTypeCodedFramesX3扩展编码帧特殊场次PacketTypeMetadata4元数据比如分辨率、帧率等信息PacketTypeMPEG2TSSequenceStart5MPEG-2 TS 流的开始用iota自动编号简洁又优雅就像流水线上的工位编号一个都不会乱。第三主角登场——RTMPData 结构体type RTMPData struct { pkg.Sample }RTMPData 并没有自己定义字段而是匿名嵌入了 pkg.Sample。这意味着它“继承”了 Sample 的所有能力Timestamp时间戳记录这一帧发生的时刻Size数据大小告诉你信有多重NewReader()获取一个读取器用来读取数据内容这种组合方式在 Go 里叫结构体嵌入是“has-a”关系的最佳实践——RTMPData不是一个Sample但它拥有一个Sample的全部能力。第四变身术——MarshalJSON对象序列化为JSONfunc (avcc *RTMPData) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf( {Timestamp:%d,Size:%d,Data:%s}, avcc.Timestamp, avcc.Size, avcc.String(), )), nil }当系统需要把RTMPData序列化成 JSON 时这个自定义方法就会生效。它生成的结构像这样{ Timestamp: 1717000000, Size: 1024, Data: 1717000000 17 00 00 00 00 00 00 00 00 00 }注意Data字段的值来自String()方法它把时间戳和前 10 个字节的十六进制数据拼在一起方便调试时一眼看出“这帧数据长什么样”。第五自述——String 方法func (avcc *RTMPData) String() string { reader : avcc.NewReader() var bytes10 [10]byte reader.Read(bytes10[:]) return fmt.Sprintf(%d % 02X, avcc.Timestamp, bytes10[:]) }String() 是 RTMPData 的“自我介绍”。它通过NewReader()拿到数据读取器读取前10 个字节到bytes10数组返回格式时间戳 空格 每个字节的两位大写十六进制例如输出1717000000 17 00 00 00 00 00 00 00 00 00这在日志排查时非常有用——你一眼就能看出这是不是关键帧通常第一个字节是0x17表示 H.264 关键帧。第六测试时间——让代码自己说话下面的测试代码模拟了pkg.Sample的行为并验证了RTMPData的输出package main import ( bytes encoding/json fmt testing ) type Sample struct { Timestamp uint32 Size uint32 data []byte } func (s *Sample) NewReader() *bytes.Reader { return bytes.NewReader(s.data) } type RTMPData struct { Sample } func (avcc *RTMPData) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf( {Timestamp:%d,Size:%d,Data:%s}, avcc.Timestamp, avcc.Size, avcc.String())), nil } func (avcc *RTMPData) String() string { reader : avcc.NewReader() var bytes10 [10]byte reader.Read(bytes10[:]) return fmt.Sprintf(%d % 02X, avcc.Timestamp, bytes10[:]) } func newTestRTMPData() *RTMPData { sampleData : []byte{ 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, } return RTMPData{Sample: Sample{ Timestamp: 1717000000, Size: uint32(len(sampleData)), data: sampleData, }} } func TestRTMPDataString(t *testing.T) { got : newTestRTMPData().String() want : 1717000000 17 00 00 00 00 00 00 00 00 00 if got ! want { t.Errorf(String() %q, want %q, got, want) return } t.Log(got) t.Log(TestRTMPDataString 成功) } func TestRTMPDataMarshalJSON(t *testing.T) { rtmpData : newTestRTMPData() encoded, err : json.Marshal(rtmpData) if err ! nil { t.Fatalf(json.Marshal() error %v, err) } var got struct { Timestamp uint32 Size uint32 Data string } if err : json.Unmarshal(encoded, got); err ! nil { t.Fatalf(json.Unmarshal() error %v; JSON %s, err, encoded) } if got.Timestamp ! rtmpData.Timestamp { t.Errorf(Timestamp %d, want %d, got.Timestamp, rtmpData.Timestamp) } if got.Size ! rtmpData.Size { t.Errorf(Size %d, want %d, got.Size, rtmpData.Size) } if got.Data ! rtmpData.String() { t.Errorf(Data %q, want %q, got.Data, rtmpData.String()) } if t.Failed() { return } jsonBytes, err : rtmpData.MarshalJSON() if err ! nil { t.Log(Error:, err) return } t.Log(string(jsonBytes)) t.Log(TestRTMPDataMarshalJSON 成功) }第七测试结果——见证奇迹的时刻go test -v .运行单元测试输出如下 RUN TestRTMPDataString RTMPData_test.go:57: 1717000000 17 00 00 00 00 00 00 00 00 00 RTMPData_test.go:58: TestRTMPDataString 成功 --- PASS: TestRTMPDataString (0.00s) RUN TestRTMPDataMarshalJSON RTMPData_test.go:94: {Timestamp:1717000000,Size:13,Data:1717000000 1700 00 00 00 00 00 00 00 00} RTMPData_test.go:95: TestRTMPDataMarshalJSON 成功 --- PASS: TestRTMPDataMarshalJSON (0.00s) PASS ok test.monibuca.com 2.178s解读String()正确输出了时间戳和前 10 字节的十六进制MarshalJSON()生成的 JSON 可以被标准库成功解析Data字段中17正是 H.264 关键帧的标识证明我们的模拟数据“演技在线”尾声小结构体的大使命RTMPData 没有华丽的界面也不处理网络连接但它默默承载着每一帧音视频的“身份信息”。它就像一张标准化的快递单让数据在服务器、客户端、转码器之间畅通无阻。下次当你刷到一个流畅的直播时不妨想一想——在那背后也许正有一个 RTMPData 正带着它的时间戳和十六进制字节飞奔在网络的赛道上。