first commit

This commit is contained in:
Andrey Semochkin
2019-11-30 21:53:21 +01:00
commit 087a2b4c2d
60 changed files with 19091 additions and 0 deletions

495
format/flv/flv.go Normal file
View File

@@ -0,0 +1,495 @@
package flv
import (
"bufio"
"fmt"
"io"
"github.com/deepch/vdk/av"
"github.com/deepch/vdk/av/avutil"
"github.com/deepch/vdk/codec"
"github.com/deepch/vdk/codec/aacparser"
"github.com/deepch/vdk/codec/fake"
"github.com/deepch/vdk/codec/h264parser"
"github.com/deepch/vdk/format/flv/flvio"
"github.com/deepch/vdk/utils/bits/pio"
)
var MaxProbePacketCount = 20
func NewMetadataByStreams(streams []av.CodecData) (metadata flvio.AMFMap, err error) {
metadata = flvio.AMFMap{}
for _, _stream := range streams {
typ := _stream.Type()
switch {
case typ.IsVideo():
stream := _stream.(av.VideoCodecData)
switch typ {
case av.H264:
metadata["videocodecid"] = flvio.VIDEO_H264
default:
err = fmt.Errorf("flv: metadata: unsupported video codecType=%v", stream.Type())
return
}
metadata["width"] = stream.Width()
metadata["height"] = stream.Height()
metadata["displayWidth"] = stream.Width()
metadata["displayHeight"] = stream.Height()
case typ.IsAudio():
stream := _stream.(av.AudioCodecData)
switch typ {
case av.AAC:
metadata["audiocodecid"] = flvio.SOUND_AAC
case av.SPEEX:
metadata["audiocodecid"] = flvio.SOUND_SPEEX
default:
err = fmt.Errorf("flv: metadata: unsupported audio codecType=%v", stream.Type())
return
}
metadata["audiosamplerate"] = stream.SampleRate()
}
}
return
}
type Prober struct {
HasAudio, HasVideo bool
GotAudio, GotVideo bool
VideoStreamIdx, AudioStreamIdx int
PushedCount int
Streams []av.CodecData
CachedPkts []av.Packet
}
func (self *Prober) CacheTag(_tag flvio.Tag, timestamp int32) {
pkt, _ := self.TagToPacket(_tag, timestamp)
self.CachedPkts = append(self.CachedPkts, pkt)
}
func (self *Prober) PushTag(tag flvio.Tag, timestamp int32) (err error) {
self.PushedCount++
if self.PushedCount > MaxProbePacketCount {
err = fmt.Errorf("flv: max probe packet count reached")
return
}
switch tag.Type {
case flvio.TAG_VIDEO:
switch tag.AVCPacketType {
case flvio.AVC_SEQHDR:
if !self.GotVideo {
var stream h264parser.CodecData
if stream, err = h264parser.NewCodecDataFromAVCDecoderConfRecord(tag.Data); err != nil {
err = fmt.Errorf("flv: h264 seqhdr invalid")
return
}
self.VideoStreamIdx = len(self.Streams)
self.Streams = append(self.Streams, stream)
self.GotVideo = true
}
case flvio.AVC_NALU:
self.CacheTag(tag, timestamp)
}
case flvio.TAG_AUDIO:
switch tag.SoundFormat {
case flvio.SOUND_AAC:
switch tag.AACPacketType {
case flvio.AAC_SEQHDR:
if !self.GotAudio {
var stream aacparser.CodecData
if stream, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(tag.Data); err != nil {
err = fmt.Errorf("flv: aac seqhdr invalid")
return
}
self.AudioStreamIdx = len(self.Streams)
self.Streams = append(self.Streams, stream)
self.GotAudio = true
}
case flvio.AAC_RAW:
self.CacheTag(tag, timestamp)
}
case flvio.SOUND_SPEEX:
if !self.GotAudio {
stream := codec.NewSpeexCodecData(16000, tag.ChannelLayout())
self.AudioStreamIdx = len(self.Streams)
self.Streams = append(self.Streams, stream)
self.GotAudio = true
self.CacheTag(tag, timestamp)
}
case flvio.SOUND_NELLYMOSER:
if !self.GotAudio {
stream := fake.CodecData{
CodecType_: av.NELLYMOSER,
SampleRate_: 16000,
SampleFormat_: av.S16,
ChannelLayout_: tag.ChannelLayout(),
}
self.AudioStreamIdx = len(self.Streams)
self.Streams = append(self.Streams, stream)
self.GotAudio = true
self.CacheTag(tag, timestamp)
}
}
}
return
}
func (self *Prober) Probed() (ok bool) {
if self.HasAudio || self.HasVideo {
if self.HasAudio == self.GotAudio && self.HasVideo == self.GotVideo {
return true
}
} else {
if self.PushedCount == MaxProbePacketCount {
return true
}
}
return
}
func (self *Prober) TagToPacket(tag flvio.Tag, timestamp int32) (pkt av.Packet, ok bool) {
switch tag.Type {
case flvio.TAG_VIDEO:
pkt.Idx = int8(self.VideoStreamIdx)
switch tag.AVCPacketType {
case flvio.AVC_NALU:
ok = true
pkt.Data = tag.Data
pkt.CompositionTime = flvio.TsToTime(tag.CompositionTime)
pkt.IsKeyFrame = tag.FrameType == flvio.FRAME_KEY
}
case flvio.TAG_AUDIO:
pkt.Idx = int8(self.AudioStreamIdx)
switch tag.SoundFormat {
case flvio.SOUND_AAC:
switch tag.AACPacketType {
case flvio.AAC_RAW:
ok = true
pkt.Data = tag.Data
}
case flvio.SOUND_SPEEX:
ok = true
pkt.Data = tag.Data
case flvio.SOUND_NELLYMOSER:
ok = true
pkt.Data = tag.Data
}
}
pkt.Time = flvio.TsToTime(timestamp)
return
}
func (self *Prober) Empty() bool {
return len(self.CachedPkts) == 0
}
func (self *Prober) PopPacket() av.Packet {
pkt := self.CachedPkts[0]
self.CachedPkts = self.CachedPkts[1:]
return pkt
}
func CodecDataToTag(stream av.CodecData) (_tag flvio.Tag, ok bool, err error) {
switch stream.Type() {
case av.H264:
h264 := stream.(h264parser.CodecData)
tag := flvio.Tag{
Type: flvio.TAG_VIDEO,
AVCPacketType: flvio.AVC_SEQHDR,
CodecID: flvio.VIDEO_H264,
Data: h264.AVCDecoderConfRecordBytes(),
FrameType: flvio.FRAME_KEY,
}
ok = true
_tag = tag
case av.NELLYMOSER:
case av.SPEEX:
case av.AAC:
aac := stream.(aacparser.CodecData)
tag := flvio.Tag{
Type: flvio.TAG_AUDIO,
SoundFormat: flvio.SOUND_AAC,
SoundRate: flvio.SOUND_44Khz,
AACPacketType: flvio.AAC_SEQHDR,
Data: aac.MPEG4AudioConfigBytes(),
}
switch aac.SampleFormat().BytesPerSample() {
case 1:
tag.SoundSize = flvio.SOUND_8BIT
default:
tag.SoundSize = flvio.SOUND_16BIT
}
switch aac.ChannelLayout().Count() {
case 1:
tag.SoundType = flvio.SOUND_MONO
case 2:
tag.SoundType = flvio.SOUND_STEREO
}
ok = true
_tag = tag
default:
err = fmt.Errorf("flv: unspported codecType=%v", stream.Type())
return
}
return
}
func PacketToTag(pkt av.Packet, stream av.CodecData) (tag flvio.Tag, timestamp int32) {
switch stream.Type() {
case av.H264:
tag = flvio.Tag{
Type: flvio.TAG_VIDEO,
AVCPacketType: flvio.AVC_NALU,
CodecID: flvio.VIDEO_H264,
Data: pkt.Data,
CompositionTime: flvio.TimeToTs(pkt.CompositionTime),
}
if pkt.IsKeyFrame {
tag.FrameType = flvio.FRAME_KEY
} else {
tag.FrameType = flvio.FRAME_INTER
}
case av.AAC:
tag = flvio.Tag{
Type: flvio.TAG_AUDIO,
SoundFormat: flvio.SOUND_AAC,
SoundRate: flvio.SOUND_44Khz,
AACPacketType: flvio.AAC_RAW,
Data: pkt.Data,
}
astream := stream.(av.AudioCodecData)
switch astream.SampleFormat().BytesPerSample() {
case 1:
tag.SoundSize = flvio.SOUND_8BIT
default:
tag.SoundSize = flvio.SOUND_16BIT
}
switch astream.ChannelLayout().Count() {
case 1:
tag.SoundType = flvio.SOUND_MONO
case 2:
tag.SoundType = flvio.SOUND_STEREO
}
case av.SPEEX:
tag = flvio.Tag{
Type: flvio.TAG_AUDIO,
SoundFormat: flvio.SOUND_SPEEX,
Data: pkt.Data,
}
case av.NELLYMOSER:
tag = flvio.Tag{
Type: flvio.TAG_AUDIO,
SoundFormat: flvio.SOUND_NELLYMOSER,
Data: pkt.Data,
}
}
timestamp = flvio.TimeToTs(pkt.Time)
return
}
type Muxer struct {
bufw writeFlusher
b []byte
streams []av.CodecData
}
type writeFlusher interface {
io.Writer
Flush() error
}
func NewMuxerWriteFlusher(w writeFlusher) *Muxer {
return &Muxer{
bufw: w,
b: make([]byte, 256),
}
}
func NewMuxer(w io.Writer) *Muxer {
return NewMuxerWriteFlusher(bufio.NewWriterSize(w, pio.RecommendBufioSize))
}
var CodecTypes = []av.CodecType{av.H264, av.AAC, av.SPEEX}
func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) {
var flags uint8
for _, stream := range streams {
if stream.Type().IsVideo() {
flags |= flvio.FILE_HAS_VIDEO
} else if stream.Type().IsAudio() {
flags |= flvio.FILE_HAS_AUDIO
}
}
n := flvio.FillFileHeader(self.b, flags)
if _, err = self.bufw.Write(self.b[:n]); err != nil {
return
}
for _, stream := range streams {
var tag flvio.Tag
var ok bool
if tag, ok, err = CodecDataToTag(stream); err != nil {
return
}
if ok {
if err = flvio.WriteTag(self.bufw, tag, 0, self.b); err != nil {
return
}
}
}
self.streams = streams
return
}
func (self *Muxer) WritePacket(pkt av.Packet) (err error) {
stream := self.streams[pkt.Idx]
tag, timestamp := PacketToTag(pkt, stream)
if err = flvio.WriteTag(self.bufw, tag, timestamp, self.b); err != nil {
return
}
return
}
func (self *Muxer) WriteTrailer() (err error) {
if err = self.bufw.Flush(); err != nil {
return
}
return
}
type Demuxer struct {
prober *Prober
bufr *bufio.Reader
b []byte
stage int
}
func NewDemuxer(r io.Reader) *Demuxer {
return &Demuxer{
bufr: bufio.NewReaderSize(r, pio.RecommendBufioSize),
prober: &Prober{},
b: make([]byte, 256),
}
}
func (self *Demuxer) prepare() (err error) {
for self.stage < 2 {
switch self.stage {
case 0:
if _, err = io.ReadFull(self.bufr, self.b[:flvio.FileHeaderLength]); err != nil {
return
}
var flags uint8
var skip int
if flags, skip, err = flvio.ParseFileHeader(self.b); err != nil {
return
}
if _, err = self.bufr.Discard(skip); err != nil {
return
}
if flags&flvio.FILE_HAS_AUDIO != 0 {
self.prober.HasAudio = true
}
if flags&flvio.FILE_HAS_VIDEO != 0 {
self.prober.HasVideo = true
}
self.stage++
case 1:
for !self.prober.Probed() {
var tag flvio.Tag
var timestamp int32
if tag, timestamp, err = flvio.ReadTag(self.bufr, self.b); err != nil {
return
}
if err = self.prober.PushTag(tag, timestamp); err != nil {
return
}
}
self.stage++
}
}
return
}
func (self *Demuxer) Streams() (streams []av.CodecData, err error) {
if err = self.prepare(); err != nil {
return
}
streams = self.prober.Streams
return
}
func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) {
if err = self.prepare(); err != nil {
return
}
if !self.prober.Empty() {
pkt = self.prober.PopPacket()
return
}
for {
var tag flvio.Tag
var timestamp int32
if tag, timestamp, err = flvio.ReadTag(self.bufr, self.b); err != nil {
return
}
var ok bool
if pkt, ok = self.prober.TagToPacket(tag, timestamp); ok {
return
}
}
return
}
func Handler(h *avutil.RegisterHandler) {
h.Probe = func(b []byte) bool {
return b[0] == 'F' && b[1] == 'L' && b[2] == 'V'
}
h.Ext = ".flv"
h.ReaderDemuxer = func(r io.Reader) av.Demuxer {
return NewDemuxer(r)
}
h.WriterMuxer = func(w io.Writer) av.Muxer {
return NewMuxer(w)
}
h.CodecTypes = CodecTypes
}

