2018-02-13 11:39:26 +03:00
|
|
|
/*
|
|
|
|
NAME
|
2018-02-28 16:46:59 +03:00
|
|
|
FLVGenerator.go
|
2018-02-13 11:39:26 +03:00
|
|
|
|
|
|
|
DESCRIPTION
|
|
|
|
See Readme.md
|
|
|
|
|
|
|
|
AUTHOR
|
2018-02-28 16:46:59 +03:00
|
|
|
Saxon Nelson-Milton <saxon@ausocean.org>
|
2018-02-13 11:39:26 +03:00
|
|
|
|
|
|
|
LICENSE
|
2018-02-28 16:46:59 +03:00
|
|
|
FLVGenerator.go is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)
|
2018-02-13 11:39:26 +03:00
|
|
|
|
|
|
|
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 [GNU licenses](http://www.gnu.org/licenses).
|
|
|
|
*/
|
2018-02-10 16:25:55 +03:00
|
|
|
package generator
|
2018-02-10 09:59:56 +03:00
|
|
|
|
2018-02-12 10:58:29 +03:00
|
|
|
import (
|
2018-03-13 12:29:15 +03:00
|
|
|
"time"
|
2018-05-30 10:23:57 +03:00
|
|
|
|
|
|
|
"bitbucket.org/ausocean/av/flv"
|
2018-02-12 10:58:29 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
interFrameCode = 1
|
|
|
|
keyFrameCode = 5
|
|
|
|
sequenceCode = 6
|
2018-02-12 10:58:29 +03:00
|
|
|
)
|
|
|
|
|
2018-02-28 16:46:59 +03:00
|
|
|
// Data representing silent audio (required for youtube)
|
|
|
|
var dummyAudioTag1Data = []byte{0x00, 0x12, 0x08, 0x56, 0xe5, 0x00}
|
|
|
|
var dummyAudioTag2Data = []byte{0x01, 0xdc, 0x00, 0x4c, 0x61, 0x76, 0x63, 0x35,
|
|
|
|
0x38, 0x2e, 0x36, 0x2e, 0x31, 0x30, 0x32, 0x00, 0x02, 0x30, 0x40, 0x0e}
|
|
|
|
|
|
|
|
// 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-05-06 11:38:45 +03:00
|
|
|
fps uint
|
|
|
|
inputChan chan []byte
|
|
|
|
outputChan chan []byte
|
|
|
|
audioFlag bool
|
|
|
|
videoFlag bool
|
|
|
|
lastTagSize int
|
|
|
|
header flv.Header
|
|
|
|
startTime time.Time
|
|
|
|
firstTag bool
|
2018-05-06 10:18:17 +03:00
|
|
|
isGenerating bool
|
2018-02-10 16:25:55 +03:00
|
|
|
}
|
|
|
|
|
2018-02-28 16:46:59 +03:00
|
|
|
// GetInputChan returns the input channel to the generator. This is where the
|
|
|
|
// raw data frames are entered into the generator
|
|
|
|
func (g *flvGenerator) GetInputChan() chan []byte {
|
2018-02-10 16:25:55 +03:00
|
|
|
return g.inputChan
|
|
|
|
}
|
|
|
|
|
2018-02-28 16:46:59 +03:00
|
|
|
// GetOutputChan retuns the output chan of the generator - this is where the
|
|
|
|
// flv packets (more specifically tags) are outputted.
|
|
|
|
func (g *flvGenerator) GetOutputChan() 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-02-12 10:58:29 +03:00
|
|
|
func NewFlvGenerator(audio bool, video bool, fps uint) (g *flvGenerator) {
|
2018-02-10 16:25:55 +03:00
|
|
|
g = new(flvGenerator)
|
2018-02-12 10:58:29 +03:00
|
|
|
g.fps = fps
|
|
|
|
g.audioFlag = audio
|
|
|
|
g.videoFlag = video
|
2018-02-11 09:06:59 +03:00
|
|
|
g.lastTagSize = 0
|
2018-02-12 10:58:29 +03:00
|
|
|
g.inputChan = make(chan []byte, inputChanLength)
|
|
|
|
g.outputChan = make(chan []byte, outputChanLength)
|
2018-03-13 12:29:15 +03:00
|
|
|
g.firstTag = true
|
2018-05-06 10:18:17 +03:00
|
|
|
g.isGenerating = false
|
2018-02-10 16:25:55 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-03-18 03:00:05 +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 := flv.Header{
|
2018-02-12 10:58:29 +03:00
|
|
|
AudioFlag: g.audioFlag,
|
|
|
|
VideoFlag: g.videoFlag,
|
2018-02-28 16:46:59 +03:00
|
|
|
}
|
|
|
|
g.outputChan <- header.ToByteSlice()
|
2018-02-10 16:25:55 +03:00
|
|
|
}
|
|
|
|
|
2018-03-18 03:00:05 +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
|
|
|
|
timestamp = 0
|
2018-03-14 04:18:03 +03:00
|
|
|
return
|
2018-03-13 12:29:15 +03:00
|
|
|
}
|
2018-03-14 04:18:03 +03:00
|
|
|
timestamp = uint32(time.Now().Sub(g.startTime).Seconds() * float64(1000))
|
2018-02-28 16:46:59 +03:00
|
|
|
return
|
2018-02-10 16:25:55 +03:00
|
|
|
}
|
|
|
|
|
2018-03-18 03:00:05 +03:00
|
|
|
// isKeyFrame returns true if the passed frame data represents that of a keyframe
|
|
|
|
// TODO: clean this up and use conts for naltype codes
|
2018-03-13 08:00:02 +03:00
|
|
|
func isKeyFrame(frame []byte) bool {
|
2018-02-16 08:46:24 +03:00
|
|
|
byteChannel := make(chan byte, len(frame))
|
|
|
|
for i := range frame {
|
|
|
|
byteChannel <- frame[i]
|
|
|
|
}
|
2018-03-14 04:18:03 +03:00
|
|
|
for len(byteChannel) >= 5 {
|
|
|
|
aByte := <-byteChannel
|
|
|
|
for i := 1; aByte == 0x00 && i != 4; i++ {
|
2018-02-16 08:46:24 +03:00
|
|
|
aByte = <-byteChannel
|
2018-03-14 04:18:03 +03:00
|
|
|
if (aByte == 0x01 && i == 2) || (aByte == 0x01 && i == 3) {
|
2018-02-16 08:46:24 +03:00
|
|
|
aByte = <-byteChannel
|
2018-03-13 08:00:02 +03:00
|
|
|
nalType := aByte & 0x1F
|
|
|
|
switch nalType {
|
|
|
|
case interFrameCode:
|
|
|
|
return false
|
|
|
|
case keyFrameCode:
|
|
|
|
return true
|
|
|
|
case 6:
|
|
|
|
return true
|
|
|
|
}
|
2018-02-16 08:46:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-03-18 03:00:05 +03:00
|
|
|
// isSequenceHeader returns true if the passed frame data represents that of a
|
|
|
|
// a sequence header.
|
|
|
|
// TODO: clean this up and use consts for the nalTypes
|
2018-02-16 08:46:24 +03:00
|
|
|
func isSequenceHeader(frame []byte) bool {
|
2018-03-13 08:00:02 +03:00
|
|
|
byteChannel := make(chan byte, len(frame))
|
|
|
|
for i := range frame {
|
|
|
|
byteChannel <- frame[i]
|
|
|
|
}
|
2018-03-14 04:18:03 +03:00
|
|
|
for len(byteChannel) >= 5 {
|
|
|
|
aByte := <-byteChannel
|
|
|
|
for i := 1; aByte == 0x00 && i != 4; i++ {
|
2018-03-13 08:00:02 +03:00
|
|
|
aByte = <-byteChannel
|
2018-03-14 04:18:03 +03:00
|
|
|
if (aByte == 0x01 && i == 2) || (aByte == 0x01 && i == 3) {
|
2018-03-13 08:00:02 +03:00
|
|
|
aByte = <-byteChannel
|
|
|
|
nalType := aByte & 0x1F
|
|
|
|
switch nalType {
|
|
|
|
case 1:
|
|
|
|
return false
|
|
|
|
case 5:
|
|
|
|
return false
|
|
|
|
case 6:
|
|
|
|
return true
|
|
|
|
case 7:
|
|
|
|
return true
|
|
|
|
case 8:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-02-16 08:46:24 +03:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
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.
|
2018-02-12 10:58:29 +03:00
|
|
|
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 {
|
|
|
|
case videoFrame := <-g.inputChan:
|
2018-02-16 08:46:24 +03:00
|
|
|
if isKeyFrame(videoFrame) {
|
|
|
|
frameType = flv.KeyFrameType
|
|
|
|
} else {
|
|
|
|
frameType = flv.InterFrameType
|
|
|
|
}
|
2018-02-28 16:46:59 +03:00
|
|
|
if isSequenceHeader(videoFrame) {
|
2018-02-16 08:46:24 +03:00
|
|
|
packetType = flv.SequenceHeader
|
|
|
|
} else {
|
|
|
|
packetType = flv.AVCNALU
|
|
|
|
}
|
2018-02-15 10:02:04 +03:00
|
|
|
timeStamp := g.getNextTimestamp()
|
2018-02-28 16:46:59 +03:00
|
|
|
// Do we have video to send off ?
|
2018-02-19 08:06:13 +03:00
|
|
|
if g.videoFlag {
|
2018-03-14 04:18:03 +03:00
|
|
|
tag := flv.VideoTag{
|
2018-02-28 16:46:59 +03:00
|
|
|
TagType: uint8(flv.VideoTagType),
|
|
|
|
DataSize: uint32(len(videoFrame)) + flv.DataHeaderLength,
|
|
|
|
Timestamp: timeStamp,
|
2018-02-19 08:06:13 +03:00
|
|
|
TimestampExtended: flv.NoTimestampExtension,
|
2018-02-28 16:46:59 +03:00
|
|
|
FrameType: frameType,
|
|
|
|
Codec: flv.H264,
|
|
|
|
PacketType: packetType,
|
|
|
|
CompositionTime: 0,
|
|
|
|
Data: videoFrame,
|
|
|
|
PrevTagSize: uint32(videoHeaderSize + len(videoFrame)),
|
2018-03-13 07:33:31 +03:00
|
|
|
}
|
|
|
|
g.outputChan <- tag.ToByteSlice()
|
2018-02-15 10:02:04 +03:00
|
|
|
}
|
2018-02-28 16:46:59 +03:00
|
|
|
// Do we even have some audio to send off ?
|
2018-02-19 08:06:13 +03:00
|
|
|
if g.audioFlag {
|
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
|
2018-03-13 07:33:31 +03:00
|
|
|
tag := flv.AudioTag{
|
2018-02-28 16:46:59 +03:00
|
|
|
TagType: uint8(flv.AudioTagType),
|
|
|
|
DataSize: 7,
|
|
|
|
Timestamp: timeStamp,
|
2018-02-19 08:06:13 +03:00
|
|
|
TimestampExtended: flv.NoTimestampExtension,
|
2018-02-28 16:46:59 +03:00
|
|
|
SoundFormat: flv.AACAudioFormat,
|
|
|
|
SoundRate: 3,
|
|
|
|
SoundSize: true,
|
|
|
|
SoundType: true,
|
|
|
|
Data: dummyAudioTag1Data,
|
|
|
|
PrevTagSize: uint32(audioSize),
|
2018-03-13 07:33:31 +03:00
|
|
|
}
|
|
|
|
g.outputChan <- tag.ToByteSlice()
|
2018-02-28 16:46:59 +03:00
|
|
|
|
2018-03-13 07:33:31 +03:00
|
|
|
tag = flv.AudioTag{
|
2018-02-28 16:46:59 +03:00
|
|
|
TagType: uint8(flv.AudioTagType),
|
|
|
|
DataSize: 21,
|
|
|
|
Timestamp: timeStamp,
|
2018-02-19 08:06:13 +03:00
|
|
|
TimestampExtended: flv.NoTimestampExtension,
|
2018-02-28 16:46:59 +03:00
|
|
|
SoundFormat: flv.AACAudioFormat,
|
|
|
|
SoundRate: 3,
|
|
|
|
SoundSize: true,
|
|
|
|
SoundType: true,
|
|
|
|
Data: dummyAudioTag2Data,
|
|
|
|
PrevTagSize: uint32(22),
|
2018-03-13 07:33:31 +03:00
|
|
|
}
|
|
|
|
g.outputChan <- tag.ToByteSlice()
|
2018-02-19 08:06:13 +03:00
|
|
|
}
|
2018-05-06 11:22:59 +03:00
|
|
|
default:
|
2018-05-06 11:38:45 +03:00
|
|
|
time.Sleep(time.Duration(5) * time.Millisecond)
|
2018-02-28 16:46:59 +03:00
|
|
|
}
|
|
|
|
}
|
2018-02-10 09:59:56 +03:00
|
|
|
}
|