以下代码是视频流解析器的简化示例。输入是包含视频和音频帧的二进制数据。每个框架由以下部分组成:
目标是解析流,从标头和有效负载中提取字段。
所以,第一种方法是:
package main
import (
"fmt"
"encoding/binary"
"bytes"
)
type Type byte
const (
Video Type = 0xFC
Audio Type = 0xFA
)
var HMap = map[Type]string {
Video: "Video",
Audio: "Audio",
}
type CommonHeader struct {
Type Type
}
type HeaderVideo struct {
Width uint16
Height uint16
Length uint32
}
type HeaderAudio struct {
SampleRate uint16
Length uint16
}
func main() {
data := bytes.NewReader([]byte{0xFC, 0x80, 0x07, 0x38, 0x04, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xAF, 0xFA, 0x10, 0x00, 0x01, 0x00, 0xFF})
var cHeader CommonHeader
var dataLength int
for {
err := binary.Read(data, binary.LittleEndian, &cHeader)
if err != nil {
break
}
fmt.Println(HMap[cHeader.Type])
switch cHeader.Type {
case Video:
var info HeaderVideo
binary.Read(data, binary.LittleEndian, &info)
dataLength = int(info.Length)
fmt.Println(info)
case Audio:
var info HeaderAudio
binary.Read(data, binary.LittleEndian, &info)
dataLength = int(info.Length)
fmt.Println(info)
}
payload := make([]byte, dataLength)
data.Read(payload)
fmt.Println(payload)
}
}
它有效,但我不喜欢
switch
情况下的代码重复。本质上,我们必须重复相同的代码,只是因为帧类型不同。
试图避免重复的一种方法是:
package main
import (
"fmt"
"encoding/binary"
"bytes"
)
type Type byte
const (
Video Type = 0xFC
Audio Type = 0xFA
)
var HMap = map[Type]string {
Video: "Video",
Audio: "Audio",
}
type CommonHeader struct {
Type Type
}
type Header interface {
GetLength() int
}
type HeaderVideo struct {
Width uint16
Height uint16
Length uint32
}
func (h HeaderVideo) GetLength() int {
return int(h.Length)
}
type HeaderAudio struct {
SampleRate uint16
Length uint16
}
func (h HeaderAudio) GetLength() int {
return int(h.Length)
}
var TMap = map[Type]func() Header {
Video: func() Header { return &HeaderVideo{} },
Audio: func() Header { return &HeaderAudio{} },
}
func main() {
data := bytes.NewReader([]byte{0xFC, 0x80, 0x07, 0x38, 0x04, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xAF, 0xFA, 0x10, 0x00, 0x01, 0x00, 0xFF})
var cHeader CommonHeader
for {
err := binary.Read(data, binary.LittleEndian, &cHeader)
if err != nil {
break
}
fmt.Println(HMap[cHeader.Type])
info := TMap[cHeader.Type]()
binary.Read(data, binary.LittleEndian, info)
fmt.Println(info)
payload := make([]byte, info.GetLength())
data.Read(payload)
fmt.Println(payload)
}
}
也就是说,我们通过引入
TMap
映射来实现动态类型选择,该映射允许根据帧类型创建正确结构的实例。然而,此解决方案的代价是对每种帧类型重复 GetLength()
方法。
我觉得很令人不安的是,似乎没有办法完全避免重复。 我错过了什么,还是只是语言的限制?
这里有一个相关问题(实际上是由同一个问题触发的),但是,它的前提忽略了动态类型选择的需要,因此接受的解决方案(使用泛型)没有帮助。
您可以将流处理的细节包装到自定义类型的方法中:
package main
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"log"
)
type Type byte
const (
Video Type = 0xFC
Audio Type = 0xFA
)
var HMap = map[Type]string{
Video: "Video",
Audio: "Audio",
}
type CommonHeader struct {
Type Type
}
func (ch CommonHeader) GetMediaHeader() MediaHeader {
switch ch.Type {
case Video:
return &HeaderVideo{}
case Audio:
return &HeaderAudio{}
default:
panic("Unsupported media type")
}
}
func (ch *CommonHeader) Fill(r io.Reader, o binary.ByteOrder) error {
return binary.Read(r, o, ch)
}
func (ch CommonHeader) String() string {
return HMap[ch.Type]
}
type MediaHeader interface {
GetLength() int
Fill(io.Reader, binary.ByteOrder) error
}
type HeaderVideo struct {
Width uint16
Height uint16
Length uint32
}
// Fill implements MediaHeader.
func (hv *HeaderVideo) Fill(r io.Reader, o binary.ByteOrder) error {
return binary.Read(r, o, hv)
}
func (hv HeaderVideo) GetLength() int {
return int(hv.Length)
}
func (hv HeaderVideo) String() string {
return fmt.Sprintf("%#v", hv)
}
var _ MediaHeader = &HeaderVideo{}
type HeaderAudio struct {
SampleRate uint16
Length uint16
}
func (ha *HeaderAudio) Fill(r io.Reader, o binary.ByteOrder) error {
return binary.Read(r, o, ha)
}
func (ha HeaderAudio) GetLength() int {
return int(ha.Length)
}
func (ha HeaderAudio) String() string {
return fmt.Sprintf("%#v", ha)
}
var _ MediaHeader = &HeaderAudio{}
func main() {
data := bytes.NewReader([]byte{0xFC, 0x80, 0x07, 0x38, 0x04, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xAF, 0xFA, 0x10, 0x00, 0x01, 0x00, 0xFF})
cHeader := &CommonHeader{}
var dataLength int
for {
err := cHeader.Fill(data, binary.LittleEndian)
if err != nil {
log.Println(err)
break
}
fmt.Println("cHeader: ", cHeader)
mediaHeader := cHeader.GetMediaHeader()
err = mediaHeader.Fill(data, binary.LittleEndian)
if err != nil {
log.Println(err)
break
}
dataLength = mediaHeader.GetLength()
fmt.Println("Media header: ", mediaHeader)
payload := make([]byte, dataLength)
data.Read(payload)
fmt.Println("Payload: ", payload)
}
}
关键特征是
cHeader.GetMediaHeader()
工厂方法。它知道流类型并生成正确的标头类型。
每种媒体标头类型都知道如何解析流(方法
Fill
)以及如何提取有效负载长度。有了接口MediaHeader
,main
代码精简,没有switch
和重复的业务逻辑。