467
format/flv/flvio/amf0.go Normal file
View File

@@ -0,0 +1,467 @@
package flvio
import (
"fmt"
"math"
"strings"
"time"
"github.com/deepch/vdk/utils/bits/pio"
)
type AMF0ParseError struct {
Offset int
Message string
Next *AMF0ParseError
}
func (self *AMF0ParseError) Error() string {
s := []string{}
for p := self; p != nil; p = p.Next {
s = append(s, fmt.Sprintf("%s:%d", p.Message, p.Offset))
}
return "amf0 parse error: " + strings.Join(s, ",")
}
func amf0ParseErr(message string, offset int, err error) error {
next, _ := err.(*AMF0ParseError)
return &AMF0ParseError{
Offset: offset,
Message: message,
Next: next,
}
}
type AMFMap map[string]interface{}
type AMFArray []interface{}
type AMFECMAArray map[string]interface{}
func parseBEFloat64(b []byte) float64 {
return math.Float64frombits(pio.U64BE(b))
}
func fillBEFloat64(b []byte, f float64) int {
pio.PutU64BE(b, math.Float64bits(f))
return 8
}
const lenAMF0Number = 9
func fillAMF0Number(b []byte, f float64) int {
b[0] = numbermarker
fillBEFloat64(b[1:], f)
return lenAMF0Number
}
const (
amf3undefinedmarker = iota
amf3nullmarker
amf3falsemarker
amf3truemarker
amf3integermarker
amf3doublemarker
amf3stringmarker
amf3xmldocmarker
amf3datemarker
amf3arraymarker
amf3objectmarker
amf3xmlmarker
amf3bytearraymarker
amf3vectorintmarker
amf3vectoruintmarker
amf3vectordoublemarker
amf3vectorobjectmarker
amf3dictionarymarker
)
const (
numbermarker = iota
booleanmarker
stringmarker
objectmarker
movieclipmarker
nullmarker
undefinedmarker
referencemarker
ecmaarraymarker
objectendmarker
strictarraymarker
datemarker
longstringmarker
unsupportedmarker
recordsetmarker
xmldocumentmarker
typedobjectmarker
avmplusobjectmarker
)
func LenAMF0Val(_val interface{}) (n int) {
switch val := _val.(type) {
case int8:
n += lenAMF0Number
case int16:
n += lenAMF0Number
case int32:
n += lenAMF0Number
case int64:
n += lenAMF0Number
case int:
n += lenAMF0Number
case uint8:
n += lenAMF0Number
case uint16:
n += lenAMF0Number
case uint32:
n += lenAMF0Number
case uint64:
n += lenAMF0Number
case uint:
n += lenAMF0Number
case float32:
n += lenAMF0Number
case float64:
n += lenAMF0Number
case string:
u := len(val)
if u <= 65536 {
n += 3
} else {
n += 5
}
n += int(u)
case AMFECMAArray:
n += 5
for k, v := range val {
n += 2 + len(k)
n += LenAMF0Val(v)
}
n += 3
case AMFMap:
n++
for k, v := range val {
if len(k) > 0 {
n += 2 + len(k)
n += LenAMF0Val(v)
}
}
n += 3
case AMFArray:
n += 5
for _, v := range val {
n += LenAMF0Val(v)
}
case time.Time:
n += 1 + 8 + 2
case bool:
n += 2
case nil:
n++
}
return
}
func FillAMF0Val(b []byte, _val interface{}) (n int) {
switch val := _val.(type) {
case int8:
n += fillAMF0Number(b[n:], float64(val))
case int16:
n += fillAMF0Number(b[n:], float64(val))
case int32:
n += fillAMF0Number(b[n:], float64(val))
case int64:
n += fillAMF0Number(b[n:], float64(val))
case int:
n += fillAMF0Number(b[n:], float64(val))
case uint8:
n += fillAMF0Number(b[n:], float64(val))
case uint16:
n += fillAMF0Number(b[n:], float64(val))
case uint32:
n += fillAMF0Number(b[n:], float64(val))
case uint64:
n += fillAMF0Number(b[n:], float64(val))
case uint:
n += fillAMF0Number(b[n:], float64(val))
case float32:
n += fillAMF0Number(b[n:], float64(val))
case float64:
n += fillAMF0Number(b[n:], float64(val))
case string:
u := len(val)
if u <= 65536 {
b[n] = stringmarker
n++
pio.PutU16BE(b[n:], uint16(u))
n += 2
} else {
b[n] = longstringmarker
n++
pio.PutU32BE(b[n:], uint32(u))
n += 4
}
copy(b[n:], []byte(val))
n += len(val)
case AMFECMAArray:
b[n] = ecmaarraymarker
n++
pio.PutU32BE(b[n:], uint32(len(val)))
n += 4
for k, v := range val {
pio.PutU16BE(b[n:], uint16(len(k)))
n += 2
copy(b[n:], []byte(k))
n += len(k)
n += FillAMF0Val(b[n:], v)
}
pio.PutU24BE(b[n:], 0x000009)
n += 3
case AMFMap:
b[n] = objectmarker
n++
for k, v := range val {
if len(k) > 0 {
pio.PutU16BE(b[n:], uint16(len(k)))
n += 2
copy(b[n:], []byte(k))
n += len(k)
n += FillAMF0Val(b[n:], v)
}
}
pio.PutU24BE(b[n:], 0x000009)
n += 3
case AMFArray:
b[n] = strictarraymarker
n++
pio.PutU32BE(b[n:], uint32(len(val)))
n += 4
for _, v := range val {
n += FillAMF0Val(b[n:], v)
}
case time.Time:
b[n] = datemarker
n++
u := val.UnixNano()
f := float64(u / 1000000)
n += fillBEFloat64(b[n:], f)
pio.PutU16BE(b[n:], uint16(0))
n += 2
case bool:
b[n] = booleanmarker
n++
var u uint8
if val {
u = 1
} else {
u = 0
}
b[n] = u
n++
case nil:
b[n] = nullmarker
n++
}
return
}
func ParseAMF0Val(b []byte) (val interface{}, n int, err error) {
return parseAMF0Val(b, 0)
}
func parseAMF0Val(b []byte, offset int) (val interface{}, n int, err error) {
if len(b) < n+1 {
err = amf0ParseErr("marker", offset+n, err)
return
}
marker := b[n]
n++
switch marker {
case numbermarker:
if len(b) < n+8 {
err = amf0ParseErr("number", offset+n, err)
return
}
val = parseBEFloat64(b[n:])
n += 8
case booleanmarker:
if len(b) < n+1 {
err = amf0ParseErr("boolean", offset+n, err)
return
}
val = b[n] != 0
n++
case stringmarker:
if len(b) < n+2 {
err = amf0ParseErr("string.length", offset+n, err)
return
}
length := int(pio.U16BE(b[n:]))
n += 2
if len(b) < n+length {
err = amf0ParseErr("string.body", offset+n, err)
return
}
val = string(b[n : n+length])
n += length
case objectmarker:
obj := AMFMap{}
for {
if len(b) < n+2 {
err = amf0ParseErr("object.key.length", offset+n, err)
return
}
length := int(pio.U16BE(b[n:]))
n += 2
if length == 0 {
break
}
if len(b) < n+length {
err = amf0ParseErr("object.key.body", offset+n, err)
return
}
okey := string(b[n : n+length])
n += length
var nval int
var oval interface{}
if oval, nval, err = parseAMF0Val(b[n:], offset+n); err != nil {
err = amf0ParseErr("object.val", offset+n, err)
return
}
n += nval
obj[okey] = oval
}
if len(b) < n+1 {
err = amf0ParseErr("object.end", offset+n, err)
return
}
n++
val = obj
case nullmarker:
case undefinedmarker:
case ecmaarraymarker:
if len(b) < n+4 {
err = amf0ParseErr("array.count", offset+n, err)
return
}
n += 4
obj := AMFMap{}
for {
if len(b) < n+2 {
err = amf0ParseErr("array.key.length", offset+n, err)
return
}
length := int(pio.U16BE(b[n:]))
n += 2
if length == 0 {
break
}
if len(b) < n+length {
err = amf0ParseErr("array.key.body", offset+n, err)
return
}
okey := string(b[n : n+length])
n += length
var nval int
var oval interface{}
if oval, nval, err = parseAMF0Val(b[n:], offset+n); err != nil {
err = amf0ParseErr("array.val", offset+n, err)
return
}
n += nval
obj[okey] = oval
}
if len(b) < n+1 {
err = amf0ParseErr("array.end", offset+n, err)
return
}
n += 1
val = obj
case objectendmarker:
if len(b) < n+3 {
err = amf0ParseErr("objectend", offset+n, err)
return
}
n += 3
case strictarraymarker:
if len(b) < n+4 {
err = amf0ParseErr("strictarray.count", offset+n, err)
return
}
count := int(pio.U32BE(b[n:]))
n += 4
obj := make(AMFArray, count)
for i := 0; i < int(count); i++ {
var nval int
if obj[i], nval, err = parseAMF0Val(b[n:], offset+n); err != nil {
err = amf0ParseErr("strictarray.val", offset+n, err)
return
}
n += nval
}
val = obj
case datemarker:
if len(b) < n+8+2 {
err = amf0ParseErr("date", offset+n, err)
return
}
ts := parseBEFloat64(b[n:])
n += 8 + 2
val = time.Unix(int64(ts/1000), (int64(ts)%1000)*1000000)
case longstringmarker:
if len(b) < n+4 {
err = amf0ParseErr("longstring.length", offset+n, err)
return
}
length := int(pio.U32BE(b[n:]))
n += 4
if len(b) < n+length {
err = amf0ParseErr("longstring.body", offset+n, err)
return
}
val = string(b[n : n+length])
n += length
default:
err = amf0ParseErr(fmt.Sprintf("invalidmarker=%d", marker), offset+n, err)
return
}
return
}

