first blood
This commit is contained in:
commit
3733fdedcc
475
flv.go
Normal file
475
flv.go
Normal file
@ -0,0 +1,475 @@
|
||||
package flv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/nareix/av"
|
||||
"github.com/nareix/bits"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
TAG_AUDIO = 8
|
||||
TAG_VIDEO = 9
|
||||
TAG_SCRIPTDATA = 18
|
||||
)
|
||||
|
||||
type Tag interface {
|
||||
Type() uint8
|
||||
Len() int
|
||||
Marshal(*Writer) error
|
||||
Unmarshal(*Reader) error
|
||||
}
|
||||
|
||||
type Scriptdata struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (self Scriptdata) Type() uint8 {
|
||||
return TAG_SCRIPTDATA
|
||||
}
|
||||
|
||||
func (self Scriptdata) Marshal(w *Writer) (err error) {
|
||||
if _, err = w.Write(self.Data); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self Scriptdata) Len() int {
|
||||
return len(self.Data)
|
||||
}
|
||||
|
||||
func (self *Scriptdata) Unmarshal(r *Reader) (err error) {
|
||||
self.Data = make([]byte, r.N())
|
||||
if _, err = io.ReadFull(r, self.Data); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
SOUND_AAC = 10
|
||||
|
||||
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 = 0
|
||||
)
|
||||
|
||||
type Audiodata struct {
|
||||
/*
|
||||
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
|
||||
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (self Audiodata) Type() uint8 {
|
||||
return TAG_AUDIO
|
||||
}
|
||||
|
||||
func (self Audiodata) Len() int {
|
||||
return 2 + len(self.Data)
|
||||
}
|
||||
|
||||
func (self Audiodata) Marshal(w *Writer) (err error) {
|
||||
var flags uint8
|
||||
flags |= self.SoundFormat << 4
|
||||
flags |= self.SoundRate << 2
|
||||
flags |= self.SoundSize << 1
|
||||
flags |= self.SoundType
|
||||
if err = w.WriteUInt8(flags); err != nil {
|
||||
return
|
||||
}
|
||||
if self.SoundFormat == SOUND_AAC {
|
||||
if err = w.WriteUInt8(self.AACPacketType); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = w.Write(self.Data); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("flv: Audiodata.Marshal: unsupported SoundFormat=%d", self.SoundFormat)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Audiodata) Unmarshal(r *Reader) (err error) {
|
||||
var flags uint8
|
||||
if flags, err = r.ReadUInt8(); err != nil {
|
||||
return
|
||||
}
|
||||
self.SoundFormat = flags >> 4
|
||||
self.SoundRate = (flags >> 2) & 0x3
|
||||
self.SoundSize = (flags >> 1) & 0x1
|
||||
self.SoundType = flags & 0x1
|
||||
if self.SoundFormat == SOUND_AAC {
|
||||
if self.AACPacketType, err = r.ReadUInt8(); err != nil {
|
||||
return
|
||||
}
|
||||
self.Data = make([]byte, r.N())
|
||||
if _, err = io.ReadFull(r, self.Data); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("flv: Audiodata.Unmarshal: unsupported SoundFormat=%d", self.SoundFormat)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
AVC_SEQHDR = 0
|
||||
AVC_NALU = 1
|
||||
AVC_EOS = 2
|
||||
|
||||
FRAME_KEY = 1
|
||||
FRAME_INTER = 2
|
||||
|
||||
CODEC_AAC = 7
|
||||
)
|
||||
|
||||
type Videodata struct {
|
||||
/*
|
||||
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
|
||||
|
||||
Data []byte
|
||||
CompositionTime int32
|
||||
}
|
||||
|
||||
func (self Videodata) Type() uint8 {
|
||||
return TAG_VIDEO
|
||||
}
|
||||
|
||||
func (self Videodata) Len() int {
|
||||
return 5 + len(self.Data)
|
||||
}
|
||||
|
||||
func (self *Videodata) Unmarshal(r *Reader) (err error) {
|
||||
var flags uint8
|
||||
if flags, err = r.ReadUInt8(); err != nil {
|
||||
return
|
||||
}
|
||||
self.FrameType = flags >> 4
|
||||
self.CodecID = flags & 0xff
|
||||
if self.AVCPacketType, err = r.ReadUInt8(); err != nil {
|
||||
return
|
||||
}
|
||||
if self.CompositionTime, err = r.ReadInt24BE(); err != nil {
|
||||
return
|
||||
}
|
||||
switch self.AVCPacketType {
|
||||
case AVC_SEQHDR, AVC_NALU:
|
||||
self.Data = make([]byte, r.N())
|
||||
if _, err = io.ReadFull(r, self.Data); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self Videodata) Marshal(w *Writer) (err error) {
|
||||
flags := self.FrameType<<4 | self.CodecID
|
||||
if err = w.WriteUInt8(flags); err != nil {
|
||||
return
|
||||
}
|
||||
if err = w.WriteUInt8(self.AVCPacketType); err != nil {
|
||||
return
|
||||
}
|
||||
if err = w.WriteInt24BE(self.CompositionTime); err != nil {
|
||||
return
|
||||
}
|
||||
switch self.AVCPacketType {
|
||||
case AVC_SEQHDR, AVC_NALU:
|
||||
if _, err = w.Write(self.Data); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Reader struct {
|
||||
bits.IntReader
|
||||
}
|
||||
|
||||
func NewReader(r io.Reader) *Reader {
|
||||
return &Reader{IntReader: bits.NewIntReader(r)}
|
||||
}
|
||||
|
||||
func (self *Reader) NewLimitedReader(n int) *Reader {
|
||||
nr := *self
|
||||
nr.IntReader.R = &io.LimitedReader{R: nr.IntReader.R, N: int64(n)}
|
||||
return &nr
|
||||
}
|
||||
|
||||
func (self Reader) N() int {
|
||||
return int(self.IntReader.R.(*io.LimitedReader).N)
|
||||
}
|
||||
|
||||
func (self *Reader) ReadHeader() (err error) {
|
||||
var cc3 uint32
|
||||
if cc3, err = self.ReadUInt24BE(); err != nil {
|
||||
return
|
||||
}
|
||||
if cc3 != 0x464c56 { // 'FLV'
|
||||
err = fmt.Errorf("flv: file header cc3 invalid")
|
||||
return
|
||||
}
|
||||
|
||||
// version
|
||||
if _, err = self.ReadInt8(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// flags
|
||||
if _, err = self.ReadInt8(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var dataoffset uint
|
||||
if dataoffset, err = self.ReadUInt32BE(); err != nil {
|
||||
return
|
||||
}
|
||||
dataoffset -= 9
|
||||
|
||||
// skip header and first `tagsize`
|
||||
if err = self.Skip(int(dataoffset + 4)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Reader) ReadTag() (tag Tag, timestamp int32, err error) {
|
||||
var tagtype uint8
|
||||
if tagtype, err = self.ReadUInt8(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch tagtype {
|
||||
case TAG_AUDIO:
|
||||
tag = &Audiodata{}
|
||||
|
||||
case TAG_VIDEO:
|
||||
tag = &Videodata{}
|
||||
|
||||
case TAG_SCRIPTDATA:
|
||||
tag = &Scriptdata{}
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("flv: ReadTag tagtype=%d invalid", tagtype)
|
||||
return
|
||||
}
|
||||
|
||||
var datasize uint32
|
||||
if datasize, err = self.ReadUInt24BE(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var tslo uint32
|
||||
var tshi uint8
|
||||
if tslo, err = self.ReadUInt24BE(); err != nil {
|
||||
return
|
||||
}
|
||||
if tshi, err = self.ReadUInt8(); err != nil {
|
||||
return
|
||||
}
|
||||
timestamp = int32(tslo|uint32(tshi)<<24)
|
||||
|
||||
if _, err = self.ReadInt24BE(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = tag.Unmarshal(self.NewLimitedReader(int(datasize))); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = self.ReadInt32BE(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
bits.IntWriter
|
||||
}
|
||||
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return &Writer{IntWriter: bits.NewIntWriter(w)}
|
||||
}
|
||||
|
||||
func (self *Writer) WriteTag(tag Tag, timestamp int32) (err error) {
|
||||
if err = self.WriteUInt8(tag.Type()); err != nil {
|
||||
return
|
||||
}
|
||||
datasize := tag.Len()
|
||||
if err = self.WriteUInt24BE(uint32(datasize)); err != nil {
|
||||
return
|
||||
}
|
||||
if err = self.WriteUInt24BE(uint32(timestamp & 0xffffff)); err != nil {
|
||||
return
|
||||
}
|
||||
if err = self.WriteUInt8(uint8(timestamp >> 24)); err != nil {
|
||||
return
|
||||
}
|
||||
if err = self.WriteInt24BE(0); err != nil {
|
||||
return
|
||||
}
|
||||
if err = tag.Marshal(self); err != nil {
|
||||
return
|
||||
}
|
||||
if err = self.WriteUInt32BE(uint32(datasize) + 11); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Writer) WriteHeader(hasVideo bool, hasAudio bool) (err error) {
|
||||
// 'FLV', version 1
|
||||
if err = self.WriteInt32BE(0x464c5601); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TypeFlagsReserved UB[5]
|
||||
// TypeFlagsAudio UB[1] Audio tags are present
|
||||
// TypeFlagsReserved UB[1] Must be 0
|
||||
// TypeFlagsVideo UB[1] Video tags are present
|
||||
var flags uint8
|
||||
if hasAudio {
|
||||
flags |= 1 << 2
|
||||
}
|
||||
if hasVideo {
|
||||
flags |= 1
|
||||
}
|
||||
if err = self.WriteUInt8(flags); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 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.
|
||||
if err = self.WriteUInt32BE(9); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// PreviousTagSize0: UI32 Always 0
|
||||
if err = self.WriteUInt32BE(0); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Muxer struct {
|
||||
fw *Writer
|
||||
}
|
||||
|
||||
func NewMuxer(w io.Writer) *Muxer {
|
||||
self := &Muxer{}
|
||||
self.fw = NewWriter(w)
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) {
|
||||
hasVideo := false
|
||||
hasAudio := false
|
||||
for _, stream := range streams {
|
||||
if stream.IsVideo() {
|
||||
hasVideo = true
|
||||
} else if stream.IsAudio() {
|
||||
hasAudio = true
|
||||
}
|
||||
}
|
||||
|
||||
if err = self.fw.WriteHeader(hasVideo, hasAudio); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user