/* NAME flv.go DESCRIPTION See Readme.md AUTHORS Saxon A. Nelson-Milton Dan Kortschak LICENSE flv.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean) It is free software: you can redistribute it and/or modify them under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. */ // See https://wwwimages2.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10.pdf // for format specification. package flv import "encoding/binary" const ( maxVideoTagSize = 10000 maxAudioTagSize = 10000 ) const ( VideoTagType = 9 AudioTagType = 8 KeyFrameType = 1 InterFrameType = 2 H264 = 7 AVCNALU = 1 SequenceHeader = 0 DataHeaderLength = 5 NoTimestampExtension = 0 AACAudioFormat = 10 PCMAudioFormat = 0 ) const ( sizeofFLVTagHeader = 11 sizeofPrevTagSize = 4 ) const version = 0x01 // FLV is big-endian. var order = binary.BigEndian // orderPutUint24 is a binary.BigEndian method look-alike for // writing 24 bit words to a byte slice. func orderPutUint24(b []byte, v uint32) { _ = b[2] // early bounds check to guarantee safety of writes below b[0] = byte(v >> 16) b[1] = byte(v >> 8) b[2] = byte(v) } var flvHeaderCode = []byte{'F', 'L', 'V', version} type Header struct { HasAudio bool HasVideo bool } func (h *Header) Bytes() []byte { const headerLength = 9 b := [headerLength]byte{ 0: 'F', 1: 'L', 2: 'V', 3: version, 4: btb(h.HasAudio)<<2 | btb(h.HasVideo), 8: headerLength, // order.PutUint32(b[5:9], headerLength) } return b[:] } type VideoTag struct { TagType uint8 DataSize uint32 Timestamp uint32 TimestampExtended uint8 FrameType uint8 Codec uint8 PacketType uint8 CompositionTime uint32 Data []byte PrevTagSize uint32 } func (t *VideoTag) Bytes() []byte { // FIXME(kortschak): This should probably be an encoding.BinaryMarshaler. // This will allow handling of invalid field values. b := make([]byte, t.DataSize+sizeofFLVTagHeader+sizeofPrevTagSize) b[0] = t.TagType orderPutUint24(b[1:4], t.DataSize) orderPutUint24(b[4:7], t.Timestamp) b[7] = t.TimestampExtended b[11] = t.FrameType<<4 | t.Codec b[12] = t.PacketType orderPutUint24(b[13:16], t.CompositionTime) copy(b[16:], t.Data) order.PutUint32(b[len(b)-4:], t.PrevTagSize) return b } type AudioTag struct { TagType uint8 DataSize uint32 Timestamp uint32 TimestampExtended uint8 SoundFormat uint8 SoundRate uint8 SoundSize bool SoundType bool Data []byte PrevTagSize uint32 } func (t *AudioTag) Bytes() []byte { // FIXME(kortschak): This should probably be an encoding.BinaryMarshaler. // This will allow handling of invalid field values. b := make([]byte, t.DataSize+sizeofFLVTagHeader+sizeofPrevTagSize) b[0] = t.TagType orderPutUint24(b[1:4], t.DataSize) orderPutUint24(b[4:7], t.Timestamp) b[7] = t.TimestampExtended b[11] = t.SoundFormat<<4 | t.SoundRate<<2 | btb(t.SoundSize)<<1 | btb(t.SoundType) copy(b[12:], t.Data) order.PutUint32(b[len(b)-4:], t.PrevTagSize) return b } func btb(b bool) byte { if b { return 1 } return 0 }