411
format/flv/flvio/flvio.go Normal file
View File

@@ -0,0 +1,411 @@
package flvio
import (
"fmt"
"io"
"time"
"github.com/deepch/vdk/av"
"github.com/deepch/vdk/utils/bits/pio"
)
func TsToTime(ts int32) time.Duration {
return time.Millisecond * time.Duration(ts)
}
func TimeToTs(tm time.Duration) int32 {
return int32(tm / time.Millisecond)
}
const MaxTagSubHeaderLength = 16
const (
TAG_AUDIO = 8
TAG_VIDEO = 9
TAG_SCRIPTDATA = 18
)
const (
SOUND_MP3 = 2
SOUND_NELLYMOSER_16KHZ_MONO = 4
SOUND_NELLYMOSER_8KHZ_MONO = 5
SOUND_NELLYMOSER = 6
SOUND_ALAW = 7
SOUND_MULAW = 8
SOUND_AAC = 10
SOUND_SPEEX = 11
SOUND_5_5Khz = 0
SOUND_11Khz = 1
SOUND_22Khz = 2
SOUND_44Khz = 3
SOUND_8BIT = 0
SOUND_16BIT = 1
SOUND_MONO = 0
SOUND_STEREO = 1
AAC_SEQHDR = 0
AAC_RAW = 1
)
const (
AVC_SEQHDR = 0
AVC_NALU = 1
AVC_EOS = 2
FRAME_KEY = 1
FRAME_INTER = 2
VIDEO_H264 = 7
)
type Tag struct {
Type uint8
/*
SoundFormat: UB[4]
0 = Linear PCM, platform endian
1 = ADPCM
2 = MP3
3 = Linear PCM, little endian
4 = Nellymoser 16-kHz mono
5 = Nellymoser 8-kHz mono
6 = Nellymoser
7 = G.711 A-law logarithmic PCM
8 = G.711 mu-law logarithmic PCM
9 = reserved
10 = AAC
11 = Speex
14 = MP3 8-Khz
15 = Device-specific sound
Formats 7, 8, 14, and 15 are reserved for internal use
AAC is supported in Flash Player 9,0,115,0 and higher.
Speex is supported in Flash Player 10 and higher.
*/
SoundFormat uint8
/*
SoundRate: UB[2]
Sampling rate
0 = 5.5-kHz For AAC: always 3
1 = 11-kHz
2 = 22-kHz
3 = 44-kHz
*/
SoundRate uint8
/*
SoundSize: UB[1]
0 = snd8Bit
1 = snd16Bit
Size of each sample.
This parameter only pertains to uncompressed formats.
Compressed formats always decode to 16 bits internally
*/
SoundSize uint8
/*
SoundType: UB[1]
0 = sndMono
1 = sndStereo
Mono or stereo sound For Nellymoser: always 0
For AAC: always 1
*/
SoundType uint8
/*
0: AAC sequence header
1: AAC raw
*/
AACPacketType uint8
/*
1: keyframe (for AVC, a seekable frame)
2: inter frame (for AVC, a non- seekable frame)
3: disposable inter frame (H.263 only)
4: generated keyframe (reserved for server use only)
5: video info/command frame
*/
FrameType uint8
/*
1: JPEG (currently unused)
2: Sorenson H.263
3: Screen video
4: On2 VP6
5: On2 VP6 with alpha channel
6: Screen video version 2
7: AVC
*/
CodecID uint8
/*
0: AVC sequence header
1: AVC NALU
2: AVC end of sequence (lower level NALU sequence ender is not required or supported)
*/
AVCPacketType uint8
CompositionTime int32
Data []byte
}
func (self Tag) ChannelLayout() av.ChannelLayout {
if self.SoundType == SOUND_MONO {
return av.CH_MONO
} else {
return av.CH_STEREO
}
}
func (self *Tag) audioParseHeader(b []byte) (n int, err error) {
if len(b) < n+1 {
err = fmt.Errorf("audiodata: parse invalid")
return
}
flags := b[n]
n++
self.SoundFormat = flags >> 4
self.SoundRate = (flags >> 2) & 0x3
self.SoundSize = (flags >> 1) & 0x1
self.SoundType = flags & 0x1
switch self.SoundFormat {
case SOUND_AAC:
if len(b) < n+1 {
err = fmt.Errorf("audiodata: parse invalid")
return
}
self.AACPacketType = b[n]
n++
}
return
}
func (self Tag) audioFillHeader(b []byte) (n int) {
var flags uint8
flags |= self.SoundFormat << 4
flags |= self.SoundRate << 2
flags |= self.SoundSize << 1
flags |= self.SoundType
b[n] = flags
n++
switch self.SoundFormat {
case SOUND_AAC:
b[n] = self.AACPacketType
n++
}
return
}
func (self *Tag) videoParseHeader(b []byte) (n int, err error) {
if len(b) < n+1 {
err = fmt.Errorf("videodata: parse invalid")
return
}
flags := b[n]
self.FrameType = flags >> 4
self.CodecID = flags & 0xf
n++
if self.FrameType == FRAME_INTER || self.FrameType == FRAME_KEY {
if len(b) < n+4 {
err = fmt.Errorf("videodata: parse invalid")
return
}
self.AVCPacketType = b[n]
n++
self.CompositionTime = pio.I24BE(b[n:])
n += 3
}
return
}
func (self Tag) videoFillHeader(b []byte) (n int) {
flags := self.FrameType<<4 | self.CodecID
b[n] = flags
n++
b[n] = self.AVCPacketType
n++
pio.PutI24BE(b[n:], self.CompositionTime)
n += 3
return
}
func (self Tag) FillHeader(b []byte) (n int) {
switch self.Type {
case TAG_AUDIO:
return self.audioFillHeader(b)
case TAG_VIDEO:
return self.videoFillHeader(b)
}
return
}
func (self *Tag) ParseHeader(b []byte) (n int, err error) {
switch self.Type {
case TAG_AUDIO:
return self.audioParseHeader(b)
case TAG_VIDEO:
return self.videoParseHeader(b)
}
return
}
const (
// TypeFlagsReserved UB[5]
// TypeFlagsAudio UB[1] Audio tags are present
// TypeFlagsReserved UB[1] Must be 0
// TypeFlagsVideo UB[1] Video tags are present
FILE_HAS_AUDIO = 0x4
FILE_HAS_VIDEO = 0x1
)
const TagHeaderLength = 11
const TagTrailerLength = 4
func ParseTagHeader(b []byte) (tag Tag, ts int32, datalen int, err error) {
tagtype := b[0]
switch tagtype {
case TAG_AUDIO, TAG_VIDEO, TAG_SCRIPTDATA:
tag = Tag{Type: tagtype}
default:
err = fmt.Errorf("flvio: ReadTag tagtype=%d invalid", tagtype)
return
}
datalen = int(pio.U24BE(b[1:4]))
var tslo uint32
var tshi uint8
tslo = pio.U24BE(b[4:7])
tshi = b[7]
ts = int32(tslo | uint32(tshi)<<24)
return
}
func ReadTag(r io.Reader, b []byte) (tag Tag, ts int32, err error) {
if _, err = io.ReadFull(r, b[:TagHeaderLength]); err != nil {
return
}
var datalen int
if tag, ts, datalen, err = ParseTagHeader(b); err != nil {
return
}
data := make([]byte, datalen)
if _, err = io.ReadFull(r, data); err != nil {
return
}
var n int
if n, err = (&tag).ParseHeader(data); err != nil {
return
}
tag.Data = data[n:]
if _, err = io.ReadFull(r, b[:4]); err != nil {
return
}
return
}
func FillTagHeader(b []byte, tagtype uint8, datalen int, ts int32) (n int) {
b[n] = tagtype
n++
pio.PutU24BE(b[n:], uint32(datalen))
n += 3
pio.PutU24BE(b[n:], uint32(ts&0xffffff))
n += 3
b[n] = uint8(ts >> 24)
n++
pio.PutI24BE(b[n:], 0)
n += 3
return
}
func FillTagTrailer(b []byte, datalen int) (n int) {
pio.PutU32BE(b[n:], uint32(datalen+TagHeaderLength))
n += 4
return
}
func WriteTag(w io.Writer, tag Tag, ts int32, b []byte) (err error) {
data := tag.Data
n := tag.FillHeader(b[TagHeaderLength:])
datalen := len(data) + n
n += FillTagHeader(b, tag.Type, datalen, ts)
if _, err = w.Write(b[:n]); err != nil {
return
}
if _, err = w.Write(data); err != nil {
return
}
n = FillTagTrailer(b, datalen)
if _, err = w.Write(b[:n]); err != nil {
return
}
return
}
const FileHeaderLength = 9
func FillFileHeader(b []byte, flags uint8) (n int) {
// 'FLV', version 1
pio.PutU32BE(b[n:], 0x464c5601)
n += 4
b[n] = flags
n++
// DataOffset: UI32 Offset in bytes from start of file to start of body (that is, size of header)
// The DataOffset field usually has a value of 9 for FLV version 1.
pio.PutU32BE(b[n:], 9)
n += 4
// PreviousTagSize0: UI32 Always 0
pio.PutU32BE(b[n:], 0)
n += 4
return
}
func ParseFileHeader(b []byte) (flags uint8, skip int, err error) {
flv := pio.U24BE(b[0:3])
if flv != 0x464c56 { // 'FLV'
err = fmt.Errorf("flvio: file header cc3 invalid")
return
}
flags = b[4]
skip = int(pio.U32BE(b[5:9])) - 9 + 4
if skip < 0 {
err = fmt.Errorf("flvio: file header datasize invalid")
return
}
return
}