first commit
This commit is contained in:
495
format/flv/flv.go
Normal file
495
format/flv/flv.go
Normal 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
467
format/flv/flvio/amf0.go
Normal 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
411
format/flv/flvio/flvio.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user