av/encoding/flv/encoder.go

286 lines
7.0 KiB
Go
Raw Normal View History

/*
NAME
flv_generator.go
DESCRIPTION
See Readme.md
AUTHOR
Dan Kortschak <dan@ausocean.org>
2018-02-28 16:46:59 +03:00
Saxon Nelson-Milton <saxon@ausocean.org>
LICENSE
flv_generator.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.
*/
package flv
import "time"
const (
2018-05-05 18:52:36 +03:00
inputChanLength = 500
outputChanLength = 500
2018-02-28 16:46:59 +03:00
audioSize = 18
videoHeaderSize = 16
)
2018-02-28 16:46:59 +03:00
// Data representing silent audio (required for youtube)
2018-07-07 08:57:59 +03:00
var (
dummyAudioTag1Data = []byte{
0x00, 0x12, 0x08, 0x56, 0xe5, 0x00,
}
dummyAudioTag2Data = []byte{
0x01, 0xdc, 0x00, 0x4c, 0x61, 0x76, 0x63, 0x35, 0x38,
0x2e, 0x36, 0x2e, 0x31, 0x30, 0x32, 0x00, 0x02, 0x30,
0x40, 0x0e,
}
)
2018-02-28 16:46:59 +03:00
// flvGenerator provides properties required for the generation of flv video
// from raw video data
2018-02-10 16:25:55 +03:00
type flvGenerator struct {
2018-07-07 08:57:59 +03:00
fps int
2018-05-06 11:38:45 +03:00
inputChan chan []byte
outputChan chan []byte
2018-07-07 08:57:59 +03:00
audio bool
video bool
2018-05-06 11:38:45 +03:00
lastTagSize int
header Header
2018-05-06 11:38:45 +03:00
startTime time.Time
firstTag bool
2018-05-06 10:18:17 +03:00
isGenerating bool
2018-02-10 16:25:55 +03:00
}
// InputChan returns the input channel to the generator. This is where the
2018-02-28 16:46:59 +03:00
// raw data frames are entered into the generator
func (g *flvGenerator) InputChan() chan []byte {
2018-02-10 16:25:55 +03:00
return g.inputChan
}
// OutputChan retuns the output chan of the generator - this is where the
2018-02-28 16:46:59 +03:00
// flv packets (more specifically tags) are outputted.
func (g *flvGenerator) OutputChan() <-chan []byte {
2018-02-10 16:25:55 +03:00
return g.outputChan
}
2018-02-28 16:46:59 +03:00
// NewFlvGenerator retuns an instance of the flvGenerator struct
2018-07-07 08:57:59 +03:00
func NewFlvGenerator(audio, video bool, fps int) *flvGenerator {
return &flvGenerator{
fps: fps,
audio: audio,
video: video,
inputChan: make(chan []byte, inputChanLength),
outputChan: make(chan []byte, outputChanLength),
firstTag: true,
}
2018-02-10 16:25:55 +03:00
}
// Start begins the generation routine - i.e. if raw data is given to the input
2018-02-28 16:46:59 +03:00
// channel flv tags will be produced and available from the output channel.
func (g *flvGenerator) Start() {
2018-05-06 11:38:45 +03:00
g.isGenerating = true
2018-02-10 16:25:55 +03:00
go g.generate()
}
2018-05-06 10:18:17 +03:00
func (g *flvGenerator) Stop() {
g.isGenerating = false
}
2018-02-28 16:46:59 +03:00
// GenHeader generates the flv header and sends it down the output chan for use
// This will generally be called once at the start of file writing/transmission.
func (g *flvGenerator) GenHeader() {
header := Header{
2018-07-07 08:57:59 +03:00
HasAudio: g.audio,
HasVideo: g.video,
2018-02-28 16:46:59 +03:00
}
2018-07-07 08:57:59 +03:00
g.outputChan <- header.Bytes()
2018-02-10 16:25:55 +03:00
}
// getNextTimestamp generates and returns the next timestamp based on current time
2018-02-28 16:46:59 +03:00
func (g *flvGenerator) getNextTimestamp() (timestamp uint32) {
2018-03-13 12:29:15 +03:00
if g.firstTag {
g.startTime = time.Now()
2018-03-14 03:49:21 +03:00
g.firstTag = false
2018-07-07 08:57:59 +03:00
return 0
2018-03-13 12:29:15 +03:00
}
2018-07-07 08:57:59 +03:00
return uint32(time.Now().Sub(g.startTime).Seconds() * float64(1000))
2018-02-10 16:25:55 +03:00
}
2018-07-07 08:57:59 +03:00
// http://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-H.264-200305-S!!PDF-E&type=items
// Table 7-1 NAL unit type codes
const (
nonIdrPic = 1
idrPic = 5
suppEnhInf = 6
seqParamSet = 7
paramSet = 8
)
// isKeyFrame returns true if the passed frame data represents that of a keyframe
2018-07-07 08:57:59 +03:00
// FIXME(kortschak): Clarify and document the logic of this functions.
2018-03-13 08:00:02 +03:00
func isKeyFrame(frame []byte) bool {
2018-07-07 08:57:59 +03:00
sc := frameScanner{buf: frame}
for {
b, ok := sc.readByte()
if !ok {
return false
}
for i := 1; b == 0x00 && i < 4; i++ {
b, ok = sc.readByte()
if !ok {
return false
}
if b != 0x01 || (i != 3 && i != 2) {
continue
}
b, ok = sc.readByte()
if !ok {
return false
}
switch nalTyp := b & 0x1f; nalTyp {
case idrPic, suppEnhInf:
return true
case nonIdrPic:
return false
2018-02-16 08:46:24 +03:00
}
}
}
return false
}
// isSequenceHeader returns true if the passed frame data represents that of a
// a sequence header.
2018-07-07 08:57:59 +03:00
// FIXME(kortschak): Clarify and document the logic of this functions.
2018-02-16 08:46:24 +03:00
func isSequenceHeader(frame []byte) bool {
2018-07-07 08:57:59 +03:00
sc := frameScanner{buf: frame}
for {
b, ok := sc.readByte()
if !ok {
return false
}
for i := 1; b == 0x00 && i != 4; i++ {
b, ok = sc.readByte()
if !ok {
return false
}
if b != 0x01 || (i != 2 && i != 3) {
continue
}
b, ok = sc.readByte()
if !ok {
return false
}
switch nalTyp := b & 0x1f; nalTyp {
case suppEnhInf, seqParamSet, paramSet:
return true
case nonIdrPic, idrPic:
return false
2018-03-13 08:00:02 +03:00
}
}
2018-02-16 08:46:24 +03:00
}
2018-07-07 08:57:59 +03:00
}
type frameScanner struct {
off int
buf []byte
}
func (s *frameScanner) readByte() (b byte, ok bool) {
if s.off >= len(s.buf) {
return 0, false
}
b = s.buf[s.off]
s.off++
return b, true
2018-02-16 08:46:24 +03:00
}
2018-02-28 16:46:59 +03:00
// generate takes in raw video data from the input chan and packetises it into
// flv tags, which are then passed to the output channel.
func (g *flvGenerator) generate() {
2018-02-28 16:46:59 +03:00
g.GenHeader()
2018-02-28 17:50:14 +03:00
var frameType byte
var packetType byte
2018-05-06 10:18:17 +03:00
for g.isGenerating {
2018-02-28 16:46:59 +03:00
select {
2018-07-07 08:57:59 +03:00
default:
time.Sleep(time.Duration(5) * time.Millisecond)
case frame := <-g.inputChan:
timeStamp := g.getNextTimestamp()
2018-07-07 08:57:59 +03:00
// Do we have video to send off?
if g.video {
if isKeyFrame(frame) {
frameType = KeyFrameType
2018-07-07 08:57:59 +03:00
} else {
frameType = InterFrameType
2018-07-07 08:57:59 +03:00
}
if isSequenceHeader(frame) {
packetType = SequenceHeader
2018-07-07 08:57:59 +03:00
} else {
packetType = AVCNALU
2018-07-07 08:57:59 +03:00
}
tag := VideoTag{
TagType: uint8(VideoTagType),
DataSize: uint32(len(frame)) + DataHeaderLength,
2018-02-28 16:46:59 +03:00
Timestamp: timeStamp,
TimestampExtended: NoTimestampExtension,
2018-02-28 16:46:59 +03:00
FrameType: frameType,
Codec: H264,
2018-02-28 16:46:59 +03:00
PacketType: packetType,
CompositionTime: 0,
2018-07-07 08:57:59 +03:00
Data: frame,
PrevTagSize: uint32(videoHeaderSize + len(frame)),
2018-03-13 07:33:31 +03:00
}
2018-07-07 08:57:59 +03:00
g.outputChan <- tag.Bytes()
}
2018-02-28 16:46:59 +03:00
// Do we even have some audio to send off ?
2018-07-07 08:57:59 +03:00
if g.audio {
2018-02-28 16:46:59 +03:00
// Not sure why but we need two audio tags for dummy silent audio
2018-04-15 13:53:53 +03:00
// TODO: create constants or SoundSize and SoundType parameters
tag := AudioTag{
TagType: uint8(AudioTagType),
2018-02-28 16:46:59 +03:00
DataSize: 7,
Timestamp: timeStamp,
TimestampExtended: NoTimestampExtension,
SoundFormat: AACAudioFormat,
2018-02-28 16:46:59 +03:00
SoundRate: 3,
SoundSize: true,
SoundType: true,
Data: dummyAudioTag1Data,
PrevTagSize: uint32(audioSize),
2018-03-13 07:33:31 +03:00
}
2018-07-07 08:57:59 +03:00
g.outputChan <- tag.Bytes()
2018-02-28 16:46:59 +03:00
tag = AudioTag{
TagType: uint8(AudioTagType),
2018-02-28 16:46:59 +03:00
DataSize: 21,
Timestamp: timeStamp,
TimestampExtended: NoTimestampExtension,
SoundFormat: AACAudioFormat,
2018-02-28 16:46:59 +03:00
SoundRate: 3,
SoundSize: true,
SoundType: true,
Data: dummyAudioTag2Data,
PrevTagSize: uint32(22),
2018-03-13 07:33:31 +03:00
}
2018-07-07 08:57:59 +03:00
g.outputChan <- tag.Bytes()
}
2018-02-28 16:46:59 +03:00
}
}
}