joy4/format/flv/flv.go
2016-07-02 06:01:34 +08:00

297 lines
6.3 KiB
Go

package flv
import (
"time"
"fmt"
"github.com/nareix/joy4/av"
"github.com/nareix/joy4/av/avutil"
"github.com/nareix/joy4/codec/h264parser"
"github.com/nareix/joy4/codec/aacparser"
"github.com/nareix/pio"
"github.com/nareix/joy4/format/flv/flvio"
"io"
"bufio"
)
type Muxer struct {
pw *pio.Writer
streams []av.CodecData
}
func NewMuxer(w io.Writer) *Muxer {
self := &Muxer{}
self.pw = pio.NewWriter(w)
return self
}
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
}
}
if err = flvio.WriteFileHeader(self.pw, flags); err != nil {
return
}
for _, stream := range streams {
var _tag flvio.Tag
switch stream.Type() {
case av.H264:
h264 := stream.(h264parser.CodecData)
tag := &flvio.Videodata{
AVCPacketType: flvio.AVC_SEQHDR,
CodecID: flvio.VIDEO_H264,
Data: h264.AVCDecoderConfRecordBytes(),
}
_tag = tag
case av.AAC:
aac := stream.(aacparser.CodecData)
tag := flvio.MakeAACAudiodata(aac, aac.MPEG4AudioConfigBytes())
tag.AACPacketType = flvio.AAC_SEQHDR
_tag = &tag
default:
err = fmt.Errorf("flv: unspported codecType=%v", stream.Type())
return
}
if err = flvio.WriteTag(self.pw, _tag, 0); err != nil {
return
}
}
self.streams = streams
return
}
func (self *Muxer) WritePacket(pkt av.Packet) (err error) {
stream := self.streams[pkt.Idx]
var _tag flvio.Tag
switch stream.Type() {
case av.H264:
if typ := h264parser.CheckNALUsType(pkt.Data); typ != h264parser.NALU_RAW {
err = fmt.Errorf("flv: h264 nalu format=%d invalid", typ)
return
}
var b [4]byte
pio.PutU32BE(b[:], uint32(len(pkt.Data)))
tag := &flvio.Videodata{
AVCPacketType: flvio.AVC_NALU,
CodecID: flvio.VIDEO_H264,
Datav: [][]byte{b[:], pkt.Data},
CompositionTime: timeToTs(pkt.CompositionTime),
}
if pkt.IsKeyFrame {
tag.FrameType = flvio.FRAME_KEY
} else {
tag.FrameType = flvio.FRAME_INTER
}
_tag = tag
case av.AAC:
tag := flvio.MakeAACAudiodata(stream.(av.AudioCodecData), pkt.Data)
_tag = &tag
}
if err = flvio.WriteTag(self.pw, _tag, timeToTs(pkt.Time)); err != nil {
return
}
return
}
func (self *Muxer) WriteTrailer() (err error) {
return
}
type flvStream struct {
av.CodecData
lastts int32
tm time.Duration
}
type Demuxer struct {
streams []*flvStream
videostreamidx int
audiostreamidx int
pr *pio.Reader
probepkts []flvio.Tag
probed bool
}
func NewDemuxer(r io.Reader) *Demuxer {
return &Demuxer{
pr: pio.NewReader(bufio.NewReaderSize(r, 128)),
}
}
func (self *Demuxer) probe() (err error) {
if self.probed {
return
}
var flags, got uint8
if flags, err = flvio.ReadFileHeader(self.pr); err != nil {
return
}
flags &= flvio.FILE_HAS_AUDIO|flvio.FILE_HAS_VIDEO
for {
var _tag flvio.Tag
if _tag, _, err = flvio.ReadTag(self.pr); err != nil {
return
}
switch tag := _tag.(type) {
case *flvio.Videodata:
switch tag.CodecID {
case flvio.VIDEO_H264:
switch tag.AVCPacketType {
case flvio.AVC_SEQHDR:
var codec h264parser.CodecData
if codec, 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, &flvStream{CodecData: codec})
got |= flvio.FILE_HAS_VIDEO
case flvio.AVC_NALU:
self.probepkts = append(self.probepkts, tag)
}
default:
err = fmt.Errorf("flv: unspported video CodecID=%d", tag.CodecID)
return
}
case *flvio.Audiodata:
switch tag.SoundFormat {
case flvio.SOUND_AAC:
switch tag.AACPacketType {
case flvio.AAC_SEQHDR:
var codec aacparser.CodecData
if codec, 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, &flvStream{CodecData: codec})
got |= flvio.FILE_HAS_AUDIO
case flvio.AAC_RAW:
self.probepkts = append(self.probepkts, tag)
}
default:
err = fmt.Errorf("flv: unspported audio SoundFormat=%d", tag.SoundFormat)
return
}
}
if got == flags {
break
}
}
self.probed = true
return
}
func (self *Demuxer) Streams() (streams []av.CodecData, err error) {
if err = self.probe(); err != nil {
return
}
for _, stream := range self.streams {
streams = append(streams, stream.CodecData)
}
return
}
func tsToTime(ts int32) time.Duration {
return time.Millisecond*time.Duration(ts)
}
func timeToTs(tm time.Duration) int32 {
return int32(tm / time.Millisecond)
}
func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) {
if err = self.probe(); err != nil {
return
}
var timestamp int32
var stream *flvStream
loop: for {
var _tag flvio.Tag
if len(self.probepkts) > 0 {
_tag = self.probepkts[0]
self.probepkts = self.probepkts[1:]
} else {
if _tag, timestamp, err = flvio.ReadTag(self.pr); err != nil {
return
}
}
switch tag := _tag.(type) {
case *flvio.Videodata:
if tag.AVCPacketType == flvio.AVC_NALU {
stream = self.streams[self.videostreamidx]
pkt.Idx = int8(self.videostreamidx)
pkt.CompositionTime = tsToTime(tag.CompositionTime)
pkt.IsKeyFrame = tag.FrameType == flvio.FRAME_KEY
var ok bool
if pkt.Data, ok = h264parser.FindDataNALUInAVCCNALUs(tag.Data); !ok {
err = fmt.Errorf("flv: input h264 format invalid")
return
}
break loop
}
case *flvio.Audiodata:
if tag.AACPacketType == flvio.AAC_RAW {
stream = self.streams[self.audiostreamidx]
pkt.Idx = int8(self.audiostreamidx)
pkt.Data = tag.Data
break loop
}
}
}
if stream.lastts == 0 {
stream.tm = tsToTime(timestamp)
} else {
diff := timestamp - stream.lastts
stream.tm += tsToTime(diff)
}
stream.lastts = timestamp
pkt.Time = stream.tm
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)
}
}