mirror of
https://github.com/cnotch/ipchub.git
synced 2025-09-26 19:41:18 +08:00
refactoring flv muxer
This commit is contained in:
94
av/format/flv/aac_packetizer.go
Normal file
94
av/format/flv/aac_packetizer.go
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2019,CAOHONGJU All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package flv
|
||||
|
||||
import "github.com/cnotch/ipchub/av/codec"
|
||||
|
||||
type aacPacketizer struct {
|
||||
meta *codec.AudioMeta
|
||||
dataTemplate *AudioData
|
||||
tagWriter TagWriter
|
||||
spsMuxed bool
|
||||
}
|
||||
|
||||
func NewAacPacketizer(meta *codec.AudioMeta, tagWriter TagWriter) Packetizer {
|
||||
ap := &aacPacketizer{
|
||||
meta: meta,
|
||||
tagWriter: tagWriter,
|
||||
}
|
||||
ap.prepareTemplate()
|
||||
return ap
|
||||
}
|
||||
|
||||
func (ap *aacPacketizer) prepareTemplate() {
|
||||
audioData := &AudioData{
|
||||
SoundFormat: SoundFormatAAC,
|
||||
AACPacketType: AACPacketTypeRawData,
|
||||
Body: nil,
|
||||
}
|
||||
|
||||
switch ap.meta.SampleRate {
|
||||
case 5512:
|
||||
audioData.SoundRate = SoundRate5512
|
||||
case 11025:
|
||||
audioData.SoundRate = SoundRate11025
|
||||
case 22050:
|
||||
audioData.SoundRate = SoundRate22050
|
||||
case 44100:
|
||||
audioData.SoundRate = SoundRate44100
|
||||
default:
|
||||
audioData.SoundRate = SoundRate44100
|
||||
}
|
||||
|
||||
if ap.meta.SampleSize == 8 {
|
||||
audioData.SoundSize = SoundeSize8bit
|
||||
} else {
|
||||
audioData.SoundSize = SoundeSize16bit
|
||||
}
|
||||
|
||||
if ap.meta.Channels > 1 {
|
||||
audioData.SoundType = SoundTypeStereo
|
||||
} else {
|
||||
audioData.SoundType = SoundTypeMono
|
||||
}
|
||||
|
||||
ap.dataTemplate = audioData
|
||||
}
|
||||
|
||||
func (ap *aacPacketizer) PacketizeSequenceHeader() error {
|
||||
if ap.spsMuxed {
|
||||
return nil
|
||||
}
|
||||
|
||||
ap.spsMuxed = true
|
||||
audioData := *ap.dataTemplate
|
||||
audioData.AACPacketType = AACPacketTypeSequenceHeader
|
||||
audioData.Body = ap.meta.Sps
|
||||
data, _ := audioData.Marshal()
|
||||
|
||||
tag := &Tag{
|
||||
TagType: TagTypeAudio,
|
||||
DataSize: uint32(len(data)),
|
||||
Timestamp: 0,
|
||||
StreamID: 0,
|
||||
Data: data,
|
||||
}
|
||||
return ap.tagWriter.WriteFlvTag(tag)
|
||||
}
|
||||
|
||||
func (ap *aacPacketizer) Packetize(basePts int64, frame *codec.Frame) error {
|
||||
audioData := *ap.dataTemplate
|
||||
audioData.Body = frame.Payload
|
||||
data, _ := audioData.Marshal()
|
||||
|
||||
tag := &Tag{
|
||||
TagType: TagTypeAudio,
|
||||
DataSize: uint32(len(data)),
|
||||
Timestamp: uint32(frame.AbsTimestamp-basePts) + ptsDelay,
|
||||
StreamID: 0,
|
||||
Data: data,
|
||||
}
|
||||
return ap.tagWriter.WriteFlvTag(tag)
|
||||
}
|
@@ -40,7 +40,7 @@ func TestFlvWriter(t *testing.T) {
|
||||
var audio codec.AudioMeta
|
||||
sdp.ParseMetadata(string(sdpraw), &video, &audio)
|
||||
writer, err := NewWriter(out, 5)
|
||||
flvMuxer := NewMuxerAvcAac(video, audio, writer, xlog.L())
|
||||
flvMuxer,_ := NewMuxer(&video, &audio, writer, xlog.L())
|
||||
|
||||
rtpDemuxer,_ := rtp.NewDemuxer(&video,&audio,flvMuxer, xlog.L())
|
||||
channels := []int{int(rtp.ChannelVideo), int(rtp.ChannelVideoControl), int(rtp.ChannelAudio), int(rtp.ChannelAudioControl)}
|
||||
|
111
av/format/flv/h264_packetizer.go
Normal file
111
av/format/flv/h264_packetizer.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2019,CAOHONGJU All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package flv
|
||||
|
||||
import (
|
||||
"github.com/cnotch/ipchub/av/codec"
|
||||
"github.com/cnotch/ipchub/av/codec/h264"
|
||||
)
|
||||
|
||||
type h264Packetizer struct {
|
||||
meta *codec.VideoMeta
|
||||
tagWriter TagWriter
|
||||
spsMuxed bool
|
||||
nextDts float64
|
||||
dtsStep float64
|
||||
}
|
||||
|
||||
func NewH264Packetizer(meta *codec.VideoMeta, tagWriter TagWriter) Packetizer {
|
||||
h264p := &h264Packetizer{
|
||||
meta: meta,
|
||||
tagWriter: tagWriter,
|
||||
}
|
||||
|
||||
if meta.FrameRate > 0 {
|
||||
h264p.dtsStep = 1000.0 / meta.FrameRate
|
||||
}
|
||||
return h264p
|
||||
}
|
||||
|
||||
func (h264p *h264Packetizer) PacketizeSequenceHeader() error {
|
||||
if h264p.spsMuxed {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !h264.MetadataIsReady(h264p.meta) {
|
||||
// not enough
|
||||
return nil
|
||||
}
|
||||
|
||||
h264p.spsMuxed = true
|
||||
|
||||
if h264p.meta.FixedFrameRate {
|
||||
h264p.dtsStep = 1000.0 / h264p.meta.FrameRate
|
||||
} else { // TODO:
|
||||
h264p.dtsStep = 1000.0 / 30
|
||||
}
|
||||
|
||||
record := NewAVCDecoderConfigurationRecord(h264p.meta.Sps, h264p.meta.Pps)
|
||||
body, _ := record.Marshal()
|
||||
|
||||
videoData := &VideoData{
|
||||
FrameType: FrameTypeKeyFrame,
|
||||
CodecID: CodecIDAVC,
|
||||
AVCPacketType: AVCPacketTypeSequenceHeader,
|
||||
CompositionTime: 0,
|
||||
Body: body,
|
||||
}
|
||||
data, _ := videoData.Marshal()
|
||||
|
||||
tag := &Tag{
|
||||
TagType: TagTypeVideo,
|
||||
DataSize: uint32(len(data)),
|
||||
Timestamp: 0,
|
||||
StreamID: 0,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
return h264p.tagWriter.WriteFlvTag(tag)
|
||||
}
|
||||
|
||||
func (h264p *h264Packetizer) Packetize(basePts int64, frame *codec.Frame) error {
|
||||
if frame.Payload[0]&0x1F == h264.NalSps {
|
||||
return h264p.PacketizeSequenceHeader()
|
||||
}
|
||||
|
||||
if frame.Payload[0]&0x1F == h264.NalPps {
|
||||
return h264p.PacketizeSequenceHeader()
|
||||
}
|
||||
|
||||
dts := int64(h264p.nextDts)
|
||||
h264p.nextDts += h264p.dtsStep
|
||||
pts := frame.AbsTimestamp - basePts + ptsDelay
|
||||
if dts > pts {
|
||||
pts = dts
|
||||
}
|
||||
|
||||
videoData := &VideoData{
|
||||
FrameType: FrameTypeInterFrame,
|
||||
CodecID: CodecIDAVC,
|
||||
AVCPacketType: AVCPacketTypeNALU,
|
||||
CompositionTime: uint32(pts - dts),
|
||||
Body: frame.Payload,
|
||||
}
|
||||
|
||||
if frame.Payload[0]&0x1F == h264.NalIdrSlice {
|
||||
videoData.FrameType = FrameTypeKeyFrame
|
||||
}
|
||||
data, _ := videoData.Marshal()
|
||||
|
||||
tag := &Tag{
|
||||
TagType: TagTypeVideo,
|
||||
DataSize: uint32(len(data)),
|
||||
Timestamp: uint32(dts),
|
||||
StreamID: 0,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
return h264p.tagWriter.WriteFlvTag(tag)
|
||||
}
|
222
av/format/flv/muxer.go
Normal file
222
av/format/flv/muxer.go
Normal file
@@ -0,0 +1,222 @@
|
||||
// Copyright (c) 2019,CAOHONGJU All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package flv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/cnotch/ipchub/av/codec"
|
||||
"github.com/cnotch/ipchub/av/format/amf"
|
||||
"github.com/cnotch/queue"
|
||||
"github.com/cnotch/xlog"
|
||||
)
|
||||
|
||||
// Packetizer 封包器
|
||||
type Packetizer interface {
|
||||
PacketizeSequenceHeader() error
|
||||
Packetize(basePts int64, frame *codec.Frame) error
|
||||
}
|
||||
|
||||
type emptyPacketizer struct{}
|
||||
|
||||
func (emptyPacketizer) PacketizeSequenceHeader() error { return nil }
|
||||
func (emptyPacketizer) Packetize(basePts int64, frame *codec.Frame) error { return nil }
|
||||
|
||||
// 网络播放时 PTS(Presentation Time Stamp)的延时
|
||||
// 影响视频 Tag 的 CTS 和音频的 DTS(Decoding Time Stamp)
|
||||
const (
|
||||
ptsDelay = 1000
|
||||
)
|
||||
|
||||
// Muxer flv muxer from av.Frame(H264[+AAC])
|
||||
type Muxer struct {
|
||||
videoMeta *codec.VideoMeta
|
||||
audioMeta *codec.AudioMeta
|
||||
vp Packetizer
|
||||
ap Packetizer
|
||||
typeFlags byte
|
||||
recvQueue *queue.SyncQueue
|
||||
tagWriter TagWriter
|
||||
closed bool
|
||||
|
||||
logger *xlog.Logger // 日志对象
|
||||
}
|
||||
|
||||
// NewMuxer .
|
||||
func NewMuxer(videoMeta *codec.VideoMeta, audioMeta *codec.AudioMeta, tagWriter TagWriter, logger *xlog.Logger) (*Muxer, error) {
|
||||
muxer := &Muxer{
|
||||
recvQueue: queue.NewSyncQueue(),
|
||||
videoMeta: videoMeta,
|
||||
audioMeta: audioMeta,
|
||||
vp: emptyPacketizer{},
|
||||
ap: emptyPacketizer{},
|
||||
typeFlags: byte(TypeFlagsVideo),
|
||||
tagWriter: tagWriter,
|
||||
closed: false,
|
||||
logger: logger,
|
||||
}
|
||||
switch videoMeta.Codec {
|
||||
case "H264":
|
||||
muxer.vp = NewH264Packetizer(videoMeta, tagWriter)
|
||||
default:
|
||||
return nil, fmt.Errorf("flv muxer unsupport video codec type:%s", videoMeta.Codec)
|
||||
}
|
||||
|
||||
if audioMeta.Codec == "AAC" {
|
||||
muxer.typeFlags |= TypeFlagsAudio
|
||||
muxer.ap = NewAacPacketizer(audioMeta, tagWriter)
|
||||
}
|
||||
|
||||
go muxer.process()
|
||||
return muxer, nil
|
||||
}
|
||||
|
||||
// WriteFrame .
|
||||
func (muxer *Muxer) WriteFrame(frame *codec.Frame) error {
|
||||
muxer.recvQueue.Push(frame)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close .
|
||||
func (muxer *Muxer) Close() error {
|
||||
if muxer.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
muxer.closed = true
|
||||
muxer.recvQueue.Signal()
|
||||
return nil
|
||||
}
|
||||
|
||||
// TypeFlags 返回 flv header 中的 TypeFlags
|
||||
func (muxer *Muxer) TypeFlags() byte {
|
||||
return muxer.typeFlags
|
||||
}
|
||||
|
||||
func (muxer *Muxer) process() {
|
||||
defer func() {
|
||||
defer func() { // 避免 handler 再 panic
|
||||
recover()
|
||||
}()
|
||||
|
||||
if r := recover(); r != nil {
|
||||
muxer.logger.Errorf("flvmuxer routine panic;r = %v \n %s", r, debug.Stack())
|
||||
}
|
||||
|
||||
// 尽早通知GC,回收内存
|
||||
muxer.recvQueue.Reset()
|
||||
}()
|
||||
|
||||
var basePts int64
|
||||
for !muxer.closed {
|
||||
f := muxer.recvQueue.Pop()
|
||||
if f == nil {
|
||||
if !muxer.closed {
|
||||
muxer.logger.Warn("flvmuxer:receive nil frame")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
frame := f.(*codec.Frame)
|
||||
if basePts == 0 {
|
||||
basePts = frame.AbsTimestamp
|
||||
muxer.muxMetadataTag()
|
||||
muxer.vp.PacketizeSequenceHeader()
|
||||
muxer.ap.PacketizeSequenceHeader()
|
||||
}
|
||||
|
||||
switch frame.MediaType {
|
||||
case codec.MediaTypeVideo:
|
||||
if err := muxer.vp.Packetize(basePts, frame); err != nil {
|
||||
muxer.logger.Errorf("flvmuxer: muxVideoTag error - %s", err.Error())
|
||||
}
|
||||
case codec.MediaTypeAudio:
|
||||
if err := muxer.ap.Packetize(basePts, frame); err != nil {
|
||||
muxer.logger.Errorf("flvmuxer: muxAudioTag error - %s", err.Error())
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (muxer *Muxer) muxMetadataTag() error {
|
||||
properties := make(amf.EcmaArray, 0, 12)
|
||||
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: "creator",
|
||||
Value: "ipchub stream media server"})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataCreationDate,
|
||||
Value: time.Now().Format(time.RFC3339)})
|
||||
|
||||
if muxer.typeFlags&TypeFlagsAudio > 0 {
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataAudioCodecID,
|
||||
Value: SoundFormatAAC})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataAudioDateRate,
|
||||
Value: muxer.audioMeta.DataRate})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataAudioSampleRate,
|
||||
Value: muxer.audioMeta.SampleRate})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataAudioSampleSize,
|
||||
Value: muxer.audioMeta.SampleSize})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataStereo,
|
||||
Value: muxer.audioMeta.Channels > 1})
|
||||
}
|
||||
|
||||
vcodecID := CodecIDAVC
|
||||
if muxer.videoMeta.Codec == "H265" {
|
||||
vcodecID = CodecIDHEVC
|
||||
}
|
||||
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataVideoCodecID,
|
||||
Value: vcodecID})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataVideoDataRate,
|
||||
Value: muxer.videoMeta.DataRate})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataFrameRate,
|
||||
Value: muxer.videoMeta.FrameRate})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataWidth,
|
||||
Value: muxer.videoMeta.Width})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataHeight,
|
||||
Value: muxer.videoMeta.Height})
|
||||
|
||||
scriptData := ScriptData{
|
||||
Name: ScriptOnMetaData,
|
||||
Value: properties,
|
||||
}
|
||||
data, _ := scriptData.Marshal()
|
||||
|
||||
tag := &Tag{
|
||||
TagType: TagTypeAmf0Data,
|
||||
DataSize: uint32(len(data)),
|
||||
Timestamp: 0,
|
||||
StreamID: 0,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
return muxer.tagWriter.WriteFlvTag(tag)
|
||||
}
|
@@ -1,362 +0,0 @@
|
||||
// Copyright (c) 2019,CAOHONGJU All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package flv
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/cnotch/ipchub/av/codec"
|
||||
"github.com/cnotch/ipchub/av/codec/h264"
|
||||
"github.com/cnotch/ipchub/av/format/amf"
|
||||
"github.com/cnotch/queue"
|
||||
"github.com/cnotch/xlog"
|
||||
)
|
||||
|
||||
// 网络播放时 PTS(Presentation Time Stamp)的延时
|
||||
// 影响视频 Tag 的 CTS 和音频的 DTS(Decoding Time Stamp)
|
||||
const (
|
||||
dtsDelay = 0
|
||||
ptsDelay = 1000
|
||||
)
|
||||
|
||||
// MuxerAvcAac flv muxer from av.Frame(H264[+AAC])
|
||||
type MuxerAvcAac struct {
|
||||
videoMeta codec.VideoMeta
|
||||
audioMeta codec.AudioMeta
|
||||
typeFlags byte
|
||||
audioDataTemplate *AudioData
|
||||
recvQueue *queue.SyncQueue
|
||||
tagWriter TagWriter
|
||||
closed bool
|
||||
spsMuxed bool
|
||||
basePts int64
|
||||
nextDts float64
|
||||
dtsStep float64
|
||||
logger *xlog.Logger // 日志对象
|
||||
}
|
||||
|
||||
// NewMuxerAvcAac .
|
||||
func NewMuxerAvcAac(videoMeta codec.VideoMeta, audioMeta codec.AudioMeta, tagWriter TagWriter, logger *xlog.Logger) *MuxerAvcAac {
|
||||
muxer := &MuxerAvcAac{
|
||||
recvQueue: queue.NewSyncQueue(),
|
||||
videoMeta: videoMeta,
|
||||
audioMeta: audioMeta,
|
||||
typeFlags: byte(TypeFlagsVideo),
|
||||
tagWriter: tagWriter,
|
||||
closed: false,
|
||||
nextDts: dtsDelay,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
if videoMeta.FrameRate > 0 {
|
||||
muxer.dtsStep = 1000.0 / videoMeta.FrameRate
|
||||
}
|
||||
if audioMeta.Codec == "AAC" {
|
||||
muxer.typeFlags |= TypeFlagsAudio
|
||||
muxer.prepareTemplate()
|
||||
}
|
||||
|
||||
go muxer.process()
|
||||
return muxer
|
||||
}
|
||||
|
||||
// WriteFrame .
|
||||
func (muxer *MuxerAvcAac) WriteFrame(frame *codec.Frame) error {
|
||||
muxer.recvQueue.Push(frame)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close .
|
||||
func (muxer *MuxerAvcAac) Close() error {
|
||||
if muxer.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
muxer.closed = true
|
||||
muxer.recvQueue.Signal()
|
||||
return nil
|
||||
}
|
||||
|
||||
// TypeFlags 返回 flv header 中的 TypeFlags
|
||||
func (muxer *MuxerAvcAac) TypeFlags() byte {
|
||||
return muxer.typeFlags
|
||||
}
|
||||
|
||||
func (muxer *MuxerAvcAac) process() {
|
||||
defer func() {
|
||||
defer func() { // 避免 handler 再 panic
|
||||
recover()
|
||||
}()
|
||||
|
||||
if r := recover(); r != nil {
|
||||
muxer.logger.Errorf("flvmuxer routine panic;r = %v \n %s", r, debug.Stack())
|
||||
}
|
||||
|
||||
// 尽早通知GC,回收内存
|
||||
muxer.recvQueue.Reset()
|
||||
}()
|
||||
|
||||
muxer.muxMetadataTag()
|
||||
muxer.muxSequenceHeaderTag()
|
||||
|
||||
for !muxer.closed {
|
||||
f := muxer.recvQueue.Pop()
|
||||
if f == nil {
|
||||
if !muxer.closed {
|
||||
muxer.logger.Warn("flvmuxer:receive nil frame")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
frame := f.(*codec.Frame)
|
||||
if muxer.basePts == 0 {
|
||||
muxer.basePts = frame.AbsTimestamp
|
||||
}
|
||||
|
||||
if frame.MediaType == codec.MediaTypeVideo {
|
||||
if err := muxer.muxVideoTag(frame); err != nil {
|
||||
muxer.logger.Errorf("flvmuxer: muxVideoTag error - %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
if err := muxer.muxAudioTag(frame); err != nil {
|
||||
muxer.logger.Errorf("flvmuxer: muxAudioTag error - %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (muxer *MuxerAvcAac) muxVideoTag(frame *codec.Frame) error {
|
||||
if frame.Payload[0]&0x1F == h264.NalSps {
|
||||
if len(muxer.videoMeta.Sps) == 0 {
|
||||
muxer.videoMeta.Sps = frame.Payload
|
||||
}
|
||||
return muxer.muxSequenceHeaderTag()
|
||||
}
|
||||
|
||||
if frame.Payload[0]&0x1F == h264.NalPps {
|
||||
if len(muxer.videoMeta.Pps) == 0 {
|
||||
muxer.videoMeta.Pps = frame.Payload
|
||||
}
|
||||
return muxer.muxSequenceHeaderTag()
|
||||
}
|
||||
|
||||
dts := int64(muxer.nextDts)
|
||||
muxer.nextDts += muxer.dtsStep
|
||||
pts := frame.AbsTimestamp - muxer.basePts + ptsDelay
|
||||
if dts > pts {
|
||||
pts = dts
|
||||
}
|
||||
|
||||
videoData := &VideoData{
|
||||
FrameType: FrameTypeInterFrame,
|
||||
CodecID: CodecIDAVC,
|
||||
AVCPacketType: AVCPacketTypeNALU,
|
||||
CompositionTime: uint32(pts - dts),
|
||||
Body: frame.Payload,
|
||||
}
|
||||
|
||||
if frame.Payload[0]&0x1F == h264.NalIdrSlice {
|
||||
videoData.FrameType = FrameTypeKeyFrame
|
||||
}
|
||||
data, _ := videoData.Marshal()
|
||||
|
||||
tag := &Tag{
|
||||
TagType: TagTypeVideo,
|
||||
DataSize: uint32(len(data)),
|
||||
Timestamp: uint32(dts),
|
||||
StreamID: 0,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
return muxer.tagWriter.WriteFlvTag(tag)
|
||||
}
|
||||
|
||||
func (muxer *MuxerAvcAac) muxAudioTag(frame *codec.Frame) error {
|
||||
audioData := *muxer.audioDataTemplate
|
||||
audioData.Body = frame.Payload
|
||||
data, _ := audioData.Marshal()
|
||||
|
||||
tag := &Tag{
|
||||
TagType: TagTypeAudio,
|
||||
DataSize: uint32(len(data)),
|
||||
Timestamp: uint32(frame.AbsTimestamp-muxer.basePts) + ptsDelay,
|
||||
StreamID: 0,
|
||||
Data: data,
|
||||
}
|
||||
return muxer.tagWriter.WriteFlvTag(tag)
|
||||
}
|
||||
|
||||
func (muxer *MuxerAvcAac) muxMetadataTag() error {
|
||||
properties := make(amf.EcmaArray, 0, 12)
|
||||
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: "creator",
|
||||
Value: "ipchub stream media server"})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataCreationDate,
|
||||
Value: time.Now().Format(time.RFC3339)})
|
||||
|
||||
if muxer.typeFlags&TypeFlagsAudio > 0 {
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataAudioCodecID,
|
||||
Value: SoundFormatAAC})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataAudioDateRate,
|
||||
Value: muxer.audioMeta.DataRate})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataAudioSampleRate,
|
||||
Value: muxer.audioMeta.SampleRate})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataAudioSampleSize,
|
||||
Value: muxer.audioMeta.SampleSize})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataStereo,
|
||||
Value: muxer.audioMeta.Channels > 1})
|
||||
}
|
||||
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataVideoCodecID,
|
||||
Value: CodecIDAVC})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataVideoDataRate,
|
||||
Value: muxer.videoMeta.DataRate})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataFrameRate,
|
||||
Value: muxer.videoMeta.FrameRate})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataWidth,
|
||||
Value: muxer.videoMeta.Width})
|
||||
properties = append(properties,
|
||||
amf.ObjectProperty{
|
||||
Name: MetaDataHeight,
|
||||
Value: muxer.videoMeta.Height})
|
||||
|
||||
scriptData := ScriptData{
|
||||
Name: ScriptOnMetaData,
|
||||
Value: properties,
|
||||
}
|
||||
data, _ := scriptData.Marshal()
|
||||
|
||||
tag := &Tag{
|
||||
TagType: TagTypeAmf0Data,
|
||||
DataSize: uint32(len(data)),
|
||||
Timestamp: 0,
|
||||
StreamID: 0,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
return muxer.tagWriter.WriteFlvTag(tag)
|
||||
}
|
||||
|
||||
func (muxer *MuxerAvcAac) muxSequenceHeaderTag() error {
|
||||
if muxer.spsMuxed {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !h264.MetadataIsReady(&muxer.videoMeta) {
|
||||
// not enough
|
||||
return nil
|
||||
}
|
||||
|
||||
if muxer.videoMeta.FixedFrameRate {
|
||||
muxer.dtsStep = 1000.0 / muxer.videoMeta.FrameRate
|
||||
} else { // TODO:
|
||||
muxer.dtsStep = 1000.0 / 30
|
||||
}
|
||||
muxer.spsMuxed = true
|
||||
|
||||
record := NewAVCDecoderConfigurationRecord(muxer.videoMeta.Sps, muxer.videoMeta.Pps)
|
||||
body, _ := record.Marshal()
|
||||
|
||||
videoData := &VideoData{
|
||||
FrameType: FrameTypeKeyFrame,
|
||||
CodecID: CodecIDAVC,
|
||||
AVCPacketType: AVCPacketTypeSequenceHeader,
|
||||
CompositionTime: 0,
|
||||
Body: body,
|
||||
}
|
||||
data, _ := videoData.Marshal()
|
||||
|
||||
tag := &Tag{
|
||||
TagType: TagTypeVideo,
|
||||
DataSize: uint32(len(data)),
|
||||
Timestamp: 0,
|
||||
StreamID: 0,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
if err := muxer.tagWriter.WriteFlvTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return muxer.muxAudioSequenceHeaderTag()
|
||||
}
|
||||
|
||||
func (muxer *MuxerAvcAac) muxAudioSequenceHeaderTag() error {
|
||||
if muxer.typeFlags&TypeFlagsAudio == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
audioData := *muxer.audioDataTemplate
|
||||
audioData.AACPacketType = AACPacketTypeSequenceHeader
|
||||
audioData.Body = muxer.audioMeta.Sps
|
||||
data, _ := audioData.Marshal()
|
||||
|
||||
tag := &Tag{
|
||||
TagType: TagTypeAudio,
|
||||
DataSize: uint32(len(data)),
|
||||
Timestamp: 0,
|
||||
StreamID: 0,
|
||||
Data: data,
|
||||
}
|
||||
return muxer.tagWriter.WriteFlvTag(tag)
|
||||
}
|
||||
|
||||
func (muxer *MuxerAvcAac) prepareTemplate() {
|
||||
audioData := &AudioData{
|
||||
SoundFormat: SoundFormatAAC,
|
||||
AACPacketType: AACPacketTypeRawData,
|
||||
Body: nil,
|
||||
}
|
||||
|
||||
switch muxer.audioMeta.SampleRate {
|
||||
case 5512:
|
||||
audioData.SoundRate = SoundRate5512
|
||||
case 11025:
|
||||
audioData.SoundRate = SoundRate11025
|
||||
case 22050:
|
||||
audioData.SoundRate = SoundRate22050
|
||||
case 44100:
|
||||
audioData.SoundRate = SoundRate44100
|
||||
default:
|
||||
audioData.SoundRate = SoundRate44100
|
||||
}
|
||||
|
||||
if muxer.audioMeta.SampleSize == 8 {
|
||||
audioData.SoundSize = SoundeSize8bit
|
||||
} else {
|
||||
audioData.SoundSize = SoundeSize16bit
|
||||
}
|
||||
|
||||
if muxer.audioMeta.Channels > 1 {
|
||||
audioData.SoundType = SoundTypeStereo
|
||||
} else {
|
||||
audioData.SoundType = SoundTypeMono
|
||||
}
|
||||
|
||||
muxer.audioDataTemplate = audioData
|
||||
}
|
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type aacDepacketizer struct {
|
||||
audio *codec.AudioMeta
|
||||
meta *codec.AudioMeta
|
||||
w codec.FrameWriter
|
||||
sizeLength int
|
||||
indexLength int
|
||||
@@ -19,14 +19,14 @@ type aacDepacketizer struct {
|
||||
}
|
||||
|
||||
// NewAacDepacketizer 实例化 AAC 解包器
|
||||
func NewAacDepacketizer(audio *codec.AudioMeta, w codec.FrameWriter) depacketizer {
|
||||
func NewAacDepacketizer(meta *codec.AudioMeta, w codec.FrameWriter) Depacketizer {
|
||||
fe := &aacDepacketizer{
|
||||
audio: audio,
|
||||
meta: meta,
|
||||
w: w,
|
||||
sizeLength: 13,
|
||||
indexLength: 3,
|
||||
}
|
||||
fe.syncClock.RTPTimeUnit = 1000.0 / float64(audio.SampleRate)
|
||||
fe.syncClock.RTPTimeUnit = 1000.0 / float64(meta.SampleRate)
|
||||
return fe
|
||||
}
|
||||
|
||||
|
@@ -13,8 +13,8 @@ import (
|
||||
"github.com/cnotch/xlog"
|
||||
)
|
||||
|
||||
// depacketizer 解包器
|
||||
type depacketizer interface {
|
||||
// Depacketizer 解包器
|
||||
type Depacketizer interface {
|
||||
Control(p *Packet) error
|
||||
Depacketize(p *Packet) error
|
||||
}
|
||||
@@ -37,15 +37,14 @@ func NewDemuxer(video *codec.VideoMeta, audio *codec.AudioMeta, fw codec.FrameWr
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
var videoDepacketizer, audioDepacketizer depacketizer
|
||||
var videoDepacketizer, audioDepacketizer Depacketizer
|
||||
switch video.Codec {
|
||||
case "H264":
|
||||
videoDepacketizer = NewH264Depacketizer(video, fw)
|
||||
case "H265":
|
||||
videoDepacketizer = NewH265Depacketizer(video, fw)
|
||||
}
|
||||
if videoDepacketizer == nil {
|
||||
return nil, fmt.Errorf("Unsupport video codec type:%s", video.Codec)
|
||||
default:
|
||||
return nil, fmt.Errorf("rtp demuxer unsupport video codec type:%s", video.Codec)
|
||||
}
|
||||
|
||||
fc.depacketizeFuncs[ChannelVideo] = videoDepacketizer.Depacketize
|
||||
|
@@ -13,19 +13,19 @@ import (
|
||||
|
||||
type h264Depacketizer struct {
|
||||
fragments []*Packet // 分片包
|
||||
video *codec.VideoMeta
|
||||
meta *codec.VideoMeta
|
||||
w codec.FrameWriter
|
||||
syncClock SyncClock
|
||||
}
|
||||
|
||||
// NewH264Depacketizer 实例化 H264 帧提取器
|
||||
func NewH264Depacketizer(video *codec.VideoMeta, w codec.FrameWriter) depacketizer {
|
||||
func NewH264Depacketizer(meta *codec.VideoMeta, w codec.FrameWriter) Depacketizer {
|
||||
fe := &h264Depacketizer{
|
||||
video: video,
|
||||
meta: meta,
|
||||
fragments: make([]*Packet, 0, 16),
|
||||
w: w,
|
||||
}
|
||||
fe.syncClock.RTPTimeUnit = 1000.0 / float64(video.ClockRate)
|
||||
fe.syncClock.RTPTimeUnit = 1000.0 / float64(meta.ClockRate)
|
||||
return fe
|
||||
}
|
||||
|
||||
@@ -199,12 +199,12 @@ func (h264dp *h264Depacketizer) writeFrame(frame *codec.Frame) error {
|
||||
nalType := frame.Payload[0] & 0x1f
|
||||
switch nalType {
|
||||
case h264.NalSps:
|
||||
if len(h264dp.video.Sps) == 0 {
|
||||
h264dp.video.Sps = frame.Payload
|
||||
if len(h264dp.meta.Sps) == 0 {
|
||||
h264dp.meta.Sps = frame.Payload
|
||||
}
|
||||
case h264.NalPps:
|
||||
if len(h264dp.video.Pps) == 0 {
|
||||
h264dp.video.Pps = frame.Payload
|
||||
if len(h264dp.meta.Pps) == 0 {
|
||||
h264dp.meta.Pps = frame.Payload
|
||||
}
|
||||
case h264.NalFillerData: // ?ignore...
|
||||
return nil
|
||||
|
@@ -11,19 +11,19 @@ import (
|
||||
|
||||
type h265Depacketizer struct {
|
||||
fragments []*Packet // 分片包
|
||||
video *codec.VideoMeta
|
||||
meta *codec.VideoMeta
|
||||
w codec.FrameWriter
|
||||
syncClock SyncClock
|
||||
}
|
||||
|
||||
// NewH265Depacketizer 实例化 H265 帧提取器
|
||||
func NewH265Depacketizer(video *codec.VideoMeta, w codec.FrameWriter) depacketizer {
|
||||
func NewH265Depacketizer(meta *codec.VideoMeta, w codec.FrameWriter) Depacketizer {
|
||||
fe := &h265Depacketizer{
|
||||
video: video,
|
||||
meta: meta,
|
||||
fragments: make([]*Packet, 0, 16),
|
||||
w: w,
|
||||
}
|
||||
fe.syncClock.RTPTimeUnit = 1000.0 / float64(video.ClockRate)
|
||||
fe.syncClock.RTPTimeUnit = 1000.0 / float64(meta.ClockRate)
|
||||
return fe
|
||||
}
|
||||
|
||||
@@ -178,16 +178,16 @@ func (h265dp *h265Depacketizer) writeFrame(frame *codec.Frame) error {
|
||||
nalType := (frame.Payload[0] >> 1) & 0x3f
|
||||
switch nalType {
|
||||
case hevc.NalVps:
|
||||
if len(h265dp.video.Vps) == 0 {
|
||||
h265dp.video.Vps = frame.Payload
|
||||
if len(h265dp.meta.Vps) == 0 {
|
||||
h265dp.meta.Vps = frame.Payload
|
||||
}
|
||||
case hevc.NalSps:
|
||||
if len(h265dp.video.Sps) == 0 {
|
||||
h265dp.video.Sps = frame.Payload
|
||||
if len(h265dp.meta.Sps) == 0 {
|
||||
h265dp.meta.Sps = frame.Payload
|
||||
}
|
||||
case hevc.NalPps:
|
||||
if len(h265dp.video.Pps) == 0 {
|
||||
h265dp.video.Pps = frame.Payload
|
||||
if len(h265dp.meta.Pps) == 0 {
|
||||
h265dp.meta.Pps = frame.Payload
|
||||
}
|
||||
}
|
||||
return h265dp.w.WriteFrame(frame)
|
||||
|
@@ -105,7 +105,7 @@ func (s *Stream) prepareOtherStream() {
|
||||
s.flvCache = emptyCache{}
|
||||
s.flvMuxer = emptyFlvMuxer{}
|
||||
|
||||
// prepare rtp.Packet -> av.Frame
|
||||
// prepare rtp.Packet -> codec.Frame
|
||||
var err error
|
||||
if s.rtpDemuxer, err = rtp.NewDemuxer(&s.Video, &s.Audio,
|
||||
s, s.logger.With(xlog.Fields(xlog.F("extra", "rtp2frame")))); err != nil {
|
||||
@@ -113,14 +113,15 @@ func (s *Stream) prepareOtherStream() {
|
||||
return
|
||||
}
|
||||
|
||||
// prepare av.Frame -> flv.Tag
|
||||
if s.Video.Codec == "H264" {
|
||||
// prepare codec.Frame -> flv.Tag
|
||||
var flvMuxer *flv.Muxer
|
||||
if flvMuxer, err = flv.NewMuxer(&s.Video, &s.Audio,
|
||||
s, s.logger.With(xlog.Fields(xlog.F("extra", "frame2flv")))); err == nil {
|
||||
s.flvCache = cache.NewFlvCache(config.CacheGop())
|
||||
s.flvMuxer = flv.NewMuxerAvcAac(s.Video, s.Audio,
|
||||
s, s.logger.With(xlog.Fields(xlog.F("extra", "frame2flv"))))
|
||||
s.flvMuxer = flvMuxer
|
||||
}
|
||||
|
||||
// prepare av.Frame -> mpegts.Frame
|
||||
// prepare codec.Frame -> mpegts.Frame
|
||||
if s.Video.Codec == "H264" {
|
||||
hlsPlaylist := hls.NewPlaylist()
|
||||
sg, err := hls.NewSegmentGenerator(hlsPlaylist, s.path,
|
||||
|
Reference in New Issue
Block a user