mirror of
https://github.com/lkmio/lkm.git
synced 2025-10-24 15:43:14 +08:00
refactor: 拆分avformat依赖库
This commit is contained in:
4
api.go
4
api.go
@@ -43,7 +43,7 @@ func init() {
|
|||||||
|
|
||||||
func filterSourceID(f func(sourceId string, w http.ResponseWriter, req *http.Request), suffix string) func(http.ResponseWriter, *http.Request) {
|
func filterSourceID(f func(sourceId string, w http.ResponseWriter, req *http.Request), suffix string) func(http.ResponseWriter, *http.Request) {
|
||||||
return func(w http.ResponseWriter, req *http.Request) {
|
return func(w http.ResponseWriter, req *http.Request) {
|
||||||
source, err := stream.Path2SourceId(req.URL.Path, suffix)
|
source, err := stream.Path2SourceID(req.URL.Path, suffix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Sugar.Errorf("拉流失败 解析流id发生err: %s path: %s", err.Error(), req.URL.Path)
|
log.Sugar.Errorf("拉流失败 解析流id发生err: %s path: %s", err.Error(), req.URL.Path)
|
||||||
httpResponse(w, http.StatusBadRequest, err.Error())
|
httpResponse(w, http.StatusBadRequest, err.Error())
|
||||||
@@ -398,7 +398,7 @@ func (api *ApiServer) OnSourceList(w http.ResponseWriter, r *http.Request) {
|
|||||||
var codecs []string
|
var codecs []string
|
||||||
tracks := source.OriginTracks()
|
tracks := source.OriginTracks()
|
||||||
for _, track := range tracks {
|
for _, track := range tracks {
|
||||||
codecs = append(codecs, track.Stream.CodecId().String())
|
codecs = append(codecs, track.Stream.CodecID.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
details = append(details, SourceDetails{
|
details = append(details, SourceDetails{
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func NewStreamEndInfo(source stream.Source) *stream.StreamEndInfo {
|
|||||||
timestamp[0] = track.Dts + int64(track.FrameDuration)
|
timestamp[0] = track.Dts + int64(track.FrameDuration)
|
||||||
timestamp[1] = track.Pts + int64(track.FrameDuration)
|
timestamp[1] = track.Pts + int64(track.FrameDuration)
|
||||||
|
|
||||||
info.Timestamps[track.Stream.CodecId()] = timestamp
|
info.Timestamps[track.Stream.CodecID] = timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, transStream := range streams {
|
for _, transStream := range streams {
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// HttpFlvBlockHeaderSize 在每块http-flv流的头部,预留指定大小的数据, 用于描述flv数据块的长度信息
|
// HttpFlvBlockHeaderSize 在每块http-flv流的头部,预留指定大小的数据, 用于描述flv数据块的长度信息
|
||||||
// http-flv是以文件流的形式传输http流, 格式如下: length\r\n|flv data\r\n
|
// http-flv是以文件流形式传输的http流, 格式如下: length\r\n|flv data\r\n
|
||||||
// 我们对http-flv-block的封装: |block size[4]|skip count[2]|length\r\n|flv data\r\n
|
// 我们对http-flv-block的封装: |block size[4]|skip count[2]|length\r\n|flv data\r\n
|
||||||
// skip count是因为length长度不固定, 需要一个字段说明, 跳过多少字节才是http-flv数据
|
|
||||||
HttpFlvBlockHeaderSize = 20
|
HttpFlvBlockHeaderSize = 20
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package flv
|
package flv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package flv
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"github.com/lkmio/avformat/libflv"
|
"github.com/lkmio/avformat"
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
|
"github.com/lkmio/flv"
|
||||||
|
"github.com/lkmio/flv/amf0"
|
||||||
"github.com/lkmio/lkm/rtmp"
|
"github.com/lkmio/lkm/rtmp"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
)
|
)
|
||||||
@@ -11,13 +13,13 @@ import (
|
|||||||
type TransStream struct {
|
type TransStream struct {
|
||||||
stream.TCPTransStream
|
stream.TCPTransStream
|
||||||
|
|
||||||
Muxer libflv.Muxer
|
Muxer *flv.Muxer
|
||||||
flvHeaderBlock []byte // 单独保存9个字节长的flv头, 只发一次, 后续恢复推流不再发送
|
flvHeaderBlock []byte // 单独保存9个字节长的flv头, 只发一次, 后续恢复推流不再发送
|
||||||
flvExtraDataBlock []byte // metadata和sequence header
|
flvExtraDataBlock []byte // metadata和sequence header
|
||||||
flvExtraDataTagSize int // 整个flv tag大小
|
flvExtraDataPreTagSize uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransStream) Input(packet utils.AVPacket) ([][]byte, int64, bool, error) {
|
func (t *TransStream) Input(packet *avformat.AVPacket) ([][]byte, int64, bool, error) {
|
||||||
t.ClearOutStreamBuffer()
|
t.ClearOutStreamBuffer()
|
||||||
|
|
||||||
var flvTagSize int
|
var flvTagSize int
|
||||||
@@ -26,17 +28,19 @@ func (t *TransStream) Input(packet utils.AVPacket) ([][]byte, int64, bool, error
|
|||||||
var dts int64
|
var dts int64
|
||||||
var pts int64
|
var pts int64
|
||||||
var keyBuffer bool
|
var keyBuffer bool
|
||||||
|
var frameType int
|
||||||
|
|
||||||
dts = packet.ConvertDts(1000)
|
dts = packet.ConvertDts(1000)
|
||||||
pts = packet.ConvertPts(1000)
|
pts = packet.ConvertPts(1000)
|
||||||
if utils.AVMediaTypeAudio == packet.MediaType() {
|
if utils.AVMediaTypeAudio == packet.MediaType {
|
||||||
flvTagSize = 17 + len(packet.Data())
|
data = packet.Data
|
||||||
data = packet.Data()
|
flvTagSize = flv.TagHeaderSize + t.Muxer.ComputeAudioDataHeaderSize() + len(packet.Data)
|
||||||
} else if utils.AVMediaTypeVideo == packet.MediaType() {
|
} else if utils.AVMediaTypeVideo == packet.MediaType {
|
||||||
flvTagSize = t.Muxer.ComputeVideoDataSize(uint32(pts-dts)) + libflv.TagHeaderSize + len(packet.AVCCPacketData())
|
data = avformat.AnnexBPacket2AVCC(packet)
|
||||||
|
flvTagSize = flv.TagHeaderSize + t.Muxer.ComputeVideoDataHeaderSize(uint32(pts-dts)) + len(data)
|
||||||
data = packet.AVCCPacketData()
|
if videoKey = packet.Key; videoKey {
|
||||||
videoKey = packet.KeyFrame()
|
frameType = flv.FrameTypeKeyFrame
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关键帧都放在切片头部,所以遇到关键帧创建新切片, 发送当前切片剩余流
|
// 关键帧都放在切片头部,所以遇到关键帧创建新切片, 发送当前切片剩余流
|
||||||
@@ -64,7 +68,7 @@ func (t *TransStream) Input(packet utils.AVPacket) ([][]byte, int64, bool, error
|
|||||||
// 分配block
|
// 分配block
|
||||||
bytes := t.MWBuffer.Allocate(separatorSize+flvTagSize, dts, videoKey)
|
bytes := t.MWBuffer.Allocate(separatorSize+flvTagSize, dts, videoKey)
|
||||||
// 写flv tag
|
// 写flv tag
|
||||||
n += t.Muxer.Input(bytes[n:], packet.MediaType(), len(data), dts, pts, packet.KeyFrame(), false)
|
n += t.Muxer.Input(bytes[n:], packet.MediaType, len(data), dts, pts, false, frameType)
|
||||||
copy(bytes[n:], data)
|
copy(bytes[n:], data)
|
||||||
|
|
||||||
// 合并写满再发
|
// 合并写满再发
|
||||||
@@ -82,43 +86,28 @@ func (t *TransStream) AddTrack(track *stream.Track) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.AVMediaTypeAudio == track.Stream.Type() {
|
if utils.AVMediaTypeAudio == track.Stream.MediaType {
|
||||||
t.Muxer.AddAudioTrack(track.Stream.CodecId(), 0, 0, 0)
|
t.Muxer.AddAudioTrack(track.Stream)
|
||||||
} else if utils.AVMediaTypeVideo == track.Stream.Type() {
|
} else if utils.AVMediaTypeVideo == track.Stream.MediaType {
|
||||||
t.Muxer.AddVideoTrack(track.Stream.CodecId())
|
t.Muxer.AddVideoTrack(track.Stream)
|
||||||
|
|
||||||
t.Muxer.MetaData().AddNumberProperty("width", float64(track.Stream.CodecParameters().Width()))
|
t.Muxer.MetaData().AddNumberProperty("width", float64(track.Stream.CodecParameters.Width()))
|
||||||
t.Muxer.MetaData().AddNumberProperty("height", float64(track.Stream.CodecParameters().Height()))
|
t.Muxer.MetaData().AddNumberProperty("height", float64(track.Stream.CodecParameters.Height()))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransStream) WriteHeader() error {
|
func (t *TransStream) WriteHeader() error {
|
||||||
var header [4096]byte
|
var header [4096]byte
|
||||||
var extraDataSize int
|
|
||||||
size := t.Muxer.WriteHeader(header[:])
|
size := t.Muxer.WriteHeader(header[:])
|
||||||
|
tags := header[9:size]
|
||||||
copy(t.flvHeaderBlock[HttpFlvBlockHeaderSize:], header[:9])
|
copy(t.flvHeaderBlock[HttpFlvBlockHeaderSize:], header[:9])
|
||||||
copy(t.flvExtraDataBlock[HttpFlvBlockHeaderSize:], header[9:size])
|
copy(t.flvExtraDataBlock[HttpFlvBlockHeaderSize:], tags)
|
||||||
|
|
||||||
extraDataSize = HttpFlvBlockHeaderSize + (size - 9)
|
t.flvExtraDataPreTagSize = t.Muxer.PrevTagSize()
|
||||||
for _, track := range t.BaseTransStream.Tracks {
|
|
||||||
var data []byte
|
|
||||||
if utils.AVMediaTypeAudio == track.Stream.Type() {
|
|
||||||
data = track.Stream.Extra()
|
|
||||||
} else if utils.AVMediaTypeVideo == track.Stream.Type() {
|
|
||||||
data = track.Stream.CodecParameters().MP4ExtraData()
|
|
||||||
}
|
|
||||||
|
|
||||||
n := t.Muxer.Input(t.flvExtraDataBlock[extraDataSize:], track.Stream.Type(), len(data), 0, 0, false, true)
|
// +2 加上末尾换行符
|
||||||
extraDataSize += n
|
t.flvExtraDataBlock = t.flvExtraDataBlock[:HttpFlvBlockHeaderSize+size-9+2]
|
||||||
copy(t.flvExtraDataBlock[extraDataSize:], data)
|
|
||||||
extraDataSize += len(data)
|
|
||||||
t.flvExtraDataTagSize = n - 15 + len(data) + 11
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加上末尾换行符
|
|
||||||
extraDataSize += 2
|
|
||||||
t.flvExtraDataBlock = t.flvExtraDataBlock[:extraDataSize]
|
|
||||||
writeSeparator(t.flvHeaderBlock)
|
writeSeparator(t.flvHeaderBlock)
|
||||||
writeSeparator(t.flvExtraDataBlock)
|
writeSeparator(t.flvExtraDataBlock)
|
||||||
|
|
||||||
@@ -135,9 +124,9 @@ func (t *TransStream) ReadKeyFrameBuffer() ([][]byte, int64, error) {
|
|||||||
|
|
||||||
// 发送当前内存池已有的合并写切片
|
// 发送当前内存池已有的合并写切片
|
||||||
t.MWBuffer.ReadSegmentsFromKeyFrameIndex(func(bytes []byte) {
|
t.MWBuffer.ReadSegmentsFromKeyFrameIndex(func(bytes []byte) {
|
||||||
if t.OutBufferSize < 1 {
|
|
||||||
// 修改第一个flv tag的pre tag size为sequence header tag size
|
// 修改第一个flv tag的pre tag size为sequence header tag size
|
||||||
binary.BigEndian.PutUint32(bytes[HttpFlvBlockHeaderSize:], uint32(t.flvExtraDataTagSize))
|
if t.OutBufferSize < 1 {
|
||||||
|
binary.BigEndian.PutUint32(bytes[HttpFlvBlockHeaderSize:], t.flvExtraDataPreTagSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 遍历发送合并写切片
|
// 遍历发送合并写切片
|
||||||
@@ -173,9 +162,9 @@ func (t *TransStream) flushSegment() ([]byte, bool) {
|
|||||||
return FormatSegment(segment), key
|
return FormatSegment(segment), key
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHttpTransStream(metadata *libflv.AMF0Object, prevTagSize uint32) stream.TransStream {
|
func NewHttpTransStream(metadata *amf0.Object, prevTagSize uint32) stream.TransStream {
|
||||||
return &TransStream{
|
return &TransStream{
|
||||||
Muxer: libflv.NewMuxerWithPrevTagSize(metadata, prevTagSize),
|
Muxer: flv.NewMuxerWithPrevTagSize(metadata, prevTagSize),
|
||||||
flvHeaderBlock: make([]byte, 31),
|
flvHeaderBlock: make([]byte, 31),
|
||||||
flvExtraDataBlock: make([]byte, 4096),
|
flvExtraDataBlock: make([]byte, 4096),
|
||||||
}
|
}
|
||||||
@@ -183,7 +172,7 @@ func NewHttpTransStream(metadata *libflv.AMF0Object, prevTagSize uint32) stream.
|
|||||||
|
|
||||||
func TransStreamFactory(source stream.Source, protocol stream.TransStreamProtocol, tracks []*stream.Track) (stream.TransStream, error) {
|
func TransStreamFactory(source stream.Source, protocol stream.TransStreamProtocol, tracks []*stream.Track) (stream.TransStream, error) {
|
||||||
var prevTagSize uint32
|
var prevTagSize uint32
|
||||||
var metaData *libflv.AMF0Object
|
var metaData *amf0.Object
|
||||||
|
|
||||||
endInfo := source.GetStreamEndInfo()
|
endInfo := source.GetStreamEndInfo()
|
||||||
if endInfo != nil {
|
if endInfo != nil {
|
||||||
@@ -191,7 +180,7 @@ func TransStreamFactory(source stream.Source, protocol stream.TransStreamProtoco
|
|||||||
}
|
}
|
||||||
|
|
||||||
if stream.SourceTypeRtmp == source.GetType() {
|
if stream.SourceTypeRtmp == source.GetType() {
|
||||||
metaData = source.(*rtmp.Publisher).Stack.MetaData()
|
metaData = source.(*rtmp.Publisher).Stack.Metadata()
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewHttpTransStream(metaData, prevTagSize), nil
|
return NewHttpTransStream(metaData, prevTagSize), nil
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package flv
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/lkmio/avformat/transport"
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package gb28181
|
package gb28181
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/librtp"
|
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/log"
|
"github.com/lkmio/lkm/log"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/rtp"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ const (
|
|||||||
type ForwardSink struct {
|
type ForwardSink struct {
|
||||||
stream.BaseSink
|
stream.BaseSink
|
||||||
setup SetupType
|
setup SetupType
|
||||||
socket transport.ITransport
|
socket transport.Transport
|
||||||
ssrc uint32
|
ssrc uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ func (f *ForwardSink) Write(index int, data [][]byte, ts int64) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 修改为与上级协商的SSRC
|
// 修改为与上级协商的SSRC
|
||||||
librtp.ModifySSRC(data[0], f.ssrc)
|
rtp.ModifySSRC(data[0], f.ssrc)
|
||||||
|
|
||||||
if SetupUDP == f.setup {
|
if SetupUDP == f.setup {
|
||||||
f.socket.(*transport.UDPClient).Write(data[0][2:])
|
f.socket.(*transport.UDPClient).Write(data[0][2:])
|
||||||
@@ -83,14 +83,14 @@ func NewForwardSink(ssrc uint32, serverAddr string, setup SetupType, sinkId stre
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := TransportManger.NewUDPClient(stream.AppConfig.ListenIP, remoteAddr)
|
client, err := TransportManger.NewUDPClient(remoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sink.socket = client
|
sink.socket = client
|
||||||
} else if SetupActive == setup {
|
} else if SetupActive == setup {
|
||||||
server, err := TransportManger.NewTCPServer(stream.AppConfig.ListenIP)
|
server, err := TransportManger.NewTCPServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package gb28181
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -82,8 +82,8 @@ func closeForwardSink(source, sink string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTransport(setup string) (transport.ITransport, *os.File) {
|
func createTransport(setup string) (transport.Transport, *os.File) {
|
||||||
var socket transport.ITransport
|
var socket transport.Transport
|
||||||
name := fmt.Sprintf("./gb_forward_ps_%s_%d.raw", setup, time.Now().UnixMilli())
|
name := fmt.Sprintf("./gb_forward_ps_%s_%d.raw", setup, time.Now().UnixMilli())
|
||||||
file, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR, 0666)
|
file, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -135,7 +135,7 @@ func TestForwardSink(t *testing.T) {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
var ids []string
|
var ids []string
|
||||||
var transports []transport.ITransport
|
var transports []transport.Transport
|
||||||
var files []*os.File
|
var files []*os.File
|
||||||
|
|
||||||
// 三种推流方式都测试
|
// 三种推流方式都测试
|
||||||
|
|||||||
@@ -5,50 +5,20 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lkmio/avformat/transport"
|
"github.com/lkmio/avformat"
|
||||||
|
"github.com/lkmio/avformat/utils"
|
||||||
|
"github.com/lkmio/mpeg"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
|
"github.com/pion/rtp"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 输入rtp负载的ps流文件路径, 根据ssrc解析, rtp头不要带扩展
|
|
||||||
func readRtpRaw(path string, ssrc uint32, tcp bool, cb func([]byte)) {
|
|
||||||
file, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset int
|
|
||||||
tcpRtp := make([]byte, 1500)
|
|
||||||
|
|
||||||
for i := 0; i < len(file)-4; i++ {
|
|
||||||
if ssrc != binary.BigEndian.Uint32(file[i:]) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if i-8 != 0 {
|
|
||||||
var err error
|
|
||||||
rtp := file[offset : i-8]
|
|
||||||
|
|
||||||
if tcp {
|
|
||||||
binary.BigEndian.PutUint16(tcpRtp, uint16(len(rtp)))
|
|
||||||
copy(tcpRtp[2:], rtp)
|
|
||||||
cb(tcpRtp[:2+len(rtp)])
|
|
||||||
} else {
|
|
||||||
cb(rtp)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
offset = i - 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func connectSource(source string, addr string) {
|
func connectSource(source string, addr string) {
|
||||||
v := &struct {
|
v := &struct {
|
||||||
Source string `json:"source"` //GetSourceID
|
Source string `json:"source"` //GetSourceID
|
||||||
@@ -131,32 +101,137 @@ func createSource(source, setup string, ssrc uint32) (string, uint16) {
|
|||||||
return connectInfo.Data.IP, connectInfo.Data.Port
|
return connectInfo.Data.IP, connectInfo.Data.Port
|
||||||
}
|
}
|
||||||
|
|
||||||
func rtp2overTcp(path string, ssrc uint32) {
|
// 分割rtp包, 返回rtp over tcp包
|
||||||
file, err := os.OpenFile("./rtp.raw", os.O_CREATE|os.O_RDWR, 0666)
|
func splitPackets(data []byte, ssrc uint32) ([][]byte, uint32) {
|
||||||
|
tcp := binary.BigEndian.Uint16(data) <= 1500
|
||||||
|
length := len(data)
|
||||||
|
var packets [][]byte
|
||||||
|
if tcp {
|
||||||
|
var offset int
|
||||||
|
for i := 0; i < length; i += 2 {
|
||||||
|
if i > 0 {
|
||||||
|
packets = append(packets, data[offset:i])
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = i
|
||||||
|
i += int(binary.BigEndian.Uint16(data[i:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(packets) > 0 {
|
||||||
|
packet := rtp.Packet{}
|
||||||
|
err := packet.Unmarshal(packets[0][2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
readRtpRaw(path, ssrc, true, func(data []byte) {
|
return packets, packet.SSRC
|
||||||
file.Write(data)
|
}
|
||||||
})
|
} else {
|
||||||
|
// udp包根据ssrc查找
|
||||||
|
var offset int
|
||||||
|
for i := 0; i < length-4; i++ {
|
||||||
|
if ssrc != binary.BigEndian.Uint32(data[i:]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
file.Close()
|
if i-8 != 0 {
|
||||||
|
packet := data[offset : i-8]
|
||||||
|
bytes := make([]byte, 2+len(packet))
|
||||||
|
binary.BigEndian.PutUint16(bytes, uint16(len(packet)))
|
||||||
|
copy(bytes[2:], packet)
|
||||||
|
packets = append(packets, bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = i - 8
|
||||||
|
}
|
||||||
|
|
||||||
|
return packets, ssrc
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ssrc
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用wireshark直接导出udp流
|
var ts int64 = -1
|
||||||
|
|
||||||
|
func ctrDelay(data []byte) {
|
||||||
|
packet := rtp.Packet{}
|
||||||
|
err := packet.Unmarshal(data)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ts == -1 {
|
||||||
|
ts = int64(packet.Timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dis := (int64(packet.Timestamp) - ts) / 90; dis > 0 {
|
||||||
|
time.Sleep(time.Duration(dis) * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
ts = int64(packet.Timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用wireshark直接导出的rtp流
|
||||||
// 根据ssrc来查找每个rtp包, rtp不要带扩展字段
|
// 根据ssrc来查找每个rtp包, rtp不要带扩展字段
|
||||||
func TestUDPRecv(t *testing.T) {
|
func TestPublish(t *testing.T) {
|
||||||
path := "D:\\GOProjects\\avformat\\gb28181_h264.rtp"
|
path := "../../source_files/gb28181_h264.rtp"
|
||||||
ssrc := 0xBEBC201
|
var ssrc uint32 = 0xBEBC201
|
||||||
localAddr := "0.0.0.0:20001"
|
localAddr := "0.0.0.0:20001"
|
||||||
setup := "udp" //udp/passive/active
|
|
||||||
id := "hls_mystream"
|
id := "hls_mystream"
|
||||||
|
|
||||||
rtp2overTcp(path, uint32(ssrc))
|
data, err := os.ReadFile(path)
|
||||||
ip, port := createSource(id, setup, uint32(ssrc))
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var packets [][]byte
|
||||||
|
packets, ssrc = splitPackets(data, ssrc)
|
||||||
|
utils.Assert(len(packets) > 0)
|
||||||
|
|
||||||
|
sort.Slice(packets, func(i, j int) bool {
|
||||||
|
packet := rtp.Packet{}
|
||||||
|
if err := packet.Unmarshal(packets[i][2:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
packet2 := rtp.Packet{}
|
||||||
|
if err := packet2.Unmarshal(packets[j][2:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return packet.SequenceNumber < packet2.SequenceNumber
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("demux", func(t *testing.T) {
|
||||||
|
buffer := mpeg.NewProbeBuffer(1024 * 1024 * 2)
|
||||||
|
demuxer := mpeg.NewPSDemuxer(true)
|
||||||
|
demuxer.SetHandler(&avformat.OnUnpackStream2FileHandler{
|
||||||
|
Path: "./ps_demux",
|
||||||
|
})
|
||||||
|
|
||||||
|
file, err := os.OpenFile("./ps_demux.ps", os.O_WRONLY|os.O_CREATE, 132)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, packet := range packets {
|
||||||
|
file.Write(packet[14:])
|
||||||
|
bytes, err := buffer.Input(packet[14:])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := demuxer.Input(bytes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.Reset(n)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("udp", func(t *testing.T) {
|
||||||
|
ip, port := createSource(id, "udp", ssrc)
|
||||||
|
|
||||||
if setup == "udp" {
|
|
||||||
addr, _ := net.ResolveUDPAddr("udp", localAddr)
|
addr, _ := net.ResolveUDPAddr("udp", localAddr)
|
||||||
remoteAddr, _ := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ip, port))
|
remoteAddr, _ := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ip, port))
|
||||||
|
|
||||||
@@ -166,11 +241,15 @@ func TestUDPRecv(t *testing.T) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
readRtpRaw(path, uint32(ssrc), false, func(data []byte) {
|
for _, packet := range packets {
|
||||||
client.Write(data)
|
client.Write(packet[2:])
|
||||||
time.Sleep(1 * time.Millisecond)
|
ctrDelay(packet[2:])
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} else if !(setup == "active") {
|
|
||||||
|
t.Run("passive", func(t *testing.T) {
|
||||||
|
ip, port := createSource(id, "passive", ssrc)
|
||||||
|
|
||||||
addr, _ := net.ResolveTCPAddr("tcp", localAddr)
|
addr, _ := net.ResolveTCPAddr("tcp", localAddr)
|
||||||
remoteAddr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", ip, port))
|
remoteAddr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", ip, port))
|
||||||
|
|
||||||
@@ -181,19 +260,23 @@ func TestUDPRecv(t *testing.T) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
readRtpRaw(path, uint32(ssrc), true, func(data []byte) {
|
for _, packet := range packets {
|
||||||
client.Write(data)
|
client.Write(packet)
|
||||||
time.Sleep(1 * time.Millisecond)
|
ctrDelay(packet[2:])
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
|
t.Run("active", func(t *testing.T) {
|
||||||
|
ip, port := createSource(id, "active", ssrc)
|
||||||
|
|
||||||
addr, _ := net.ResolveTCPAddr("tcp", localAddr)
|
addr, _ := net.ResolveTCPAddr("tcp", localAddr)
|
||||||
server := transport.TCPServer{}
|
server := transport.TCPServer{}
|
||||||
|
|
||||||
server.SetHandler2(func(conn net.Conn) []byte {
|
server.SetHandler2(func(conn net.Conn) []byte {
|
||||||
readRtpRaw(path, uint32(ssrc), true, func(data []byte) {
|
for _, packet := range packets {
|
||||||
conn.Write(data)
|
conn.Write(packet)
|
||||||
time.Sleep(1 * time.Millisecond)
|
ctrDelay(packet[2:])
|
||||||
})
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
@@ -204,7 +287,5 @@ func TestUDPRecv(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connectSource(id, fmt.Sprintf("%s:%d", ip, port))
|
connectSource(id, fmt.Sprintf("%s:%d", ip, port))
|
||||||
}
|
})
|
||||||
|
|
||||||
select {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package gb28181
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lkmio/avformat/libmpeg"
|
"github.com/lkmio/avformat"
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/log"
|
"github.com/lkmio/lkm/log"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/mpeg"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
@@ -49,12 +50,10 @@ type GBSource interface {
|
|||||||
type BaseGBSource struct {
|
type BaseGBSource struct {
|
||||||
stream.PublishSource
|
stream.PublishSource
|
||||||
|
|
||||||
deMuxerCtx *libmpeg.PSDeMuxerContext
|
probeBuffer *mpeg.PSProbeBuffer
|
||||||
audioStream utils.AVStream
|
|
||||||
videoStream utils.AVStream
|
|
||||||
|
|
||||||
ssrc uint32
|
ssrc uint32
|
||||||
transport transport.ITransport
|
transport transport.Transport
|
||||||
|
|
||||||
audioTimestamp int64
|
audioTimestamp int64
|
||||||
videoTimestamp int64
|
videoTimestamp int64
|
||||||
@@ -64,9 +63,13 @@ type BaseGBSource struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (source *BaseGBSource) Init(receiveQueueSize int) {
|
func (source *BaseGBSource) Init(receiveQueueSize int) {
|
||||||
source.deMuxerCtx = libmpeg.NewPSDeMuxerContext(make([]byte, PsProbeBufferSize))
|
source.TransDemuxer = mpeg.NewPSDemuxer(false)
|
||||||
source.deMuxerCtx.SetHandler(source)
|
source.TransDemuxer.SetHandler(source)
|
||||||
|
source.TransDemuxer.SetOnPreprocessPacketHandler(func(packet *avformat.AVPacket) {
|
||||||
|
source.correctTimestamp(packet, packet.Dts, packet.Pts)
|
||||||
|
})
|
||||||
source.SetType(stream.SourceType28181)
|
source.SetType(stream.SourceType28181)
|
||||||
|
source.probeBuffer = mpeg.NewProbeBuffer(PsProbeBufferSize)
|
||||||
source.PublishSource.Init(receiveQueueSize)
|
source.PublishSource.Init(receiveQueueSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +88,14 @@ func (source *BaseGBSource) Input(data []byte) error {
|
|||||||
|
|
||||||
packet := rtp.Packet{}
|
packet := rtp.Packet{}
|
||||||
_ = packet.Unmarshal(data)
|
_ = packet.Unmarshal(data)
|
||||||
err := source.deMuxerCtx.Input(packet.Payload)
|
|
||||||
|
var bytes []byte
|
||||||
|
var n int
|
||||||
|
var err error
|
||||||
|
bytes, err = source.probeBuffer.Input(packet.Payload)
|
||||||
|
if err == nil {
|
||||||
|
n, err = source.TransDemuxer.Input(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
// 非解析缓冲区满的错误, 继续解析
|
// 非解析缓冲区满的错误, 继续解析
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -94,94 +104,25 @@ func (source *BaseGBSource) Input(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnPartPacket 部分es流回调
|
source.probeBuffer.Reset(n)
|
||||||
func (source *BaseGBSource) OnPartPacket(index int, mediaType utils.AVMediaType, codec utils.AVCodecID, data []byte, first bool) {
|
|
||||||
buffer := source.FindOrCreatePacketBuffer(index, mediaType)
|
|
||||||
|
|
||||||
// 第一个es包, 标记内存起始位置
|
|
||||||
if first {
|
|
||||||
buffer.Mark()
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnLossPacket 非完整es包丢弃回调, 直接释放内存块
|
|
||||||
func (source *BaseGBSource) OnLossPacket(index int, mediaType utils.AVMediaType, codec utils.AVCodecID) {
|
|
||||||
buffer := source.FindOrCreatePacketBuffer(index, mediaType)
|
|
||||||
|
|
||||||
buffer.Fetch()
|
|
||||||
buffer.FreeTail()
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnCompletePacket 完整帧回调
|
|
||||||
func (source *BaseGBSource) OnCompletePacket(index int, mediaType utils.AVMediaType, codec utils.AVCodecID, dts int64, pts int64, key bool) error {
|
|
||||||
buffer := source.FindOrCreatePacketBuffer(index, mediaType)
|
|
||||||
data := buffer.Fetch()
|
|
||||||
|
|
||||||
var packet utils.AVPacket
|
|
||||||
var stream_ utils.AVStream
|
|
||||||
var err error
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if packet == nil {
|
|
||||||
buffer.FreeTail()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if utils.AVMediaTypeAudio == mediaType {
|
|
||||||
stream_, packet, err = stream.ExtractAudioPacket(codec, source.audioStream == nil, data, pts, dts, index, 90000)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if stream_ != nil {
|
|
||||||
source.audioStream = stream_
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if source.videoStream == nil && !key {
|
|
||||||
log.Sugar.Errorf("skip non keyframes conn:%s", source.Conn.RemoteAddr())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
stream_, packet, err = stream.ExtractVideoPacket(codec, key, source.videoStream == nil, data, pts, dts, index, 90000)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if stream_ != nil {
|
|
||||||
source.videoStream = stream_
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if stream_ != nil {
|
|
||||||
source.OnDeMuxStream(stream_)
|
|
||||||
if len(source.OriginTracks()) >= source.deMuxerCtx.TrackCount() {
|
|
||||||
source.OnDeMuxStreamDone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
source.correctTimestamp(packet, dts, pts)
|
|
||||||
source.OnDeMuxPacket(packet)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 纠正国标推流的时间戳
|
// 纠正国标推流的时间戳
|
||||||
func (source *BaseGBSource) correctTimestamp(packet utils.AVPacket, dts, pts int64) {
|
func (source *BaseGBSource) correctTimestamp(packet *avformat.AVPacket, dts, pts int64) {
|
||||||
// dts和pts保持一致
|
// dts和pts保持一致
|
||||||
pts = int64(math.Max(float64(dts), float64(pts)))
|
pts = int64(math.Max(float64(dts), float64(pts)))
|
||||||
dts = pts
|
dts = pts
|
||||||
packet.SetPts(pts)
|
packet.Pts = pts
|
||||||
packet.SetDts(dts)
|
packet.Dts = dts
|
||||||
|
|
||||||
var lastTimestamp int64
|
var lastTimestamp int64
|
||||||
var lastCreatedTime int64
|
var lastCreatedTime int64
|
||||||
if utils.AVMediaTypeAudio == packet.MediaType() {
|
if utils.AVMediaTypeAudio == packet.MediaType {
|
||||||
lastTimestamp = source.audioTimestamp
|
lastTimestamp = source.audioTimestamp
|
||||||
lastCreatedTime = source.audioPacketCreatedTime
|
lastCreatedTime = source.audioPacketCreatedTime
|
||||||
} else if utils.AVMediaTypeVideo == packet.MediaType() {
|
} else if utils.AVMediaTypeVideo == packet.MediaType {
|
||||||
lastTimestamp = source.videoTimestamp
|
lastTimestamp = source.videoTimestamp
|
||||||
lastCreatedTime = source.videoPacketCreatedTime
|
lastCreatedTime = source.videoPacketCreatedTime
|
||||||
}
|
}
|
||||||
@@ -193,38 +134,38 @@ func (source *BaseGBSource) correctTimestamp(packet utils.AVPacket, dts, pts int
|
|||||||
duration = 0x1FFFFFFFF - lastTimestamp + pts
|
duration = 0x1FFFFFFFF - lastTimestamp + pts
|
||||||
if duration < 90000 {
|
if duration < 90000 {
|
||||||
// 处理正常溢出
|
// 处理正常溢出
|
||||||
packet.SetDuration(duration)
|
packet.Duration = duration
|
||||||
} else {
|
} else {
|
||||||
// 时间戳不正确
|
// 时间戳不正确
|
||||||
log.Sugar.Errorf("推流时间戳不正确, 使用系统时钟. ssrc:%d", source.ssrc)
|
log.Sugar.Errorf("推流时间戳不正确, 使用系统时钟. ssrc: %x", source.ssrc)
|
||||||
source.isSystemClock = true
|
source.isSystemClock = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
duration = pts - lastTimestamp
|
duration = pts - lastTimestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
packet.SetDuration(duration)
|
packet.Duration = duration
|
||||||
duration = packet.Duration(90000)
|
duration = packet.GetDuration(90000)
|
||||||
if duration < 0 || duration < 750 {
|
if duration < 0 || duration < 750 {
|
||||||
log.Sugar.Errorf("推流时间戳不正确, 使用系统时钟. ts: %d duration: %d source: %s ssrc: %d", pts, duration, source.ID, source.ssrc)
|
log.Sugar.Errorf("推流时间戳不正确, 使用系统时钟. ts: %d duration: %d source: %s ssrc: %x", pts, duration, source.ID, source.ssrc)
|
||||||
source.isSystemClock = true
|
source.isSystemClock = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 纠正时间戳
|
// 纠正时间戳
|
||||||
if source.isSystemClock && lastTimestamp != -1 {
|
if source.isSystemClock && lastTimestamp != -1 {
|
||||||
duration = (packet.CreatedTime() - lastCreatedTime) * 90
|
duration = (packet.CreatedTime - lastCreatedTime) * 90
|
||||||
packet.SetDts(lastTimestamp + duration)
|
packet.Dts = lastTimestamp + duration
|
||||||
packet.SetPts(lastTimestamp + duration)
|
packet.Pts = lastTimestamp + duration
|
||||||
packet.SetDuration(duration)
|
packet.Duration = duration
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.AVMediaTypeAudio == packet.MediaType() {
|
if utils.AVMediaTypeAudio == packet.MediaType {
|
||||||
source.audioTimestamp = packet.Pts()
|
source.audioTimestamp = packet.Pts
|
||||||
source.audioPacketCreatedTime = packet.CreatedTime()
|
source.audioPacketCreatedTime = packet.CreatedTime
|
||||||
} else if utils.AVMediaTypeVideo == packet.MediaType() {
|
} else if utils.AVMediaTypeVideo == packet.MediaType {
|
||||||
source.videoTimestamp = packet.Pts()
|
source.videoTimestamp = packet.Pts
|
||||||
source.videoPacketCreatedTime = packet.CreatedTime()
|
source.videoPacketCreatedTime = packet.CreatedTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,11 +190,6 @@ func (source *BaseGBSource) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source.PublishSource.Close()
|
source.PublishSource.Close()
|
||||||
|
|
||||||
if source.deMuxerCtx != nil {
|
|
||||||
source.deMuxerCtx.Close()
|
|
||||||
source.deMuxerCtx = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (source *BaseGBSource) SetConn(conn net.Conn) {
|
func (source *BaseGBSource) SetConn(conn net.Conn) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package gb28181
|
package gb28181
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,8 +13,8 @@ type ActiveSource struct {
|
|||||||
tcp *TCPClient
|
tcp *TCPClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a ActiveSource) Connect(remoteAddr *net.TCPAddr) error {
|
func (a *ActiveSource) Connect(remoteAddr *net.TCPAddr) error {
|
||||||
client, err := NewTCPClient(a.port, remoteAddr, &a)
|
client, err := NewTCPClient(a.port, remoteAddr, a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -22,7 +23,7 @@ func (a ActiveSource) Connect(remoteAddr *net.TCPAddr) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a ActiveSource) SetupType() SetupType {
|
func (a *ActiveSource) SetupType() SetupType {
|
||||||
return SetupActive
|
return SetupActive
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,5 +34,10 @@ func NewActiveSource() (*ActiveSource, int, error) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return &ActiveSource{port: port}, port, nil
|
return &ActiveSource{
|
||||||
|
PassiveSource: PassiveSource{
|
||||||
|
decoder: transport.NewLengthFieldFrameDecoder(0xFFFF, 2),
|
||||||
|
},
|
||||||
|
port: port,
|
||||||
|
}, port, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
package gb28181
|
package gb28181
|
||||||
|
|
||||||
import "github.com/lkmio/avformat/transport"
|
import "github.com/lkmio/transport"
|
||||||
|
|
||||||
type PassiveSource struct {
|
type PassiveSource struct {
|
||||||
BaseGBSource
|
BaseGBSource
|
||||||
decoder *transport.LengthFieldFrameDecoder
|
decoder *transport.LengthFieldFrameDecoder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input 重写stream.Source的Input函数, 主解析将会把推流数据交给PassiveSource处理
|
// Input 重写stream.Source的Input函数, 主协程把推流数据交给PassiveSource处理
|
||||||
func (p PassiveSource) Input(data []byte) error {
|
func (p *PassiveSource) Input(data []byte) error {
|
||||||
return p.decoder.Input(data)
|
_, err := DecodeGBRTPOverTCPPacket(data, p, p.decoder, nil, p.Conn)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PassiveSource) SetupType() SetupType {
|
func (p *PassiveSource) SetupType() SetupType {
|
||||||
return SetupPassive
|
return SetupPassive
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPassiveSource() *PassiveSource {
|
func NewPassiveSource() *PassiveSource {
|
||||||
return &PassiveSource{}
|
return &PassiveSource{
|
||||||
|
decoder: transport.NewLengthFieldFrameDecoder(0xFFFF, 2),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package gb28181
|
package gb28181
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package gb28181
|
package gb28181
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/transport"
|
"encoding/hex"
|
||||||
"github.com/lkmio/lkm/log"
|
"github.com/lkmio/lkm/log"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
)
|
||||||
@@ -49,11 +50,16 @@ func (T *TCPServer) OnPacket(conn net.Conn, data []byte) []byte {
|
|||||||
|
|
||||||
// 单端口推流时, 先解析出SSRC找到GBSource. 后序将推流数据交给stream.Source处理
|
// 单端口推流时, 先解析出SSRC找到GBSource. 后序将推流数据交给stream.Source处理
|
||||||
if session.source == nil {
|
if session.source == nil {
|
||||||
if err := session.decoder.Input(data); err != nil {
|
source, err := DecodeGBRTPOverTCPPacket(data, nil, session.decoder, T.filter, conn)
|
||||||
log.Sugar.Errorf("解析粘包数据失败 err: %s", err.Error())
|
if err != nil {
|
||||||
conn.Close()
|
log.Sugar.Errorf("解析rtp失败 err: %s conn: %s data: %s", err.Error(), conn.RemoteAddr().String(), hex.EncodeToString(data))
|
||||||
|
_ = conn.Close()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if source != nil {
|
||||||
|
session.Init(source)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 将流交给Source的主协程处理,主协程最终会调用PassiveSource的Input函数处理
|
// 将流交给Source的主协程处理,主协程最终会调用PassiveSource的Input函数处理
|
||||||
if session.source.SetupType() == SetupPassive {
|
if session.source.SetupType() == SetupPassive {
|
||||||
@@ -80,7 +86,7 @@ func NewTCPServer(filter Filter) (*TCPServer, error) {
|
|||||||
var err error
|
var err error
|
||||||
if stream.AppConfig.GB28181.IsMultiPort() {
|
if stream.AppConfig.GB28181.IsMultiPort() {
|
||||||
tcp = &transport.TCPServer{}
|
tcp = &transport.TCPServer{}
|
||||||
tcp, err = TransportManger.NewTCPServer(stream.AppConfig.ListenIP)
|
tcp, err = TransportManger.NewTCPServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package gb28181
|
package gb28181
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"fmt"
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/lkm/log"
|
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
@@ -13,20 +12,14 @@ import (
|
|||||||
type TCPSession struct {
|
type TCPSession struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
source GBSource
|
source GBSource
|
||||||
decoder *transport.LengthFieldFrameDecoder
|
|
||||||
receiveBuffer *stream.ReceiveBuffer
|
receiveBuffer *stream.ReceiveBuffer
|
||||||
|
decoder *transport.LengthFieldFrameDecoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TCPSession) Init(source GBSource) {
|
func (t *TCPSession) Init(source GBSource) {
|
||||||
t.source = source
|
t.source = source
|
||||||
// 创建收流缓冲区
|
// 创建收流缓冲区
|
||||||
t.receiveBuffer = stream.NewTCPReceiveBuffer()
|
t.receiveBuffer = stream.NewTCPReceiveBuffer()
|
||||||
// session关联到source后, 后续流数据都由source处理
|
|
||||||
if source.SetupType() == SetupPassive {
|
|
||||||
source.(*PassiveSource).decoder = t.decoder
|
|
||||||
} else {
|
|
||||||
source.(*ActiveSource).decoder = t.decoder
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TCPSession) Close() {
|
func (t *TCPSession) Close() {
|
||||||
@@ -35,52 +28,55 @@ func (t *TCPSession) Close() {
|
|||||||
t.source.Close()
|
t.source.Close()
|
||||||
t.source = nil
|
t.source = nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if t.decoder != nil {
|
func DecodeGBRTPOverTCPPacket(data []byte, source GBSource, decoder *transport.LengthFieldFrameDecoder, filter Filter, conn net.Conn) (GBSource, error) {
|
||||||
t.decoder.Close()
|
length := len(data)
|
||||||
t.decoder = nil
|
for i := 0; i < length; {
|
||||||
|
n, bytes, err := decoder.Input(data[i:])
|
||||||
|
if err != nil {
|
||||||
|
return source, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i += n
|
||||||
|
|
||||||
|
// 单端口模式,ssrc匹配source
|
||||||
|
if source == nil || stream.SessionStateHandshakeSuccess == source.State() {
|
||||||
|
packet := rtp.Packet{}
|
||||||
|
if err := packet.Unmarshal(bytes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if source == nil {
|
||||||
|
source = filter.FindSource(packet.SSRC)
|
||||||
|
}
|
||||||
|
|
||||||
|
if source == nil {
|
||||||
|
// ssrc 匹配不到Source
|
||||||
|
return nil, fmt.Errorf("gb28181推流失败 ssrc: %x 匹配不到source", packet.SSRC)
|
||||||
|
}
|
||||||
|
|
||||||
|
if stream.SessionStateHandshakeSuccess == source.State() {
|
||||||
|
source.PreparePublish(conn, packet.SSRC, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是单端口推流, 并且刚才与source绑定, 此时正位于网络收流协程, 否则都位于主协程
|
||||||
|
if source.SetupType() == SetupPassive {
|
||||||
|
source.(*PassiveSource).BaseGBSource.Input(bytes)
|
||||||
|
} else {
|
||||||
|
source.(*ActiveSource).BaseGBSource.Input(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return source, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTCPSession(conn net.Conn, filter Filter) *TCPSession {
|
func NewTCPSession(conn net.Conn, filter Filter) *TCPSession {
|
||||||
session := &TCPSession{
|
session := &TCPSession{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
|
// filter: filter,
|
||||||
|
decoder: transport.NewLengthFieldFrameDecoder(0xFFFF, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建粘包解码器, 并设置解粘包处理回调
|
|
||||||
session.decoder = transport.NewLengthFieldFrameDecoder(0xFFFF, 2, func(bytes []byte) {
|
|
||||||
// 单端口模式,ssrc匹配source
|
|
||||||
if session.source == nil || stream.SessionStateHandshakeSuccess == session.source.State() {
|
|
||||||
packet := rtp.Packet{}
|
|
||||||
if err := packet.Unmarshal(bytes); err != nil {
|
|
||||||
log.Sugar.Errorf("解析rtp失败 err: %s conn: %s data: %s", err.Error(), conn.RemoteAddr().String(), hex.EncodeToString(bytes))
|
|
||||||
conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
source := filter.FindSource(packet.SSRC)
|
|
||||||
if source == nil {
|
|
||||||
// 匹配不到Source, 直接关闭连接
|
|
||||||
log.Sugar.Errorf("gb28181推流失败 ssrc: %x 匹配不到source conn: %s data: %s", packet.SSRC, session.conn.RemoteAddr().String(), hex.EncodeToString(bytes))
|
|
||||||
conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
session.Init(source)
|
|
||||||
|
|
||||||
if stream.SessionStateHandshakeSuccess == session.source.State() {
|
|
||||||
session.source.PreparePublish(session.conn, packet.SSRC, session.source)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是单端口推流, 并且刚才与source绑定, 此时正位于网络收流协程, 否则都位于主协程
|
|
||||||
if session.source.SetupType() == SetupPassive {
|
|
||||||
session.source.(*PassiveSource).BaseGBSource.Input(bytes)
|
|
||||||
} else {
|
|
||||||
session.source.(*ActiveSource).BaseGBSource.Input(bytes)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 多端口收流, Source已知, 直接初始化Session
|
// 多端口收流, Source已知, 直接初始化Session
|
||||||
if stream.AppConfig.GB28181.IsMultiPort() {
|
if stream.AppConfig.GB28181.IsMultiPort() {
|
||||||
session.Init(filter.(*singleFilter).source)
|
session.Init(filter.(*singleFilter).source)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package gb28181
|
package gb28181
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/lkm/log"
|
"github.com/lkmio/lkm/log"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -64,7 +64,7 @@ func NewUDPServer(filter Filter) (*UDPServer, error) {
|
|||||||
var udp *transport.UDPServer
|
var udp *transport.UDPServer
|
||||||
var err error
|
var err error
|
||||||
if stream.AppConfig.GB28181.IsMultiPort() {
|
if stream.AppConfig.GB28181.IsMultiPort() {
|
||||||
udp, err = TransportManger.NewUDPServer(stream.AppConfig.ListenIP)
|
udp, err = TransportManger.NewUDPServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
25
go.mod
25
go.mod
@@ -1,13 +1,19 @@
|
|||||||
module github.com/lkmio/lkm
|
module github.com/lkmio/lkm
|
||||||
|
|
||||||
require github.com/lkmio/avformat v0.0.0
|
require (
|
||||||
|
github.com/lkmio/mpeg v0.0.0
|
||||||
|
github.com/lkmio/flv v0.0.0
|
||||||
|
github.com/lkmio/rtmp v0.0.0
|
||||||
|
github.com/lkmio/transport v0.0.0
|
||||||
|
github.com/lkmio/rtp v0.0.0
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/gorilla/websocket v1.5.1
|
github.com/gorilla/websocket v1.5.1
|
||||||
|
github.com/lkmio/avformat v0.0.0
|
||||||
github.com/natefinch/lumberjack v2.0.0+incompatible
|
github.com/natefinch/lumberjack v2.0.0+incompatible
|
||||||
github.com/pion/rtcp v1.2.14
|
github.com/pion/interceptor v0.1.25
|
||||||
github.com/pion/rtp v1.8.5
|
|
||||||
github.com/pion/webrtc/v3 v3.2.29
|
github.com/pion/webrtc/v3 v3.2.29
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
||||||
@@ -24,10 +30,11 @@ require (
|
|||||||
github.com/pion/datachannel v1.5.5 // indirect
|
github.com/pion/datachannel v1.5.5 // indirect
|
||||||
github.com/pion/dtls/v2 v2.2.7 // indirect
|
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||||
github.com/pion/ice/v2 v2.3.13 // indirect
|
github.com/pion/ice/v2 v2.3.13 // indirect
|
||||||
github.com/pion/interceptor v0.1.25 // indirect
|
|
||||||
github.com/pion/logging v0.2.2 // indirect
|
github.com/pion/logging v0.2.2 // indirect
|
||||||
github.com/pion/mdns v0.0.12 // indirect
|
github.com/pion/mdns v0.0.12 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
|
github.com/pion/rtcp v1.2.12 // indirect
|
||||||
|
github.com/pion/rtp v1.8.5 // indirect
|
||||||
github.com/pion/sctp v1.8.12 // indirect
|
github.com/pion/sctp v1.8.12 // indirect
|
||||||
github.com/pion/sdp/v3 v3.0.8 // indirect
|
github.com/pion/sdp/v3 v3.0.8 // indirect
|
||||||
github.com/pion/srtp/v2 v2.0.18 // indirect
|
github.com/pion/srtp/v2 v2.0.18 // indirect
|
||||||
@@ -47,4 +54,14 @@ require (
|
|||||||
|
|
||||||
replace github.com/lkmio/avformat => ../avformat
|
replace github.com/lkmio/avformat => ../avformat
|
||||||
|
|
||||||
|
replace github.com/lkmio/mpeg => ../mpeg
|
||||||
|
|
||||||
|
replace github.com/lkmio/flv => ../flv
|
||||||
|
|
||||||
|
replace github.com/lkmio/rtmp => ../rtmp
|
||||||
|
|
||||||
|
replace github.com/lkmio/transport => ../transport
|
||||||
|
|
||||||
|
replace github.com/lkmio/rtp => ../rtp
|
||||||
|
|
||||||
go 1.19
|
go 1.19
|
||||||
|
|||||||
@@ -2,32 +2,29 @@ package hls
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lkmio/avformat/libhls"
|
"github.com/lkmio/avformat"
|
||||||
"github.com/lkmio/avformat/libmpeg"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/log"
|
"github.com/lkmio/lkm/log"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/mpeg"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tsContext struct {
|
type TransStream struct {
|
||||||
|
stream.BaseTransStream
|
||||||
|
muxer *mpeg.TSMuxer
|
||||||
|
ctx struct {
|
||||||
segmentSeq int // 切片序号
|
segmentSeq int // 切片序号
|
||||||
writeBuffer []byte // ts流的缓冲区, 由TSMuxer使用. 减少用户态和内核态交互,以及磁盘IO频率
|
writeBuffer []byte // ts流的缓冲区, 由TSMuxer使用. 减少用户态和内核态交互,以及磁盘IO频率
|
||||||
writeBufferSize int // 已缓存TS流大小
|
writeBufferSize int // 已缓存TS流大小
|
||||||
|
|
||||||
url string // @See TransStream.tsUrl
|
url string // @See TransStream.tsUrl
|
||||||
path string // ts切片位于磁盘中的绝对路径
|
path string // ts切片位于磁盘中的绝对路径
|
||||||
file *os.File // ts切片文件句柄
|
file *os.File // ts切片文件句柄
|
||||||
}
|
}
|
||||||
|
|
||||||
type TransStream struct {
|
M3U8Writer stream.M3U8Writer
|
||||||
stream.BaseTransStream
|
|
||||||
muxer libmpeg.TSMuxer
|
|
||||||
context *tsContext
|
|
||||||
|
|
||||||
M3U8Writer libhls.M3U8Writer
|
|
||||||
m3u8Name string // m3u8文件名
|
m3u8Name string // m3u8文件名
|
||||||
m3u8File *os.File // m3u8文件句柄
|
m3u8File *os.File // m3u8文件句柄
|
||||||
dir string // m3u8文件父目录
|
dir string // m3u8文件父目录
|
||||||
@@ -40,16 +37,12 @@ type TransStream struct {
|
|||||||
PlaylistFormat *string // 位于内存中的播放列表,每个sink都引用指针地址.
|
PlaylistFormat *string // 位于内存中的播放列表,每个sink都引用指针地址.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransStream) Input(packet utils.AVPacket) ([][]byte, int64, bool, error) {
|
func (t *TransStream) Input(packet *avformat.AVPacket) ([][]byte, int64, bool, error) {
|
||||||
if packet.Index() >= t.muxer.TrackCount() {
|
|
||||||
return nil, -1, false, fmt.Errorf("track not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建一下个切片
|
// 创建一下个切片
|
||||||
// 已缓存时长>=指定时长, 如果存在视频, 还需要等遇到关键帧才切片
|
// 已缓存时长>=指定时长, 如果存在视频, 还需要等遇到关键帧才切片
|
||||||
if (!t.ExistVideo || utils.AVMediaTypeVideo == packet.MediaType() && packet.KeyFrame()) && float32(t.muxer.Duration())/90000 >= float32(t.duration) {
|
if (!t.ExistVideo || utils.AVMediaTypeVideo == packet.MediaType && packet.Key) && float32(t.muxer.Duration())/90000 >= float32(t.duration) {
|
||||||
// 保存当前切片文件
|
// 保存当前切片文件
|
||||||
if t.context.file != nil {
|
if t.ctx.file != nil {
|
||||||
err := t.flushSegment(false)
|
err := t.flushSegment(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, false, err
|
return nil, -1, false, err
|
||||||
@@ -64,12 +57,23 @@ func (t *TransStream) Input(packet utils.AVPacket) ([][]byte, int64, bool, error
|
|||||||
|
|
||||||
pts := packet.ConvertPts(90000)
|
pts := packet.ConvertPts(90000)
|
||||||
dts := packet.ConvertDts(90000)
|
dts := packet.ConvertDts(90000)
|
||||||
if utils.AVMediaTypeVideo == packet.MediaType() {
|
data := packet.Data
|
||||||
t.muxer.Input(packet.Index(), packet.AnnexBPacketData(t.BaseTransStream.Tracks[packet.Index()].Stream), pts, dts, packet.KeyFrame())
|
if utils.AVMediaTypeVideo == packet.MediaType {
|
||||||
} else {
|
data = avformat.AVCCPacket2AnnexB(t.BaseTransStream.Tracks[packet.Index].Stream, packet)
|
||||||
t.muxer.Input(packet.Index(), packet.Data(), pts, dts, packet.KeyFrame())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
length := len(data)
|
||||||
|
capacity := cap(t.ctx.writeBuffer)
|
||||||
|
for i := 0; i < length; {
|
||||||
|
if capacity-t.ctx.writeBufferSize < mpeg.TsPacketSize {
|
||||||
|
_, _ = t.ctx.file.Write(t.ctx.writeBuffer[:t.ctx.writeBufferSize])
|
||||||
|
t.ctx.writeBufferSize = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes := t.ctx.writeBuffer[t.ctx.writeBufferSize : t.ctx.writeBufferSize+mpeg.TsPacketSize]
|
||||||
|
i += t.muxer.Input(bytes, packet.Index, data[i:], length, dts, pts, packet.Key, i == 0)
|
||||||
|
t.ctx.writeBufferSize += mpeg.TsPacketSize
|
||||||
|
}
|
||||||
return nil, -1, true, nil
|
return nil, -1, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,42 +83,31 @@ func (t *TransStream) AddTrack(track *stream.Track) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if utils.AVMediaTypeVideo == track.Stream.Type() {
|
if utils.AVMediaTypeVideo == track.Stream.MediaType {
|
||||||
data := track.Stream.CodecParameters().AnnexBExtraData()
|
data := track.Stream.CodecParameters.AnnexBExtraData()
|
||||||
_, err = t.muxer.AddTrack(track.Stream.Type(), track.Stream.CodecId(), data)
|
_, err = t.muxer.AddTrack(track.Stream.MediaType, track.Stream.CodecID, data)
|
||||||
} else {
|
} else {
|
||||||
_, err = t.muxer.AddTrack(track.Stream.Type(), track.Stream.CodecId(), track.Stream.Extra())
|
_, err = t.muxer.AddTrack(track.Stream.MediaType, track.Stream.CodecID, track.Stream.Data)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransStream) WriteHeader() error {
|
func (t *TransStream) WriteHeader() error {
|
||||||
|
//if packet.Index >= t.muxer.TrackCount() {
|
||||||
|
// return nil, -1, false, fmt.Errorf("track not available")
|
||||||
|
//}
|
||||||
return t.createSegment()
|
return t.createSegment()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransStream) onTSWrite(data []byte) {
|
|
||||||
t.context.writeBufferSize += len(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TransStream) onTSAlloc(size int) []byte {
|
|
||||||
n := len(t.context.writeBuffer) - t.context.writeBufferSize
|
|
||||||
if n < size {
|
|
||||||
_, _ = t.context.file.Write(t.context.writeBuffer[:t.context.writeBufferSize])
|
|
||||||
t.context.writeBufferSize = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return t.context.writeBuffer[t.context.writeBufferSize : t.context.writeBufferSize+size]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入新的TS切片,更新M3U8
|
// 写入新的TS切片,更新M3U8
|
||||||
func (t *TransStream) flushSegment(end bool) error {
|
func (t *TransStream) flushSegment(end bool) error {
|
||||||
// 写入剩余TS包
|
// 写入剩余TS包
|
||||||
if t.context.writeBufferSize > 0 {
|
if t.ctx.writeBufferSize > 0 {
|
||||||
_, _ = t.context.file.Write(t.context.writeBuffer[:t.context.writeBufferSize])
|
_, _ = t.ctx.file.Write(t.ctx.writeBuffer[:t.ctx.writeBufferSize])
|
||||||
t.context.writeBufferSize = 0
|
t.ctx.writeBufferSize = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := t.context.file.Close(); err != nil {
|
if err := t.ctx.file.Close(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +118,7 @@ func (t *TransStream) flushSegment(end bool) error {
|
|||||||
|
|
||||||
// 更新m3u8列表
|
// 更新m3u8列表
|
||||||
duration := float32(t.muxer.Duration()) / 90000
|
duration := float32(t.muxer.Duration()) / 90000
|
||||||
t.M3U8Writer.AddSegment(duration, t.context.url, t.context.segmentSeq, t.context.path)
|
t.M3U8Writer.AddSegment(duration, t.ctx.url, t.ctx.segmentSeq, t.ctx.path)
|
||||||
m3u8Txt := t.M3U8Writer.String()
|
m3u8Txt := t.M3U8Writer.String()
|
||||||
|
|
||||||
//if end {
|
//if end {
|
||||||
@@ -163,21 +156,21 @@ func (t *TransStream) createSegment() error {
|
|||||||
t.muxer.Reset()
|
t.muxer.Reset()
|
||||||
|
|
||||||
var tsFile *os.File
|
var tsFile *os.File
|
||||||
startSeq := t.context.segmentSeq + 1
|
startSeq := t.ctx.segmentSeq + 1
|
||||||
for {
|
for {
|
||||||
tsName := fmt.Sprintf(t.tsFormat, startSeq)
|
tsName := fmt.Sprintf(t.tsFormat, startSeq)
|
||||||
// ts文件
|
// ts文件
|
||||||
t.context.path = fmt.Sprintf("%s/%s", t.dir, tsName)
|
t.ctx.path = fmt.Sprintf("%s/%s", t.dir, tsName)
|
||||||
// m3u8列表中切片的url
|
// m3u8列表中切片的url
|
||||||
t.context.url = fmt.Sprintf("%s%s", t.tsUrl, tsName)
|
t.ctx.url = fmt.Sprintf("%s%s", t.tsUrl, tsName)
|
||||||
|
|
||||||
file, err := os.OpenFile(t.context.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
file, err := os.OpenFile(t.ctx.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tsFile = file
|
tsFile = file
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Sugar.Errorf("创建ts切片文件失败 err:%s path:%s", err.Error(), t.context.path)
|
log.Sugar.Errorf("创建ts切片文件失败 err:%s path:%s", err.Error(), t.ctx.path)
|
||||||
if os.IsPermission(err) || os.IsTimeout(err) || os.IsNotExist(err) {
|
if os.IsPermission(err) || os.IsTimeout(err) || os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -186,19 +179,24 @@ func (t *TransStream) createSegment() error {
|
|||||||
startSeq++
|
startSeq++
|
||||||
}
|
}
|
||||||
|
|
||||||
t.context.segmentSeq = startSeq
|
t.ctx.segmentSeq = startSeq
|
||||||
t.context.file = tsFile
|
t.ctx.file = tsFile
|
||||||
_ = t.muxer.WriteHeader()
|
n, err := t.muxer.WriteHeader(t.ctx.writeBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.ctx.writeBufferSize = n
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransStream) Close() ([][]byte, int64, error) {
|
func (t *TransStream) Close() ([][]byte, int64, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if t.context.file != nil {
|
if t.ctx.file != nil {
|
||||||
err = t.flushSegment(true)
|
err = t.flushSegment(true)
|
||||||
err = t.context.file.Close()
|
err = t.ctx.file.Close()
|
||||||
t.context.file = nil
|
t.ctx.file = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.muxer != nil {
|
if t.muxer != nil {
|
||||||
@@ -244,7 +242,7 @@ func DeleteOldSegments(id string) {
|
|||||||
// @Params parentDir 保存切片的绝对路径. mu38和ts切片放在同一目录下, 目录地址使用parentDir+urlPrefix
|
// @Params parentDir 保存切片的绝对路径. mu38和ts切片放在同一目录下, 目录地址使用parentDir+urlPrefix
|
||||||
// @Params segmentDuration 单个切片时长
|
// @Params segmentDuration 单个切片时长
|
||||||
// @Params playlistLength 缓存多少个切片
|
// @Params playlistLength 缓存多少个切片
|
||||||
func NewTransStream(dir, m3u8Name, tsFormat, tsUrl string, segmentDuration, playlistLength int, seq int, playlistFormat *string, writer libhls.M3U8Writer) (stream.TransStream, error) {
|
func NewTransStream(dir, m3u8Name, tsFormat, tsUrl string, segmentDuration, playlistLength int, seq int, playlistFormat *string, writer stream.M3U8Writer) (stream.TransStream, error) {
|
||||||
// 创建文件夹
|
// 创建文件夹
|
||||||
m3u8Path := fmt.Sprintf("%s/%s", dir, m3u8Name)
|
m3u8Path := fmt.Sprintf("%s/%s", dir, m3u8Name)
|
||||||
if err := os.MkdirAll(filepath.Dir(m3u8Path), 0666); err != nil {
|
if err := os.MkdirAll(filepath.Dir(m3u8Path), 0666); err != nil {
|
||||||
@@ -271,7 +269,7 @@ func NewTransStream(dir, m3u8Name, tsFormat, tsUrl string, segmentDuration, play
|
|||||||
if writer != nil {
|
if writer != nil {
|
||||||
transStream.M3U8Writer = writer
|
transStream.M3U8Writer = writer
|
||||||
} else {
|
} else {
|
||||||
transStream.M3U8Writer = libhls.NewM3U8Writer(playlistLength)
|
transStream.M3U8Writer = stream.NewM3U8Writer(playlistLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
if playlistFormat != nil {
|
if playlistFormat != nil {
|
||||||
@@ -281,16 +279,11 @@ func NewTransStream(dir, m3u8Name, tsFormat, tsUrl string, segmentDuration, play
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建TS封装器
|
// 创建TS封装器
|
||||||
muxer := libmpeg.NewTSMuxer()
|
muxer := mpeg.NewTSMuxer()
|
||||||
muxer.SetWriteHandler(transStream.onTSWrite)
|
|
||||||
muxer.SetAllocHandler(transStream.onTSAlloc)
|
|
||||||
|
|
||||||
// ts封装上下文对象
|
// 初始化ts封装上下文对象
|
||||||
transStream.context = &tsContext{
|
transStream.ctx.segmentSeq = seq
|
||||||
segmentSeq: seq,
|
transStream.ctx.writeBuffer = make([]byte, 1024*1024)
|
||||||
writeBuffer: make([]byte, 1024*1024),
|
|
||||||
writeBufferSize: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
transStream.muxer = muxer
|
transStream.muxer = muxer
|
||||||
transStream.m3u8File = file
|
transStream.m3u8File = file
|
||||||
@@ -301,7 +294,7 @@ func NewTransStream(dir, m3u8Name, tsFormat, tsUrl string, segmentDuration, play
|
|||||||
func TransStreamFactory(source stream.Source, protocol stream.TransStreamProtocol, tracks []*stream.Track) (stream.TransStream, error) {
|
func TransStreamFactory(source stream.Source, protocol stream.TransStreamProtocol, tracks []*stream.Track) (stream.TransStream, error) {
|
||||||
id := source.GetID()
|
id := source.GetID()
|
||||||
|
|
||||||
var writer libhls.M3U8Writer
|
var writer stream.M3U8Writer
|
||||||
var playlistFormat *string
|
var playlistFormat *string
|
||||||
startSeq := -1
|
startSeq := -1
|
||||||
|
|
||||||
|
|||||||
97
jt1078/jt_demuxer.go
Normal file
97
jt1078/jt_demuxer.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package jt1078
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/lkmio/avformat"
|
||||||
|
"github.com/lkmio/avformat/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Demuxer struct {
|
||||||
|
avformat.BaseDemuxer
|
||||||
|
prevPacket *Packet
|
||||||
|
sim string
|
||||||
|
channel int
|
||||||
|
lastError string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Demuxer) ProcessPrevPacket() error {
|
||||||
|
var codec utils.AVCodecID
|
||||||
|
index := d.FindBufferIndex(int(d.prevPacket.pt))
|
||||||
|
bytes, err := d.BaseDemuxer.DataPipeline.Feat(index)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else /*if d.prevPacket.packetType > AudioFrameMark {
|
||||||
|
// 透传数据, 丢弃
|
||||||
|
d.DataPipeline.DiscardBackPacket(index)
|
||||||
|
return nil
|
||||||
|
} else*/if err, codec = PT2CodecID(d.prevPacket.pt); err != nil {
|
||||||
|
d.BaseDemuxer.DataPipeline.DiscardBackPacket(index)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.prevPacket.packetType == AudioFrameMark {
|
||||||
|
d.OnAudioPacket(index, codec, bytes, int64(d.prevPacket.ts))
|
||||||
|
} else if d.prevPacket.packetType < AudioFrameMark {
|
||||||
|
// 视频帧
|
||||||
|
d.OnVideoPacket(index, codec, bytes, avformat.IsKeyFrame(codec, bytes), int64(d.prevPacket.ts), int64(d.prevPacket.ts), avformat.PacketTypeAnnexB)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !d.Completed && d.Tracks.Size() > 1 {
|
||||||
|
d.ProbeComplete()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Demuxer) Input(data []byte) (int, error) {
|
||||||
|
packet := Packet{}
|
||||||
|
if err := packet.Unmarshal(data); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if len(packet.payload) == 0 {
|
||||||
|
// 过滤空数据
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果时间戳或者负载类型发生变化, 认为是新的音视频帧,处理前一包,创建AVPacket,回调给PublishSource。
|
||||||
|
// 分包标记可能不靠谱
|
||||||
|
if d.prevPacket != nil && (d.prevPacket.ts != packet.ts || d.prevPacket.pt != packet.pt) {
|
||||||
|
err := d.ProcessPrevPacket()
|
||||||
|
if err != nil && err.Error() != d.lastError {
|
||||||
|
println(err.Error())
|
||||||
|
d.lastError = err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.prevPacket == nil {
|
||||||
|
d.prevPacket = &Packet{}
|
||||||
|
d.sim = packet.simNumber
|
||||||
|
d.channel = int(packet.channelNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mediaType utils.AVMediaType
|
||||||
|
if packet.packetType < AudioFrameMark {
|
||||||
|
mediaType = utils.AVMediaTypeVideo
|
||||||
|
} else if packet.packetType == AudioFrameMark {
|
||||||
|
mediaType = utils.AVMediaTypeAudio
|
||||||
|
} else {
|
||||||
|
// 透传数据, 丢弃
|
||||||
|
return len(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
index := d.FindBufferIndex(int(packet.pt))
|
||||||
|
_, err := d.DataPipeline.Write(packet.payload, index, mediaType)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*d.prevPacket = packet
|
||||||
|
return len(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDemuxer() *Demuxer {
|
||||||
|
return &Demuxer{
|
||||||
|
BaseDemuxer: avformat.BaseDemuxer{
|
||||||
|
DataPipeline: &avformat.StreamsBuffer{},
|
||||||
|
Name: "jt1078", // vob
|
||||||
|
AutoFree: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
120
jt1078/jt_packet.go
Normal file
120
jt1078/jt_packet.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package jt1078
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"github.com/lkmio/avformat/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
VideoIFrameMark = 0b000
|
||||||
|
VideoPFrameMark = 0b001
|
||||||
|
VideoBFrameMark = 0b010
|
||||||
|
AudioFrameMark = 0b011
|
||||||
|
TransmissionDataMark = 0b100
|
||||||
|
|
||||||
|
PTVideoH264 = 98
|
||||||
|
PTVideoH265 = 99
|
||||||
|
PTVideoAVS = 100
|
||||||
|
PTVideoSVAC = 101
|
||||||
|
|
||||||
|
PTAudioG711A = 6
|
||||||
|
PTAudioG711U = 7
|
||||||
|
PTAudioG726 = 8
|
||||||
|
PTAudioG729A = 9
|
||||||
|
PTAudioAAC = 19
|
||||||
|
PTAudioMP3 = 25
|
||||||
|
PTAudioADPCMA = 26
|
||||||
|
)
|
||||||
|
|
||||||
|
type Packet struct {
|
||||||
|
pt byte
|
||||||
|
packetType byte
|
||||||
|
ts uint64
|
||||||
|
subMark byte
|
||||||
|
simNumber string
|
||||||
|
channelNumber byte
|
||||||
|
payload []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Packet) Unmarshal(data []byte) error {
|
||||||
|
if len(data) < 12 {
|
||||||
|
return fmt.Errorf("invaild data")
|
||||||
|
}
|
||||||
|
|
||||||
|
packetType := data[11] >> 4 & 0x0F
|
||||||
|
// 忽略透传数据
|
||||||
|
if TransmissionDataMark == packetType {
|
||||||
|
return fmt.Errorf("invaild data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 忽略低于最低长度的数据包
|
||||||
|
if (AudioFrameMark == packetType && len(data) < 26) || (AudioFrameMark == packetType && len(data) < 22) {
|
||||||
|
return fmt.Errorf("invaild data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// x扩展位,固定为0
|
||||||
|
_ = data[0] >> 4 & 0x1
|
||||||
|
pt := data[1] & 0x7F
|
||||||
|
// seq
|
||||||
|
_ = binary.BigEndian.Uint16(data[2:])
|
||||||
|
|
||||||
|
var simNumber string
|
||||||
|
for i := 4; i < 10; i++ {
|
||||||
|
simNumber += fmt.Sprintf("%02d", data[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// channel
|
||||||
|
channelNumber := data[10]
|
||||||
|
// subMark
|
||||||
|
subMark := data[11] & 0x0F
|
||||||
|
// 时间戳,单位ms
|
||||||
|
var ts uint64
|
||||||
|
n := 12
|
||||||
|
if TransmissionDataMark != packetType {
|
||||||
|
ts = binary.BigEndian.Uint64(data[n:])
|
||||||
|
n += 8
|
||||||
|
}
|
||||||
|
|
||||||
|
if AudioFrameMark > packetType {
|
||||||
|
// iFrameInterval
|
||||||
|
_ = binary.BigEndian.Uint16(data[n:])
|
||||||
|
n += 2
|
||||||
|
// lastFrameInterval
|
||||||
|
_ = binary.BigEndian.Uint16(data[n:])
|
||||||
|
n += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// size
|
||||||
|
_ = binary.BigEndian.Uint16(data[n:])
|
||||||
|
n += 2
|
||||||
|
|
||||||
|
p.pt = pt
|
||||||
|
p.packetType = packetType
|
||||||
|
p.ts = ts
|
||||||
|
p.simNumber = simNumber
|
||||||
|
p.channelNumber = channelNumber
|
||||||
|
p.subMark = subMark
|
||||||
|
p.payload = data[n:]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PT2CodecID(pt byte) (error, utils.AVCodecID) {
|
||||||
|
switch pt {
|
||||||
|
case PTVideoH264:
|
||||||
|
return nil, utils.AVCodecIdH264
|
||||||
|
case PTVideoH265:
|
||||||
|
return nil, utils.AVCodecIdH265
|
||||||
|
case PTAudioG711A:
|
||||||
|
return nil, utils.AVCodecIdPCMALAW
|
||||||
|
case PTAudioG711U:
|
||||||
|
return nil, utils.AVCodecIdPCMMULAW
|
||||||
|
case PTAudioAAC:
|
||||||
|
return nil, utils.AVCodecIdAAC
|
||||||
|
//case PTAudioADPCMA:
|
||||||
|
// return nil, utils.AVCodecIdADPCMAFC
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("the codec %d is not implemented", pt), utils.AVCodecIdNONE
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package jt1078
|
package jt1078
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,86 +1,46 @@
|
|||||||
package jt1078
|
package jt1078
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"github.com/lkmio/avformat/collections"
|
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/log"
|
"github.com/lkmio/lkm/log"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
VideoIFrameMark = 0b000
|
|
||||||
VideoPFrameMark = 0b001
|
|
||||||
VideoBFrameMark = 0b010
|
|
||||||
AudioFrameMark = 0b011
|
|
||||||
TransmissionDataMark = 0b100
|
|
||||||
|
|
||||||
PTVideoH264 = 98
|
|
||||||
PTVideoH265 = 99
|
|
||||||
PTVideoAVS = 100
|
|
||||||
PTVideoSVAC = 101
|
|
||||||
|
|
||||||
PTAudioG711A = 6
|
|
||||||
PTAudioG711U = 7
|
|
||||||
PTAudioG726 = 8
|
|
||||||
PTAudioG729A = 9
|
|
||||||
PTAudioAAC = 19
|
|
||||||
PTAudioMP3 = 25
|
|
||||||
PTAudioADPCMA = 26
|
|
||||||
)
|
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
stream.PublishSource
|
stream.PublishSource
|
||||||
|
|
||||||
phone string
|
|
||||||
decoder *transport.DelimiterFrameDecoder
|
decoder *transport.DelimiterFrameDecoder
|
||||||
|
|
||||||
audioIndex int
|
|
||||||
videoIndex int
|
|
||||||
audioStream utils.AVStream
|
|
||||||
videoStream utils.AVStream
|
|
||||||
audioBuffer collections.MemoryPool
|
|
||||||
videoBuffer collections.MemoryPool
|
|
||||||
rtpPacket *RtpPacket
|
|
||||||
receiveBuffer *stream.ReceiveBuffer
|
receiveBuffer *stream.ReceiveBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
type RtpPacket struct {
|
func (s *Session) Input(data []byte) error {
|
||||||
pt byte
|
var n int
|
||||||
packetType byte
|
for length := len(data); n < length; {
|
||||||
ts uint64
|
i, bytes, err := s.decoder.Input(data[n:])
|
||||||
subMark byte
|
|
||||||
simNumber string
|
|
||||||
channelNumber byte
|
|
||||||
|
|
||||||
payload []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) OnJtPTPPacket(data []byte) {
|
|
||||||
packet, err := read1078RTPPacket(data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
|
} else if len(bytes) < 1 {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过滤空数据
|
n += i
|
||||||
if len(packet.payload) == 0 {
|
demuxer := s.TransDemuxer.(*Demuxer)
|
||||||
return
|
firstOfPacket := demuxer.prevPacket == nil
|
||||||
|
_, err = demuxer.Input(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 首包处理, hook通知
|
// 首包处理, hook通知
|
||||||
if s.rtpPacket == nil {
|
if firstOfPacket && demuxer.prevPacket != nil {
|
||||||
s.SetID(packet.simNumber + "/" + strconv.Itoa(int(packet.channelNumber)))
|
s.SetID(demuxer.sim + "/" + strconv.Itoa(demuxer.channel))
|
||||||
s.rtpPacket = &RtpPacket{}
|
|
||||||
*s.rtpPacket = packet
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_, state := stream.PreparePublishSource(s, true)
|
_, state := stream.PreparePublishSource(s, true)
|
||||||
if utils.HookStateOK != state {
|
if utils.HookStateOK != state {
|
||||||
log.Sugar.Errorf("1078推流失败 source: %s", s.phone)
|
log.Sugar.Errorf("1078推流失败 source: %s", demuxer.sim)
|
||||||
|
|
||||||
if s.Conn != nil {
|
if s.Conn != nil {
|
||||||
s.Conn.Close()
|
s.Conn.Close()
|
||||||
@@ -88,59 +48,9 @@ func (s *Session) OnJtPTPPacket(data []byte) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果时间戳或者负载类型发生变化, 认为是新的音视频帧,处理前一包,创建AVPacket,回调给PublishSource。
|
|
||||||
// 分包标记可能不靠谱
|
|
||||||
if s.rtpPacket.ts != packet.ts || s.rtpPacket.pt != packet.pt {
|
|
||||||
if s.rtpPacket.packetType == AudioFrameMark && s.audioBuffer != nil {
|
|
||||||
if err := s.processAudioPacket(s.rtpPacket.pt, s.rtpPacket.packetType, s.rtpPacket.ts, s.audioBuffer.Fetch(), s.audioIndex); err != nil {
|
|
||||||
log.Sugar.Errorf("处理音频包失败 phone:%s err:%s", s.phone, err.Error())
|
|
||||||
s.audioBuffer.FreeTail()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*s.rtpPacket = packet
|
return nil
|
||||||
} else if s.rtpPacket.packetType < AudioFrameMark && s.videoBuffer != nil {
|
|
||||||
if err := s.processVideoPacket(s.rtpPacket.pt, s.rtpPacket.packetType, s.rtpPacket.ts, s.videoBuffer.Fetch(), s.videoIndex); err != nil {
|
|
||||||
log.Sugar.Errorf("处理视频包失败 phone:%s err:%s", s.phone, err.Error())
|
|
||||||
s.videoBuffer.FreeTail()
|
|
||||||
}
|
|
||||||
|
|
||||||
*s.rtpPacket = packet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 部分音视频帧
|
|
||||||
if packet.packetType == AudioFrameMark {
|
|
||||||
if s.audioBuffer == nil {
|
|
||||||
if s.videoIndex == 0 && s.audioIndex == 0 {
|
|
||||||
s.videoIndex = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建音频的AVPacket缓冲区
|
|
||||||
s.audioBuffer = s.FindOrCreatePacketBuffer(s.audioIndex, utils.AVMediaTypeAudio)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将部分音频帧写入缓冲区
|
|
||||||
s.audioBuffer.TryMark()
|
|
||||||
s.audioBuffer.Write(packet.payload)
|
|
||||||
} else {
|
|
||||||
if s.videoBuffer == nil {
|
|
||||||
if s.videoIndex == 0 && s.audioIndex == 0 {
|
|
||||||
s.audioIndex = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建视频的AVPacket缓冲区
|
|
||||||
s.videoBuffer = s.FindOrCreatePacketBuffer(s.videoIndex, utils.AVMediaTypeVideo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将部分视频帧写入缓冲区
|
|
||||||
s.videoBuffer.TryMark()
|
|
||||||
s.videoBuffer.Write(packet.payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) Input(data []byte) error {
|
|
||||||
return s.decoder.Input(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) Close() {
|
func (s *Session) Close() {
|
||||||
@@ -151,149 +61,23 @@ func (s *Session) Close() {
|
|||||||
s.Conn = nil
|
s.Conn = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.decoder != nil {
|
|
||||||
s.decoder.Close()
|
|
||||||
s.decoder = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s.PublishSource.Close()
|
s.PublishSource.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从视频帧中提取AVPacket和AVStream, 回调给PublishSource
|
|
||||||
func (s *Session) processVideoPacket(pt byte, pktType byte, ts uint64, data []byte, index int) error {
|
|
||||||
var codecId utils.AVCodecID
|
|
||||||
|
|
||||||
if PTVideoH264 == pt {
|
|
||||||
if s.videoStream == nil && VideoIFrameMark != pktType {
|
|
||||||
log.Sugar.Errorf("skip non keyframes conn:%s", s.Conn.RemoteAddr())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
codecId = utils.AVCodecIdH264
|
|
||||||
} else if PTVideoH265 == pt {
|
|
||||||
if s.videoStream == nil && VideoIFrameMark != pktType {
|
|
||||||
log.Sugar.Errorf("skip non keyframes conn:%s", s.Conn.RemoteAddr())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
codecId = utils.AVCodecIdH265
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("the codec %d is not implemented", pt)
|
|
||||||
}
|
|
||||||
|
|
||||||
videoStream, videoPacket, err := stream.ExtractVideoPacket(codecId, VideoIFrameMark == pktType, s.videoStream == nil, data, int64(ts), int64(ts), index, 1000)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if videoStream != nil {
|
|
||||||
s.videoStream = videoStream
|
|
||||||
s.OnDeMuxStream(videoStream)
|
|
||||||
if s.videoStream != nil && s.audioStream != nil {
|
|
||||||
s.OnDeMuxStreamDone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.OnDeMuxPacket(videoPacket)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从音频帧中提取AVPacket和AVStream, 回调给PublishSource
|
|
||||||
func (s *Session) processAudioPacket(pt byte, pktType byte, ts uint64, data []byte, index int) error {
|
|
||||||
var codecId utils.AVCodecID
|
|
||||||
|
|
||||||
if PTAudioG711A == pt {
|
|
||||||
codecId = utils.AVCodecIdPCMALAW
|
|
||||||
} else if PTAudioG711U == pt {
|
|
||||||
codecId = utils.AVCodecIdPCMMULAW
|
|
||||||
} else if PTAudioAAC == pt {
|
|
||||||
codecId = utils.AVCodecIdAAC
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("the codec %d is not implemented", pt)
|
|
||||||
}
|
|
||||||
|
|
||||||
audioStream, audioPacket, err := stream.ExtractAudioPacket(codecId, s.audioStream == nil, data, int64(ts), int64(ts), index, 1000)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if audioStream != nil {
|
|
||||||
s.audioStream = audioStream
|
|
||||||
s.OnDeMuxStream(audioStream)
|
|
||||||
if s.videoStream != nil && s.audioStream != nil {
|
|
||||||
s.OnDeMuxStreamDone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.OnDeMuxPacket(audioPacket)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取1078的rtp包, 返回数据类型, 负载类型、时间戳、负载数据
|
|
||||||
func read1078RTPPacket(data []byte) (RtpPacket, error) {
|
|
||||||
if len(data) < 12 {
|
|
||||||
return RtpPacket{}, fmt.Errorf("invaild data")
|
|
||||||
}
|
|
||||||
|
|
||||||
packetType := data[11] >> 4 & 0x0F
|
|
||||||
// 忽略透传数据
|
|
||||||
if TransmissionDataMark == packetType {
|
|
||||||
return RtpPacket{}, fmt.Errorf("invaild data")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 忽略低于最低长度的数据包
|
|
||||||
if (AudioFrameMark == packetType && len(data) < 26) || (AudioFrameMark == packetType && len(data) < 22) {
|
|
||||||
return RtpPacket{}, fmt.Errorf("invaild data")
|
|
||||||
}
|
|
||||||
|
|
||||||
// x扩展位,固定为0
|
|
||||||
_ = data[0] >> 4 & 0x1
|
|
||||||
pt := data[1] & 0x7F
|
|
||||||
// seq
|
|
||||||
_ = binary.BigEndian.Uint16(data[2:])
|
|
||||||
|
|
||||||
var simNumber string
|
|
||||||
for i := 4; i < 10; i++ {
|
|
||||||
simNumber += fmt.Sprintf("%02d", data[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// channel
|
|
||||||
channelNumber := data[10]
|
|
||||||
// subMark
|
|
||||||
subMark := data[11] & 0x0F
|
|
||||||
// 时间戳,单位ms
|
|
||||||
var ts uint64
|
|
||||||
n := 12
|
|
||||||
if TransmissionDataMark != packetType {
|
|
||||||
ts = binary.BigEndian.Uint64(data[n:])
|
|
||||||
n += 8
|
|
||||||
}
|
|
||||||
|
|
||||||
if AudioFrameMark > packetType {
|
|
||||||
// iFrameInterval
|
|
||||||
_ = binary.BigEndian.Uint16(data[n:])
|
|
||||||
n += 2
|
|
||||||
// lastFrameInterval
|
|
||||||
_ = binary.BigEndian.Uint16(data[n:])
|
|
||||||
n += 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// size
|
|
||||||
_ = binary.BigEndian.Uint16(data[n:])
|
|
||||||
n += 2
|
|
||||||
|
|
||||||
return RtpPacket{pt: pt, packetType: packetType, ts: ts, simNumber: simNumber, channelNumber: channelNumber, subMark: subMark, payload: data[n:]}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSession(conn net.Conn) *Session {
|
func NewSession(conn net.Conn) *Session {
|
||||||
|
delimiter := [4]byte{0x30, 0x31, 0x63, 0x64}
|
||||||
session := Session{
|
session := Session{
|
||||||
PublishSource: stream.PublishSource{
|
PublishSource: stream.PublishSource{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
Type: stream.SourceType1078,
|
Type: stream.SourceType1078,
|
||||||
|
TransDemuxer: NewDemuxer(),
|
||||||
},
|
},
|
||||||
}
|
|
||||||
delimiter := [4]byte{0x30, 0x31, 0x63, 0x64}
|
|
||||||
session.decoder = transport.NewDelimiterFrameDecoder(1024*1024*2, delimiter[:], session.OnJtPTPPacket)
|
|
||||||
session.receiveBuffer = stream.NewTCPReceiveBuffer()
|
|
||||||
|
|
||||||
|
decoder: transport.NewDelimiterFrameDecoder(1024*1024*2, delimiter[:]),
|
||||||
|
receiveBuffer: stream.NewTCPReceiveBuffer(),
|
||||||
|
}
|
||||||
|
|
||||||
|
session.TransDemuxer.SetHandler(&session)
|
||||||
session.Init(stream.ReceiveBufferTCPBlockCount)
|
session.Init(stream.ReceiveBufferTCPBlockCount)
|
||||||
go stream.LoopEvent(&session)
|
go stream.LoopEvent(&session)
|
||||||
return &session
|
return &session
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package jt1078
|
package jt1078
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/libbufio"
|
"encoding/binary"
|
||||||
"github.com/lkmio/avformat/transport"
|
"fmt"
|
||||||
|
"github.com/lkmio/avformat/bufio"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -10,7 +12,52 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestPublish(t *testing.T) {
|
func TestPublish(t *testing.T) {
|
||||||
path := "D:\\GOProjects\\avformat\\10352264314-2.bin"
|
|
||||||
|
t.Run("decode_1078_data", func(t *testing.T) {
|
||||||
|
data, err := os.ReadFile("../dump/jt1078-127.0.0.1.50659")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
delimiter := [4]byte{0x30, 0x31, 0x63, 0x64}
|
||||||
|
decoder := transport.NewDelimiterFrameDecoder(1024*1024*2, delimiter[:])
|
||||||
|
|
||||||
|
length := len(data)
|
||||||
|
for j := 0; j < length; j += 4 {
|
||||||
|
size := int(binary.BigEndian.Uint32(data[j:]))
|
||||||
|
if 4+size > length-j {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
rtp := data[j+4 : j+4+size]
|
||||||
|
var n int
|
||||||
|
for length := len(rtp); n < length; {
|
||||||
|
i, bytes, err := decoder.Input(rtp[n:])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else if len(bytes) < 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
n += i
|
||||||
|
packet := Packet{}
|
||||||
|
err = packet.Unmarshal(bytes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("1078 packet ts: %d\r\n", packet.ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
j += size
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("publish", func(t *testing.T) {
|
||||||
|
|
||||||
|
//path := "../../source_files/10352264314-2.bin"
|
||||||
|
path := "../../source_files/013800138000-1.bin"
|
||||||
|
|
||||||
client := transport.TCPClient{}
|
client := transport.TCPClient{}
|
||||||
addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:1078")
|
addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:1078")
|
||||||
@@ -29,11 +76,10 @@ func TestPublish(t *testing.T) {
|
|||||||
|
|
||||||
index := 0
|
index := 0
|
||||||
for index < len(file) {
|
for index < len(file) {
|
||||||
n := libbufio.MinInt(len(file)-index, 1500)
|
n := bufio.MinInt(len(file)-index, 1500)
|
||||||
client.Write(file[index : index+n])
|
client.Write(file[index : index+n])
|
||||||
index += n
|
index += n
|
||||||
time.Sleep(1 * time.Millisecond)
|
time.Sleep(1 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
println("end")
|
|
||||||
}
|
}
|
||||||
|
|||||||
21
main.go
21
main.go
@@ -2,16 +2,18 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/lkm/flv"
|
"github.com/lkmio/lkm/flv"
|
||||||
"github.com/lkmio/lkm/gb28181"
|
|
||||||
"github.com/lkmio/lkm/hls"
|
"github.com/lkmio/lkm/hls"
|
||||||
"github.com/lkmio/lkm/jt1078"
|
"github.com/lkmio/lkm/jt1078"
|
||||||
"github.com/lkmio/lkm/log"
|
|
||||||
"github.com/lkmio/lkm/record"
|
"github.com/lkmio/lkm/record"
|
||||||
|
"github.com/lkmio/lkm/rtsp"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/lkmio/lkm/gb28181"
|
||||||
|
"github.com/lkmio/lkm/log"
|
||||||
"github.com/lkmio/lkm/rtc"
|
"github.com/lkmio/lkm/rtc"
|
||||||
"github.com/lkmio/lkm/rtmp"
|
"github.com/lkmio/lkm/rtmp"
|
||||||
"github.com/lkmio/lkm/rtsp"
|
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
"net"
|
"net"
|
||||||
@@ -67,11 +69,18 @@ func init() {
|
|||||||
log.InitLogger(config.Log.FileLogging, zapcore.Level(stream.AppConfig.Log.Level), stream.AppConfig.Log.Name, stream.AppConfig.Log.MaxSize, stream.AppConfig.Log.MaxBackup, stream.AppConfig.Log.MaxAge, stream.AppConfig.Log.Compress)
|
log.InitLogger(config.Log.FileLogging, zapcore.Level(stream.AppConfig.Log.Level), stream.AppConfig.Log.Name, stream.AppConfig.Log.MaxSize, stream.AppConfig.Log.MaxBackup, stream.AppConfig.Log.MaxAge, stream.AppConfig.Log.Compress)
|
||||||
|
|
||||||
if stream.AppConfig.GB28181.Enable && stream.AppConfig.GB28181.IsMultiPort() {
|
if stream.AppConfig.GB28181.Enable && stream.AppConfig.GB28181.IsMultiPort() {
|
||||||
gb28181.TransportManger = transport.NewTransportManager(uint16(stream.AppConfig.GB28181.Port[0]), uint16(stream.AppConfig.GB28181.Port[1]))
|
gb28181.TransportManger = transport.NewTransportManager(config.ListenIP, uint16(stream.AppConfig.GB28181.Port[0]), uint16(stream.AppConfig.GB28181.Port[1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
if stream.AppConfig.Rtsp.Enable && stream.AppConfig.Rtsp.IsMultiPort() {
|
if stream.AppConfig.Rtsp.Enable && stream.AppConfig.Rtsp.IsMultiPort() {
|
||||||
rtsp.TransportManger = transport.NewTransportManager(uint16(stream.AppConfig.Rtsp.Port[1]), uint16(stream.AppConfig.Rtsp.Port[2]))
|
rtsp.TransportManger = transport.NewTransportManager(config.ListenIP, uint16(stream.AppConfig.Rtsp.Port[1]), uint16(stream.AppConfig.Rtsp.Port[2]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建dump目录
|
||||||
|
if stream.AppConfig.Debug {
|
||||||
|
if err := os.MkdirAll("dump", 0666); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打印配置信息
|
// 打印配置信息
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ type FLVFileSink struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Input 输入http-flv数据
|
// Input 输入http-flv数据
|
||||||
func (f *FLVFileSink) Input(data []byte) error {
|
func (f *FLVFileSink) Write(index int, blocks [][]byte, ts int64) error {
|
||||||
if f.fail {
|
if f.fail {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//去掉不需要的换行符
|
for _, data := range blocks {
|
||||||
|
// 去掉不需要的换行符
|
||||||
var offset int
|
var offset int
|
||||||
for i := 2; i < len(data); i++ {
|
for i := 2; i < len(data); i++ {
|
||||||
if data[i-2] == 0x0D && data[i-1] == 0x0A {
|
if data[i-2] == 0x0D && data[i-1] == 0x0A {
|
||||||
@@ -30,11 +31,13 @@ func (f *FLVFileSink) Input(data []byte) error {
|
|||||||
|
|
||||||
_, err := f.file.Write(data[offset : len(data)-2])
|
_, err := f.file.Write(data[offset : len(data)-2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//只要写入失败一次,后续不再允许写入, 不影响拉流
|
// 只要写入失败一次,后续不再允许写入, 不影响拉流
|
||||||
f.fail = true
|
f.fail = true
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FLVFileSink) Close() {
|
func (f *FLVFileSink) Close() {
|
||||||
@@ -50,20 +53,20 @@ func NewFLVFileSink(sourceId string) (stream.Sink, string, error) {
|
|||||||
now := time.Now().Format("2006-01-02/15-04-05")
|
now := time.Now().Format("2006-01-02/15-04-05")
|
||||||
path := filepath.Join(stream.AppConfig.Record.Dir, sourceId, now+".flv")
|
path := filepath.Join(stream.AppConfig.Record.Dir, sourceId, now+".flv")
|
||||||
|
|
||||||
//创建目录
|
// 创建目录
|
||||||
dir := filepath.Dir(path)
|
dir := filepath.Dir(path)
|
||||||
if err := os.MkdirAll(dir, 0666); err != nil {
|
if err := os.MkdirAll(dir, 0666); err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
//创建flv文件
|
// 创建flv文件
|
||||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0666)
|
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &FLVFileSink{
|
return &FLVFileSink{
|
||||||
BaseSink: stream.BaseSink{ID: "record-sink-flv", SourceID: sourceId, Protocol: stream.TransStreamFlv},
|
BaseSink: stream.BaseSink{ID: "record-sink-flv", SourceID: sourceId, Protocol: stream.TransStreamFlv, Ready: true},
|
||||||
file: file,
|
file: file,
|
||||||
}, path, nil
|
}, path, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (s *Sink) StartStreaming(transStream stream.TransStream) error {
|
|||||||
for index, track := range tracks {
|
for index, track := range tracks {
|
||||||
var mimeType string
|
var mimeType string
|
||||||
var id string
|
var id string
|
||||||
codecId := track.Stream.CodecId()
|
codecId := track.Stream.CodecID
|
||||||
if utils.AVCodecIdH264 == codecId {
|
if utils.AVCodecIdH264 == codecId {
|
||||||
mimeType = webrtc.MimeTypeH264
|
mimeType = webrtc.MimeTypeH264
|
||||||
} else if utils.AVCodecIdH265 == codecId {
|
} else if utils.AVCodecIdH265 == codecId {
|
||||||
@@ -64,7 +64,7 @@ func (s *Sink) StartStreaming(transStream stream.TransStream) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.AVMediaTypeAudio == track.Stream.Type() {
|
if utils.AVMediaTypeAudio == track.Stream.MediaType {
|
||||||
id = "audio"
|
id = "audio"
|
||||||
} else {
|
} else {
|
||||||
id = "video"
|
id = "video"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package rtc
|
package rtc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/lkmio/avformat"
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
"github.com/pion/interceptor"
|
"github.com/pion/interceptor"
|
||||||
@@ -16,21 +17,23 @@ type transStream struct {
|
|||||||
stream.BaseTransStream
|
stream.BaseTransStream
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *transStream) Input(packet utils.AVPacket) ([][]byte, int64, bool, error) {
|
func (t *transStream) Input(packet *avformat.AVPacket) ([][]byte, int64, bool, error) {
|
||||||
t.ClearOutStreamBuffer()
|
t.ClearOutStreamBuffer()
|
||||||
|
|
||||||
if utils.AVMediaTypeAudio == packet.MediaType() {
|
if utils.AVMediaTypeAudio == packet.MediaType {
|
||||||
t.AppendOutStreamBuffer(packet.Data())
|
t.AppendOutStreamBuffer(packet.Data)
|
||||||
} else if utils.AVMediaTypeVideo == packet.MediaType() {
|
} else if utils.AVMediaTypeVideo == packet.MediaType {
|
||||||
if packet.KeyFrame() {
|
avStream := t.BaseTransStream.Tracks[packet.Index].Stream
|
||||||
extra := t.BaseTransStream.Tracks[packet.Index()].Stream.CodecParameters().AnnexBExtraData()
|
if packet.Key {
|
||||||
|
extra := avStream.CodecParameters.AnnexBExtraData()
|
||||||
t.AppendOutStreamBuffer(extra)
|
t.AppendOutStreamBuffer(extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.AppendOutStreamBuffer(packet.AnnexBPacketData(t.BaseTransStream.Tracks[packet.Index()].Stream))
|
data := avformat.AVCCPacket2AnnexB(avStream, packet)
|
||||||
|
t.AppendOutStreamBuffer(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.OutBuffer[:t.OutBufferSize], int64(uint32(packet.Duration(1000))), utils.AVMediaTypeVideo == packet.MediaType() && packet.KeyFrame(), nil
|
return t.OutBuffer[:t.OutBufferSize], int64(uint32(packet.GetDuration(1000))), utils.AVMediaTypeVideo == packet.MediaType && packet.Key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *transStream) WriteHeader() error {
|
func (t *transStream) WriteHeader() error {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package rtmp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"github.com/lkmio/avformat/transport"
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -19,7 +19,7 @@ func TestName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
client := transport.TCPClient{}
|
client := transport.TCPClient{}
|
||||||
if err := client.Connect(nil, addr); err != nil {
|
if _, err := client.Connect(nil, addr); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +1,19 @@
|
|||||||
package rtmp
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/libflv"
|
|
||||||
"github.com/lkmio/avformat/librtmp"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/rtmp"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Publisher RTMP推流Source
|
// Publisher RTMP推流Source
|
||||||
type Publisher struct {
|
type Publisher struct {
|
||||||
stream.PublishSource
|
stream.PublishSource
|
||||||
|
Stack *rtmp.ServerStack
|
||||||
Stack *librtmp.Stack
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Publisher) Input(data []byte) error {
|
func (p *Publisher) Input(data []byte) error {
|
||||||
return p.Stack.Input(data)
|
return p.Stack.Input(p.Conn, data)
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Publisher) OnDeMuxStream(stream utils.AVStream) {
|
|
||||||
// AVStream的ExtraData已经拷贝, 释放掉内存池中最新分配的内存
|
|
||||||
p.FindOrCreatePacketBuffer(stream.Index(), stream.Type()).FreeTail()
|
|
||||||
p.PublishSource.OnDeMuxStream(stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnVideo 解析出来的完整视频包
|
|
||||||
func (p *Publisher) OnVideo(index int, data []byte, ts uint32) {
|
|
||||||
data = p.FindOrCreatePacketBuffer(index, utils.AVMediaTypeVideo).Fetch()
|
|
||||||
// 交给flv解复用器, 解析出AVPacket
|
|
||||||
p.PublishSource.TransDeMuxer.(libflv.DeMuxer).InputVideo(data, ts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Publisher) OnAudio(index int, data []byte, ts uint32) {
|
|
||||||
data = p.FindOrCreatePacketBuffer(index, utils.AVMediaTypeAudio).Fetch()
|
|
||||||
p.PublishSource.TransDeMuxer.(libflv.DeMuxer).InputAudio(data, ts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnPartPacket AVPacket的部分数据包
|
|
||||||
func (p *Publisher) OnPartPacket(index int, mediaType utils.AVMediaType, data []byte, first bool) {
|
|
||||||
buffer := p.FindOrCreatePacketBuffer(index, mediaType)
|
|
||||||
if first {
|
|
||||||
buffer.Mark()
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.Write(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Publisher) Close() {
|
func (p *Publisher) Close() {
|
||||||
@@ -52,10 +21,11 @@ func (p *Publisher) Close() {
|
|||||||
p.Stack = nil
|
p.Stack = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPublisher(source string, stack *librtmp.Stack, conn net.Conn) *Publisher {
|
func NewPublisher(source string, stack *rtmp.ServerStack, conn net.Conn) *Publisher {
|
||||||
deMuxer := libflv.NewDeMuxer()
|
demuxer := stack.FLV
|
||||||
publisher := &Publisher{PublishSource: stream.PublishSource{ID: source, Type: stream.SourceTypeRtmp, TransDeMuxer: deMuxer, Conn: conn}, Stack: stack}
|
publisher := &Publisher{PublishSource: stream.PublishSource{ID: source, Type: stream.SourceTypeRtmp, TransDemuxer: demuxer, Conn: conn}, Stack: stack}
|
||||||
// 设置回调, 接受从DeMuxer解析出来的音视频包
|
// 设置回调, 接受从DeMuxer解析出来的音视频包
|
||||||
deMuxer.SetHandler(publisher)
|
demuxer.SetHandler(publisher)
|
||||||
|
demuxer.AutoFree = false
|
||||||
return publisher
|
return publisher
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server interface {
|
type Server interface {
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package rtmp
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/librtmp"
|
"github.com/lkmio/avformat"
|
||||||
"github.com/lkmio/avformat/utils"
|
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/rtmp"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateTransStream(source stream.Source, protocol stream.TransStreamProtocol, streams []utils.AVStream) stream.TransStream {
|
func CreateTransStream(source stream.Source, protocol stream.TransStreamProtocol, streams []*avformat.AVStream) stream.TransStream {
|
||||||
if stream.TransStreamRtmp == protocol {
|
if stream.TransStreamRtmp == protocol {
|
||||||
return NewTransStream(librtmp.ChunkSize)
|
return NewTransStream(rtmp.MaxChunkSize, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
package rtmp
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/librtmp"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/log"
|
"github.com/lkmio/lkm/log"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/rtmp"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Session RTMP会话, 解析处理Message
|
// Session RTMP会话, 解析处理Message
|
||||||
type Session struct {
|
type Session struct {
|
||||||
stack *librtmp.Stack // rtmp协议栈, 解析message
|
|
||||||
handle interface{} // 持有具体会话句柄(推流端/拉流端), 在@see OnPublish @see OnPlay回调中赋值
|
|
||||||
isPublisher bool // 是否时推流会话
|
|
||||||
|
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
|
stack *rtmp.ServerStack // rtmp协议栈, 解析message
|
||||||
|
handle interface{} // 持有具体会话句柄(推流端/拉流端), 在@see OnPublish @see OnPlay回调中赋值
|
||||||
|
isPublisher bool // 是否是推流会话
|
||||||
|
|
||||||
receiveBuffer *stream.ReceiveBuffer // 推流源收流队列
|
receiveBuffer *stream.ReceiveBuffer // 推流源收流队列
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,14 +29,12 @@ func (s *Session) generateSourceID(app, stream string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) OnPublish(app, stream_ string) utils.HookState {
|
func (s *Session) OnPublish(app, stream_ string) utils.HookState {
|
||||||
log.Sugar.Infof("rtmp onpublish app:%s stream:%s conn:%s", app, stream_, s.conn.RemoteAddr().String())
|
log.Sugar.Infof("rtmp onpublish app: %s stream: %s conn: %s", app, stream_, s.conn.RemoteAddr().String())
|
||||||
|
|
||||||
streamName, values := stream.ParseUrl(stream_)
|
streamName, values := stream.ParseUrl(stream_)
|
||||||
|
|
||||||
sourceId := s.generateSourceID(app, streamName)
|
sourceId := s.generateSourceID(app, streamName)
|
||||||
source := NewPublisher(sourceId, s.stack, s.conn)
|
source := NewPublisher(sourceId, s.stack, s.conn)
|
||||||
// 设置推流的音视频回调
|
|
||||||
s.stack.SetOnPublishHandler(source)
|
|
||||||
|
|
||||||
// 初始化放在add source前面, 以防add后再init, 空窗期拉流队列空指针.
|
// 初始化放在add source前面, 以防add后再init, 空窗期拉流队列空指针.
|
||||||
source.Init(stream.ReceiveBufferTCPBlockCount)
|
source.Init(stream.ReceiveBufferTCPBlockCount)
|
||||||
@@ -45,7 +43,7 @@ func (s *Session) OnPublish(app, stream_ string) utils.HookState {
|
|||||||
// 统一处理source推流事件, source是否已经存在, hook回调....
|
// 统一处理source推流事件, source是否已经存在, hook回调....
|
||||||
_, state := stream.PreparePublishSource(source, true)
|
_, state := stream.PreparePublishSource(source, true)
|
||||||
if utils.HookStateOK != state {
|
if utils.HookStateOK != state {
|
||||||
log.Sugar.Errorf("rtmp推流失败 source:%s", sourceId)
|
log.Sugar.Errorf("rtmp推流失败 source: %s", sourceId)
|
||||||
} else {
|
} else {
|
||||||
s.handle = source
|
s.handle = source
|
||||||
s.isPublisher = true
|
s.isPublisher = true
|
||||||
@@ -81,12 +79,12 @@ func (s *Session) Input(data []byte) error {
|
|||||||
if s.isPublisher {
|
if s.isPublisher {
|
||||||
return s.handle.(*Publisher).PublishSource.Input(data)
|
return s.handle.(*Publisher).PublishSource.Input(data)
|
||||||
} else {
|
} else {
|
||||||
return s.stack.Input(data)
|
return s.stack.Input(s.conn, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) Close() {
|
func (s *Session) Close() {
|
||||||
// session/conn/stack相互引用, go释放不了...手动赋值为nil
|
// session/conn/stack相互引用, gc回收不了...手动赋值为nil
|
||||||
s.conn = nil
|
s.conn = nil
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -120,8 +118,9 @@ func (s *Session) Close() {
|
|||||||
func NewSession(conn net.Conn) *Session {
|
func NewSession(conn net.Conn) *Session {
|
||||||
session := &Session{}
|
session := &Session{}
|
||||||
|
|
||||||
stack := librtmp.NewStack(conn, session)
|
stackServer := rtmp.NewStackServer(false)
|
||||||
session.stack = stack
|
stackServer.SetOnStreamHandler(session)
|
||||||
|
session.stack = stackServer
|
||||||
session.conn = conn
|
session.conn = conn
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
package rtmp
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/librtmp"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/rtmp"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Sink struct {
|
type Sink struct {
|
||||||
stream.BaseSink
|
stream.BaseSink
|
||||||
stack *librtmp.Stack
|
stack *rtmp.ServerStack
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sink) StartStreaming(_ stream.TransStream) error {
|
func (s *Sink) StartStreaming(_ stream.TransStream) error {
|
||||||
return s.stack.SendStreamBeginChunk()
|
return s.stack.SendStreamBeginChunk(s.Conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sink) StopStreaming(stream stream.TransStream) {
|
func (s *Sink) StopStreaming(stream stream.TransStream) {
|
||||||
_ = s.stack.SendStreamEOFChunk()
|
_ = s.stack.SendStreamEOFChunk(s.Conn)
|
||||||
s.BaseSink.StopStreaming(stream)
|
s.BaseSink.StopStreaming(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ func (s *Sink) Close() {
|
|||||||
s.stack = nil
|
s.stack = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSink(id stream.SinkID, sourceId string, conn net.Conn, stack *librtmp.Stack) stream.Sink {
|
func NewSink(id stream.SinkID, sourceId string, conn net.Conn, stack *rtmp.ServerStack) stream.Sink {
|
||||||
return &Sink{
|
return &Sink{
|
||||||
BaseSink: stream.BaseSink{ID: id, SourceID: sourceId, State: stream.SessionStateCreated, Protocol: stream.TransStreamRtmp, Conn: conn, DesiredAudioCodecId_: utils.AVCodecIdNONE, DesiredVideoCodecId_: utils.AVCodecIdNONE, TCPStreaming: true},
|
BaseSink: stream.BaseSink{ID: id, SourceID: sourceId, State: stream.SessionStateCreated, Protocol: stream.TransStreamRtmp, Conn: conn, DesiredAudioCodecId_: utils.AVCodecIdNONE, DesiredVideoCodecId_: utils.AVCodecIdNONE, TCPStreaming: true},
|
||||||
stack: stack,
|
stack: stack,
|
||||||
|
|||||||
@@ -1,59 +1,63 @@
|
|||||||
package rtmp
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/libflv"
|
"github.com/lkmio/avformat"
|
||||||
"github.com/lkmio/avformat/librtmp"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
|
"github.com/lkmio/flv"
|
||||||
|
"github.com/lkmio/flv/amf0"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/rtmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type transStream struct {
|
type transStream struct {
|
||||||
stream.TCPTransStream
|
stream.TCPTransStream
|
||||||
|
sequenceHeader []byte // sequence sequenceHeader
|
||||||
|
|
||||||
|
muxer *flv.Muxer
|
||||||
|
audioChunk rtmp.Chunk
|
||||||
|
videoChunk rtmp.Chunk
|
||||||
chunkSize int
|
chunkSize int
|
||||||
|
metaData *amf0.Object // 推流方携带的元数据
|
||||||
header []byte //sequence header
|
|
||||||
headerSize int
|
|
||||||
|
|
||||||
muxer libflv.Muxer
|
|
||||||
audioChunk librtmp.Chunk
|
|
||||||
videoChunk librtmp.Chunk
|
|
||||||
metaData *libflv.AMF0Object // 推流方携带的元数据
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *transStream) Input(packet utils.AVPacket) ([][]byte, int64, bool, error) {
|
func (t *transStream) Input(packet *avformat.AVPacket) ([][]byte, int64, bool, error) {
|
||||||
t.ClearOutStreamBuffer()
|
t.ClearOutStreamBuffer()
|
||||||
|
|
||||||
var data []byte
|
var data []byte
|
||||||
var chunk *librtmp.Chunk
|
var chunk *rtmp.Chunk
|
||||||
var videoPkt bool
|
var videoPkt bool
|
||||||
var videoKey bool
|
var videoKey bool
|
||||||
// rtmp chunk消息体的数据大小
|
// chunk负载消息体的数据大小
|
||||||
var payloadSize int
|
var payloadSize int
|
||||||
// 先向rtmp buffer写的flv头,再按照chunk size分割,所以第一个chunk要跳过flv头大小
|
// flv data header
|
||||||
var chunkPayloadOffset int
|
var dataHeaderSize int
|
||||||
var dts int64
|
var dts int64
|
||||||
var pts int64
|
var pts int64
|
||||||
var keyBuffer bool
|
var keyBuffer bool
|
||||||
|
var frameType int
|
||||||
|
|
||||||
dts = packet.ConvertDts(1000)
|
dts = packet.ConvertDts(1000)
|
||||||
pts = packet.ConvertPts(1000)
|
pts = packet.ConvertPts(1000)
|
||||||
ct := pts - dts
|
ct := pts - dts
|
||||||
|
|
||||||
if utils.AVMediaTypeAudio == packet.MediaType() {
|
// chunk = header+payload(audio data / video data)
|
||||||
data = packet.Data()
|
if utils.AVMediaTypeAudio == packet.MediaType {
|
||||||
|
data = packet.Data
|
||||||
chunk = &t.audioChunk
|
chunk = &t.audioChunk
|
||||||
chunkPayloadOffset = 2
|
dataHeaderSize = t.muxer.ComputeAudioDataHeaderSize()
|
||||||
payloadSize += chunkPayloadOffset + len(data)
|
} else if utils.AVMediaTypeVideo == packet.MediaType {
|
||||||
} else if utils.AVMediaTypeVideo == packet.MediaType() {
|
|
||||||
videoPkt = true
|
videoPkt = true
|
||||||
videoKey = packet.KeyFrame()
|
if videoKey = packet.Key; videoKey {
|
||||||
data = packet.AVCCPacketData()
|
frameType = flv.FrameTypeKeyFrame
|
||||||
chunk = &t.videoChunk
|
|
||||||
chunkPayloadOffset = t.muxer.ComputeVideoDataSize(uint32(ct))
|
|
||||||
payloadSize += chunkPayloadOffset + len(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data = avformat.AnnexBPacket2AVCC(packet)
|
||||||
|
chunk = &t.videoChunk
|
||||||
|
dataHeaderSize = t.muxer.ComputeVideoDataHeaderSize(uint32(ct))
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadSize += dataHeaderSize + len(data)
|
||||||
|
|
||||||
// 遇到视频关键帧, 发送剩余的流, 创建新切片
|
// 遇到视频关键帧, 发送剩余的流, 创建新切片
|
||||||
if videoKey {
|
if videoKey {
|
||||||
if segment, key := t.MWBuffer.FlushSegment(); len(segment) > 0 {
|
if segment, key := t.MWBuffer.FlushSegment(); len(segment) > 0 {
|
||||||
@@ -66,30 +70,31 @@ func (t *transStream) Input(packet utils.AVPacket) ([][]byte, int64, bool, error
|
|||||||
chunkHeaderSize := 12
|
chunkHeaderSize := 12
|
||||||
// type为3的chunk数量
|
// type为3的chunk数量
|
||||||
numChunks := (payloadSize - 1) / t.chunkSize
|
numChunks := (payloadSize - 1) / t.chunkSize
|
||||||
rtmpMsgSize := chunkHeaderSize + payloadSize + numChunks
|
// 整个chunk大小
|
||||||
|
totalSize := chunkHeaderSize + payloadSize + numChunks
|
||||||
// 如果时间戳超过3字节, 每个chunk都需要多4字节的扩展时间戳
|
// 如果时间戳超过3字节, 每个chunk都需要多4字节的扩展时间戳
|
||||||
if dts >= 0xFFFFFF && dts <= 0xFFFFFFFF {
|
if dts >= 0xFFFFFF && dts >= 0xFFFFFFFF {
|
||||||
rtmpMsgSize += (1 + numChunks) * 4
|
totalSize += (1 + numChunks) * 4
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分配指定大小的内存
|
// 分配指定大小的内存
|
||||||
allocate := t.MWBuffer.Allocate(rtmpMsgSize, dts, videoKey)
|
bytes := t.MWBuffer.Allocate(totalSize, dts, videoKey)
|
||||||
|
|
||||||
// 写第一个type为0的chunk header
|
// 写第一个type为0的chunk sequenceHeader
|
||||||
chunk.Length = payloadSize
|
chunk.Length = payloadSize
|
||||||
chunk.Timestamp = uint32(dts)
|
chunk.Timestamp = uint32(dts)
|
||||||
n := chunk.MarshalHeader(allocate)
|
n := chunk.MarshalHeader(bytes)
|
||||||
|
|
||||||
// 封装成flv
|
// 封装成flv
|
||||||
if videoPkt {
|
if videoPkt {
|
||||||
n += t.muxer.WriteVideoData(allocate[n:], uint32(ct), packet.KeyFrame(), false)
|
n += t.muxer.VideoData.Marshal(bytes[n:], uint32(ct), frameType, false)
|
||||||
} else {
|
} else {
|
||||||
n += t.muxer.WriteAudioData(allocate[n:], false)
|
n += t.muxer.AudioData.Marshal(bytes[n:], false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将flv data写入chunk body
|
// 将flv data写入chunk body
|
||||||
n += chunk.WriteBody(allocate[n:], data, t.chunkSize, chunkPayloadOffset)
|
n += chunk.WriteBody(bytes[n:], data, t.chunkSize, dataHeaderSize)
|
||||||
utils.Assert(len(allocate) == n)
|
utils.Assert(len(bytes) == n)
|
||||||
|
|
||||||
// 合并写满了再发
|
// 合并写满了再发
|
||||||
if segment, key := t.MWBuffer.PeekCompletedSegment(); len(segment) > 0 {
|
if segment, key := t.MWBuffer.PeekCompletedSegment(); len(segment) > 0 {
|
||||||
@@ -101,10 +106,10 @@ func (t *transStream) Input(packet utils.AVPacket) ([][]byte, int64, bool, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *transStream) ReadExtraData(_ int64) ([][]byte, int64, error) {
|
func (t *transStream) ReadExtraData(_ int64) ([][]byte, int64, error) {
|
||||||
utils.Assert(t.headerSize > 0)
|
utils.Assert(len(t.sequenceHeader) > 0)
|
||||||
|
|
||||||
// 发送sequence header
|
// 发送sequence sequenceHeader
|
||||||
return [][]byte{t.header[:t.headerSize]}, 0, nil
|
return [][]byte{t.sequenceHeader}, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *transStream) ReadKeyFrameBuffer() ([][]byte, int64, error) {
|
func (t *transStream) ReadKeyFrameBuffer() ([][]byte, int64, error) {
|
||||||
@@ -122,20 +127,20 @@ func (t *transStream) WriteHeader() error {
|
|||||||
utils.Assert(t.Tracks != nil)
|
utils.Assert(t.Tracks != nil)
|
||||||
utils.Assert(!t.BaseTransStream.Completed)
|
utils.Assert(!t.BaseTransStream.Completed)
|
||||||
|
|
||||||
var audioStream utils.AVStream
|
var audioStream *avformat.AVStream
|
||||||
var videoStream utils.AVStream
|
var videoStream *avformat.AVStream
|
||||||
var audioCodecId utils.AVCodecID
|
var audioCodecId utils.AVCodecID
|
||||||
var videoCodecId utils.AVCodecID
|
var videoCodecId utils.AVCodecID
|
||||||
|
|
||||||
for _, track := range t.Tracks {
|
for _, track := range t.Tracks {
|
||||||
if utils.AVMediaTypeAudio == track.Stream.Type() {
|
if utils.AVMediaTypeAudio == track.Stream.MediaType {
|
||||||
audioStream = track.Stream
|
audioStream = track.Stream
|
||||||
audioCodecId = audioStream.CodecId()
|
audioCodecId = audioStream.CodecID
|
||||||
t.audioChunk = librtmp.NewAudioChunk()
|
t.audioChunk = rtmp.NewAudioChunk()
|
||||||
} else if utils.AVMediaTypeVideo == track.Stream.Type() {
|
} else if utils.AVMediaTypeVideo == track.Stream.MediaType {
|
||||||
videoStream = track.Stream
|
videoStream = track.Stream
|
||||||
videoCodecId = videoStream.CodecId()
|
videoCodecId = videoStream.CodecID
|
||||||
t.videoChunk = librtmp.NewVideoChunk()
|
t.videoChunk = rtmp.NewVideoChunk()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,67 +148,67 @@ func (t *transStream) WriteHeader() error {
|
|||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
t.BaseTransStream.Completed = true
|
t.BaseTransStream.Completed = true
|
||||||
t.muxer = libflv.NewMuxer(t.metaData)
|
t.muxer = flv.NewMuxer(t.metaData)
|
||||||
if utils.AVCodecIdNONE != audioCodecId {
|
if utils.AVCodecIdNONE != audioCodecId {
|
||||||
t.muxer.AddAudioTrack(audioCodecId, 0, 0, 0)
|
t.muxer.AddAudioTrack(audioStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.AVCodecIdNONE != videoCodecId {
|
if utils.AVCodecIdNONE != videoCodecId {
|
||||||
t.muxer.AddVideoTrack(videoCodecId)
|
t.muxer.AddVideoTrack(videoStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
var n int
|
var n int
|
||||||
t.header = make([]byte, 4096)
|
t.sequenceHeader = make([]byte, 4096)
|
||||||
|
|
||||||
// 生成推流的数据头(chunk+sequence header)
|
// 生成数据头
|
||||||
if audioStream != nil {
|
if audioStream != nil {
|
||||||
n += t.muxer.WriteAudioData(t.header[12:], true)
|
n += t.muxer.AudioData.Marshal(t.sequenceHeader[12:], true)
|
||||||
extra := audioStream.Extra()
|
extra := audioStream.Data
|
||||||
copy(t.header[n+12:], extra)
|
copy(t.sequenceHeader[n+12:], extra)
|
||||||
n += len(extra)
|
n += len(extra)
|
||||||
|
|
||||||
t.audioChunk.Length = n
|
t.audioChunk.Length = n
|
||||||
t.audioChunk.MarshalHeader(t.header)
|
t.audioChunk.MarshalHeader(t.sequenceHeader)
|
||||||
n += 12
|
n += 12
|
||||||
}
|
}
|
||||||
|
|
||||||
if videoStream != nil {
|
if videoStream != nil {
|
||||||
tmp := n
|
tmp := n
|
||||||
n += t.muxer.WriteVideoData(t.header[n+12:], 0, false, true)
|
n += t.muxer.VideoData.Marshal(t.sequenceHeader[n+12:], 0, 0, true)
|
||||||
extra := videoStream.CodecParameters().MP4ExtraData()
|
extra := videoStream.CodecParameters.MP4ExtraData()
|
||||||
copy(t.header[n+12:], extra)
|
copy(t.sequenceHeader[n+12:], extra)
|
||||||
n += len(extra)
|
n += len(extra)
|
||||||
|
|
||||||
t.videoChunk.Length = 5 + len(extra)
|
t.videoChunk.Length = 5 + len(extra)
|
||||||
t.videoChunk.MarshalHeader(t.header[tmp:])
|
t.videoChunk.MarshalHeader(t.sequenceHeader[tmp:])
|
||||||
n += 12
|
n += 12
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建元数据chunk
|
// 创建元数据chunk
|
||||||
var body [1024]byte
|
var body [1024]byte
|
||||||
amf0 := libflv.AMF0{}
|
metadata := amf0.Data{}
|
||||||
amf0.AddString("onMetaData")
|
metadata.AddString("onMetaData")
|
||||||
amf0.Add(t.muxer.MetaData())
|
metadata.Add(t.muxer.MetaData())
|
||||||
length, _ := amf0.Marshal(body[:])
|
length, _ := metadata.Marshal(body[:])
|
||||||
|
|
||||||
metaData := librtmp.Chunk{
|
metaDataChunk := rtmp.Chunk{
|
||||||
Type: librtmp.ChunkType0,
|
Type: rtmp.ChunkType0,
|
||||||
ChunkStreamID_: 5,
|
ChunkStreamID: 5,
|
||||||
Timestamp: 0,
|
Timestamp: 0,
|
||||||
TypeID: librtmp.MessageTypeIDDataAMF0,
|
TypeID: rtmp.MessageTypeIDDataAMF0,
|
||||||
StreamID: 1,
|
StreamID: 1,
|
||||||
Body: body[:length],
|
Body: body[:length],
|
||||||
Length: length,
|
Length: length,
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmp [1600]byte
|
var tmp [1600]byte
|
||||||
size := metaData.Marshal(tmp[:], librtmp.ChunkSize)
|
size := metaDataChunk.Marshal(tmp[:], rtmp.MaxChunkSize)
|
||||||
// metadata 放在sequence之前
|
// metadata放在sequence之前
|
||||||
copy(t.header[size:], t.header[:n])
|
copy(t.sequenceHeader[size:], t.sequenceHeader[:n])
|
||||||
copy(t.header, tmp[:][:size])
|
copy(t.sequenceHeader, tmp[:][:size])
|
||||||
|
|
||||||
n += size
|
n += size
|
||||||
t.headerSize = n
|
t.sequenceHeader = t.sequenceHeader[:n]
|
||||||
t.MWBuffer = stream.NewMergeWritingBuffer(t.ExistVideo)
|
t.MWBuffer = stream.NewMergeWritingBuffer(t.ExistVideo)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -219,15 +224,15 @@ func (t *transStream) Close() ([][]byte, int64, error) {
|
|||||||
return t.OutBuffer[:t.OutBufferSize], 0, nil
|
return t.OutBuffer[:t.OutBufferSize], 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransStream(chunkSize int, metaData *libflv.AMF0Object) stream.TransStream {
|
func NewTransStream(chunkSize int, metaData *amf0.Object) stream.TransStream {
|
||||||
return &transStream{chunkSize: chunkSize, metaData: metaData}
|
return &transStream{chunkSize: chunkSize, metaData: metaData}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TransStreamFactory(source stream.Source, protocol stream.TransStreamProtocol, tracks []*stream.Track) (stream.TransStream, error) {
|
func TransStreamFactory(source stream.Source, protocol stream.TransStreamProtocol, tracks []*stream.Track) (stream.TransStream, error) {
|
||||||
// 获取推流的元数据
|
// 获取推流的元数据
|
||||||
var metaData *libflv.AMF0Object
|
var metaData *amf0.Object
|
||||||
if stream.SourceTypeRtmp == source.GetType() {
|
if stream.SourceTypeRtmp == source.GetType() {
|
||||||
metaData = source.(*Publisher).Stack.MetaData()
|
metaData = source.(*Publisher).Stack.Metadata()
|
||||||
}
|
}
|
||||||
return NewTransStream(librtmp.ChunkSize, metaData), nil
|
return NewTransStream(rtmp.MaxChunkSize, metaData), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package rtsp
|
package rtsp
|
||||||
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
@@ -70,7 +71,7 @@ func (h handler) Process(session *session, method string, url_ *url.URL, headers
|
|||||||
return fmt.Errorf("please establish a session first")
|
return fmt.Errorf("please establish a session first")
|
||||||
}
|
}
|
||||||
|
|
||||||
source, _ := stream.Path2SourceId(url_.Path, "")
|
source, _ := stream.Path2SourceID(url_.Path, "")
|
||||||
|
|
||||||
//反射调用各个处理函数
|
//反射调用各个处理函数
|
||||||
results := m.Call([]reflect.Value{
|
results := m.Call([]reflect.Value{
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package rtsp
|
package rtsp
|
||||||
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/log"
|
"github.com/lkmio/lkm/log"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package rtsp
|
package rtsp
|
||||||
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package rtsp
|
package rtsp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/librtp"
|
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/log"
|
"github.com/lkmio/lkm/log"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/rtp"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"github.com/pion/rtcp"
|
"github.com/pion/rtcp"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
@@ -21,7 +21,7 @@ var (
|
|||||||
type Sink struct {
|
type Sink struct {
|
||||||
stream.BaseSink
|
stream.BaseSink
|
||||||
|
|
||||||
senders []*librtp.RtpSender // 一个rtsp源, 可能存在多个流, 每个流都需要拉取
|
senders []*rtp.RtpSender // 一个rtsp源, 可能存在多个流, 每个流都需要拉取
|
||||||
cb func(sdp string) // sdp回调, 响应describe
|
cb func(sdp string) // sdp回调, 响应describe
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ func (s *Sink) StartStreaming(transStream stream.TransStream) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s.senders = make([]*librtp.RtpSender, transStream.TrackSize())
|
s.senders = make([]*rtp.RtpSender, transStream.TrackSize())
|
||||||
// sdp回调给sink, sink应答给describe请求
|
// sdp回调给sink, sink应答给describe请求
|
||||||
if s.cb != nil {
|
if s.cb != nil {
|
||||||
s.cb(transStream.(*TransStream).sdp)
|
s.cb(transStream.(*TransStream).sdp)
|
||||||
@@ -47,19 +47,19 @@ func (s *Sink) AddSender(index int, tcp bool, ssrc uint32) (uint16, uint16, erro
|
|||||||
var rtpPort uint16
|
var rtpPort uint16
|
||||||
var rtcpPort uint16
|
var rtcpPort uint16
|
||||||
|
|
||||||
sender := librtp.RtpSender{
|
sender := rtp.RtpSender{
|
||||||
SSRC: ssrc,
|
SSRC: ssrc,
|
||||||
}
|
}
|
||||||
|
|
||||||
if tcp {
|
if tcp {
|
||||||
s.TCPStreaming = true
|
s.TCPStreaming = true
|
||||||
} else {
|
} else {
|
||||||
sender.Rtp, err = TransportManger.NewUDPServer("0.0.0.0")
|
sender.Rtp, err = TransportManger.NewUDPServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sender.Rtcp, err = TransportManger.NewUDPServer("0.0.0.0")
|
sender.Rtcp, err = TransportManger.NewUDPServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sender.Rtp.Close()
|
sender.Rtp.Close()
|
||||||
sender.Rtp = nil
|
sender.Rtp = nil
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ package rtsp
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lkmio/avformat/libavc"
|
"github.com/lkmio/avformat"
|
||||||
"github.com/lkmio/avformat/librtp"
|
"github.com/lkmio/avformat/avc"
|
||||||
"github.com/lkmio/avformat/librtsp/sdp"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/stream"
|
"github.com/lkmio/lkm/stream"
|
||||||
|
"github.com/lkmio/rtp"
|
||||||
|
"github.com/pion/sdp/v3"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
@@ -38,21 +39,22 @@ func (t *TransStream) OverTCP(data []byte, channel int) {
|
|||||||
binary.BigEndian.PutUint16(data[2:], uint16(len(data)-4))
|
binary.BigEndian.PutUint16(data[2:], uint16(len(data)-4))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransStream) Input(packet utils.AVPacket) ([][]byte, int64, bool, error) {
|
func (t *TransStream) Input(packet *avformat.AVPacket) ([][]byte, int64, bool, error) {
|
||||||
t.ClearOutStreamBuffer()
|
t.ClearOutStreamBuffer()
|
||||||
|
|
||||||
var ts uint32
|
var ts uint32
|
||||||
track := t.RtspTracks[packet.Index()]
|
track := t.RtspTracks[packet.Index]
|
||||||
if utils.AVMediaTypeAudio == packet.MediaType() {
|
if utils.AVMediaTypeAudio == packet.MediaType {
|
||||||
ts = uint32(packet.ConvertPts(track.Rate))
|
ts = uint32(packet.ConvertPts(track.Rate))
|
||||||
t.PackRtpPayload(track, packet.Index(), packet.Data(), ts)
|
t.PackRtpPayload(track, packet.Index, packet.Data, ts)
|
||||||
} else if utils.AVMediaTypeVideo == packet.MediaType() {
|
} else if utils.AVMediaTypeVideo == packet.MediaType {
|
||||||
ts = uint32(packet.ConvertPts(track.Rate))
|
ts = uint32(packet.ConvertPts(track.Rate))
|
||||||
data := libavc.RemoveStartCode(packet.AnnexBPacketData(t.BaseTransStream.Tracks[packet.Index()].Stream))
|
annexBData := avformat.AVCCPacket2AnnexB(t.BaseTransStream.Tracks[packet.Index].Stream, packet)
|
||||||
t.PackRtpPayload(track, packet.Index(), data, ts)
|
data := avc.RemoveStartCode(annexBData)
|
||||||
|
t.PackRtpPayload(track, packet.Index, data, ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.OutBuffer[:t.OutBufferSize], int64(ts), utils.AVMediaTypeVideo == packet.MediaType() && packet.KeyFrame(), nil
|
return t.OutBuffer[:t.OutBufferSize], int64(ts), utils.AVMediaTypeVideo == packet.MediaType && packet.Key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransStream) ReadExtraData(ts int64) ([][]byte, int64, error) {
|
func (t *TransStream) ReadExtraData(ts int64) ([][]byte, int64, error) {
|
||||||
@@ -65,7 +67,7 @@ func (t *TransStream) ReadExtraData(ts int64) ([][]byte, int64, error) {
|
|||||||
// 回滚序号和时间戳
|
// 回滚序号和时间戳
|
||||||
index := int(track.StartSeq) - len(track.ExtraDataBuffer)
|
index := int(track.StartSeq) - len(track.ExtraDataBuffer)
|
||||||
for i, bytes := range track.ExtraDataBuffer {
|
for i, bytes := range track.ExtraDataBuffer {
|
||||||
librtp.RollbackSeq(bytes[OverTcpHeaderSize:], index+i+1)
|
rtp.RollbackSeq(bytes[OverTcpHeaderSize:], index+i+1)
|
||||||
binary.BigEndian.PutUint32(bytes[OverTcpHeaderSize+4:], uint32(ts))
|
binary.BigEndian.PutUint32(bytes[OverTcpHeaderSize+4:], uint32(ts))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,9 +100,9 @@ func (t *TransStream) AddTrack(track *stream.Track) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
payloadType, ok := librtp.CodecIdPayloads[track.Stream.CodecId()]
|
payloadType, ok := rtp.CodecIdPayloads[track.Stream.CodecID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("no payload type was found for codecid: %d", track.Stream.CodecId())
|
return fmt.Errorf("no payload type was found for codecid: %d", track.Stream.CodecID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复上次拉流的序号
|
// 恢复上次拉流的序号
|
||||||
@@ -111,35 +113,34 @@ func (t *TransStream) AddTrack(track *stream.Track) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建RTP封装器
|
// 创建RTP封装器
|
||||||
var muxer librtp.Muxer
|
var muxer rtp.Muxer
|
||||||
if utils.AVCodecIdH264 == track.Stream.CodecId() {
|
if utils.AVCodecIdH264 == track.Stream.CodecID {
|
||||||
muxer = librtp.NewH264Muxer(payloadType.Pt, int(startSeq), 0xFFFFFFFF)
|
muxer = rtp.NewH264Muxer(payloadType.Pt, int(startSeq), 0xFFFFFFFF)
|
||||||
} else if utils.AVCodecIdH265 == track.Stream.CodecId() {
|
} else if utils.AVCodecIdH265 == track.Stream.CodecID {
|
||||||
muxer = librtp.NewH265Muxer(payloadType.Pt, int(startSeq), 0xFFFFFFFF)
|
muxer = rtp.NewH265Muxer(payloadType.Pt, int(startSeq), 0xFFFFFFFF)
|
||||||
} else if utils.AVCodecIdAAC == track.Stream.CodecId() {
|
} else if utils.AVCodecIdAAC == track.Stream.CodecID {
|
||||||
muxer = librtp.NewAACMuxer(payloadType.Pt, int(startSeq), 0xFFFFFFFF)
|
muxer = rtp.NewAACMuxer(payloadType.Pt, int(startSeq), 0xFFFFFFFF)
|
||||||
} else if utils.AVCodecIdPCMALAW == track.Stream.CodecId() || utils.AVCodecIdPCMMULAW == track.Stream.CodecId() {
|
} else if utils.AVCodecIdPCMALAW == track.Stream.CodecID || utils.AVCodecIdPCMMULAW == track.Stream.CodecID {
|
||||||
muxer = librtp.NewMuxer(payloadType.Pt, int(startSeq), 0xFFFFFFFF)
|
muxer = rtp.NewMuxer(payloadType.Pt, int(startSeq), 0xFFFFFFFF)
|
||||||
}
|
}
|
||||||
|
|
||||||
rtspTrack := NewRTSPTrack(muxer, byte(payloadType.Pt), payloadType.ClockRate, track.Stream.Type())
|
rtspTrack := NewRTSPTrack(muxer, byte(payloadType.Pt), payloadType.ClockRate, track.Stream.MediaType)
|
||||||
t.RtspTracks = append(t.RtspTracks, rtspTrack)
|
t.RtspTracks = append(t.RtspTracks, rtspTrack)
|
||||||
index := len(t.RtspTracks) - 1
|
index := len(t.RtspTracks) - 1
|
||||||
|
|
||||||
// 将sps和pps按照单一模式打包
|
// 将sps和pps按照单一模式打包
|
||||||
bufferIndex := t.buffer.Index()
|
bufferIndex := t.buffer.Index()
|
||||||
if utils.AVMediaTypeVideo == track.Stream.Type() {
|
if utils.AVMediaTypeVideo == track.Stream.MediaType {
|
||||||
parameters := track.Stream.CodecParameters()
|
parameters := track.Stream.CodecParameters
|
||||||
|
if utils.AVCodecIdH265 == track.Stream.CodecID {
|
||||||
if utils.AVCodecIdH265 == track.Stream.CodecId() {
|
bytes := parameters.(*avformat.HEVCCodecData).VPS()
|
||||||
bytes := parameters.(*utils.HEVCCodecData).VPS()
|
t.PackRtpPayload(rtspTrack, index, avc.RemoveStartCode(bytes[0]), 0)
|
||||||
t.PackRtpPayload(rtspTrack, index, libavc.RemoveStartCode(bytes[0]), 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
spsBytes := parameters.SPS()
|
spsBytes := parameters.SPS()
|
||||||
ppsBytes := parameters.PPS()
|
ppsBytes := parameters.PPS()
|
||||||
t.PackRtpPayload(rtspTrack, index, libavc.RemoveStartCode(spsBytes[0]), 0)
|
t.PackRtpPayload(rtspTrack, index, avc.RemoveStartCode(spsBytes[0]), 0)
|
||||||
t.PackRtpPayload(rtspTrack, index, libavc.RemoveStartCode(ppsBytes[0]), 0)
|
t.PackRtpPayload(rtspTrack, index, avc.RemoveStartCode(ppsBytes[0]), 0)
|
||||||
|
|
||||||
// 拷贝扩展数据的rtp包
|
// 拷贝扩展数据的rtp包
|
||||||
size := t.buffer.Index() - bufferIndex
|
size := t.buffer.Index() - bufferIndex
|
||||||
@@ -191,7 +192,7 @@ func (t *TransStream) WriteHeader() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, track := range t.Tracks {
|
for i, track := range t.Tracks {
|
||||||
payloadType, _ := librtp.CodecIdPayloads[track.Stream.CodecId()]
|
payloadType, _ := rtp.CodecIdPayloads[track.Stream.CodecID]
|
||||||
mediaDescription := sdp.MediaDescription{
|
mediaDescription := sdp.MediaDescription{
|
||||||
ConnectionInformation: &sdp.ConnectionInformation{
|
ConnectionInformation: &sdp.ConnectionInformation{
|
||||||
NetworkType: "IN",
|
NetworkType: "IN",
|
||||||
@@ -209,10 +210,10 @@ func (t *TransStream) WriteHeader() error {
|
|||||||
mediaDescription.MediaName.Protos = []string{"RTP", "AVP"}
|
mediaDescription.MediaName.Protos = []string{"RTP", "AVP"}
|
||||||
mediaDescription.MediaName.Formats = []string{strconv.Itoa(payloadType.Pt)}
|
mediaDescription.MediaName.Formats = []string{strconv.Itoa(payloadType.Pt)}
|
||||||
|
|
||||||
if utils.AVMediaTypeAudio == track.Stream.Type() {
|
if utils.AVMediaTypeAudio == track.Stream.MediaType {
|
||||||
mediaDescription.MediaName.Media = "audio"
|
mediaDescription.MediaName.Media = "audio"
|
||||||
|
|
||||||
if utils.AVCodecIdAAC == track.Stream.CodecId() {
|
if utils.AVCodecIdAAC == track.Stream.CodecID {
|
||||||
//[14496-3], [RFC6416] profile-level-id:
|
//[14496-3], [RFC6416] profile-level-id:
|
||||||
//1 : Main Audio Profile Level 1
|
//1 : Main Audio Profile Level 1
|
||||||
//9 : Speech Audio Profile Level 1
|
//9 : Speech Audio Profile Level 1
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package rtsp
|
package rtsp
|
||||||
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/librtp"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
|
"github.com/lkmio/rtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Track RtspTrack 对rtsp每路输出流的封装
|
// Track RtspTrack 对rtsp每路输出流的封装
|
||||||
@@ -13,14 +14,14 @@ type Track struct {
|
|||||||
StartSeq uint16
|
StartSeq uint16
|
||||||
EndSeq uint16
|
EndSeq uint16
|
||||||
|
|
||||||
Muxer librtp.Muxer
|
Muxer rtp.Muxer
|
||||||
ExtraDataBuffer [][]byte // 缓存带有编码信息的rtp包, 对所有sink通用
|
ExtraDataBuffer [][]byte // 缓存带有编码信息的rtp包, 对所有sink通用
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Track) Close() {
|
func (r *Track) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRTSPTrack(muxer librtp.Muxer, pt byte, rate int, mediaType utils.AVMediaType) *Track {
|
func NewRTSPTrack(muxer rtp.Muxer, pt byte, rate int, mediaType utils.AVMediaType) *Track {
|
||||||
stream := &Track{
|
stream := &Track{
|
||||||
PT: pt,
|
PT: pt,
|
||||||
Rate: rate,
|
Rate: rate,
|
||||||
|
|||||||
@@ -232,11 +232,6 @@ func GetStreamPlayUrls(source string) []string {
|
|||||||
|
|
||||||
// DumpStream2File 保存推流到文件, 用4字节帧长分割
|
// DumpStream2File 保存推流到文件, 用4字节帧长分割
|
||||||
func DumpStream2File(sourceType SourceType, conn net.Conn, data []byte) {
|
func DumpStream2File(sourceType SourceType, conn net.Conn, data []byte) {
|
||||||
if err := os.MkdirAll("dump", 0666); err != nil {
|
|
||||||
log.Sugar.Errorf("创建dump文件夹失败 err:%s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
path := fmt.Sprintf("dump/%s-%s", sourceType.String(), conn.RemoteAddr().String())
|
path := fmt.Sprintf("dump/%s-%s", sourceType.String(), conn.RemoteAddr().String())
|
||||||
path = strings.ReplaceAll(path, ":", ".")
|
path = strings.ReplaceAll(path, ":", ".")
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package stream
|
package stream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/lkmio/avformat"
|
||||||
"github.com/lkmio/avformat/collections"
|
"github.com/lkmio/avformat/collections"
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
)
|
)
|
||||||
@@ -9,14 +10,14 @@ import (
|
|||||||
type GOPBuffer interface {
|
type GOPBuffer interface {
|
||||||
|
|
||||||
// AddPacket Return bool 缓存帧是否成功, 如果首帧非关键帧, 缓存失败
|
// AddPacket Return bool 缓存帧是否成功, 如果首帧非关键帧, 缓存失败
|
||||||
AddPacket(packet utils.AVPacket) bool
|
AddPacket(packet *avformat.AVPacket) bool
|
||||||
|
|
||||||
// SetDiscardHandler 设置丢弃帧时的回调
|
// SetDiscardHandler 设置丢弃帧时的回调
|
||||||
SetDiscardHandler(handler func(packet utils.AVPacket))
|
SetDiscardHandler(handler func(packet *avformat.AVPacket))
|
||||||
|
|
||||||
PeekAll(handler func(packet utils.AVPacket))
|
PeekAll(handler func(packet *avformat.AVPacket))
|
||||||
|
|
||||||
Peek(index int) utils.AVPacket
|
Peek(index int) *avformat.AVPacket
|
||||||
|
|
||||||
Size() int
|
Size() int
|
||||||
|
|
||||||
@@ -26,24 +27,24 @@ type GOPBuffer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type streamBuffer struct {
|
type streamBuffer struct {
|
||||||
buffer collections.RingBuffer
|
buffer collections.RingBuffer[*avformat.AVPacket]
|
||||||
existVideoKeyFrame bool
|
existVideoKeyFrame bool
|
||||||
discardHandler func(packet utils.AVPacket)
|
discardHandler func(packet *avformat.AVPacket)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *streamBuffer) AddPacket(packet utils.AVPacket) bool {
|
func (s *streamBuffer) AddPacket(packet *avformat.AVPacket) bool {
|
||||||
// 缓存满,清空
|
// 缓存满,清空
|
||||||
if s.Size()+1 == s.buffer.Capacity() {
|
if s.Size()+1 == s.buffer.Capacity() {
|
||||||
s.Clear()
|
s.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 丢弃首帧视频非关键帧
|
// 丢弃首帧视频非关键帧
|
||||||
if utils.AVMediaTypeVideo == packet.MediaType() && !s.existVideoKeyFrame && !packet.KeyFrame() {
|
if utils.AVMediaTypeVideo == packet.MediaType && !s.existVideoKeyFrame && !packet.Key {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 丢弃前一组GOP
|
// 丢弃前一组GOP
|
||||||
videoKeyFrame := utils.AVMediaTypeVideo == packet.MediaType() && packet.KeyFrame()
|
videoKeyFrame := utils.AVMediaTypeVideo == packet.MediaType && packet.Key
|
||||||
if videoKeyFrame {
|
if videoKeyFrame {
|
||||||
if s.existVideoKeyFrame {
|
if s.existVideoKeyFrame {
|
||||||
s.discard()
|
s.discard()
|
||||||
@@ -56,7 +57,7 @@ func (s *streamBuffer) AddPacket(packet utils.AVPacket) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *streamBuffer) SetDiscardHandler(handler func(packet utils.AVPacket)) {
|
func (s *streamBuffer) SetDiscardHandler(handler func(packet *avformat.AVPacket)) {
|
||||||
s.discardHandler = handler
|
s.discardHandler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,36 +66,36 @@ func (s *streamBuffer) discard() {
|
|||||||
pkt := s.buffer.Pop()
|
pkt := s.buffer.Pop()
|
||||||
|
|
||||||
if s.discardHandler != nil {
|
if s.discardHandler != nil {
|
||||||
s.discardHandler(pkt.(utils.AVPacket))
|
s.discardHandler(pkt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.existVideoKeyFrame = false
|
s.existVideoKeyFrame = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *streamBuffer) Peek(index int) utils.AVPacket {
|
func (s *streamBuffer) Peek(index int) *avformat.AVPacket {
|
||||||
utils.Assert(index < s.buffer.Size())
|
utils.Assert(index < s.buffer.Size())
|
||||||
head, tail := s.buffer.Data()
|
head, tail := s.buffer.Data()
|
||||||
|
|
||||||
if index < len(head) {
|
if index < len(head) {
|
||||||
return head[index].(utils.AVPacket)
|
return head[index]
|
||||||
} else {
|
} else {
|
||||||
return tail[index-len(head)].(utils.AVPacket)
|
return tail[index-len(head)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *streamBuffer) PeekAll(handler func(packet utils.AVPacket)) {
|
func (s *streamBuffer) PeekAll(handler func(packet *avformat.AVPacket)) {
|
||||||
head, tail := s.buffer.Data()
|
head, tail := s.buffer.Data()
|
||||||
|
|
||||||
if head != nil {
|
if head != nil {
|
||||||
for _, value := range head {
|
for _, value := range head {
|
||||||
handler(value.(utils.AVPacket))
|
handler(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tail != nil {
|
if tail != nil {
|
||||||
for _, value := range tail {
|
for _, value := range tail {
|
||||||
handler(value.(utils.AVPacket))
|
handler(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,5 +113,5 @@ func (s *streamBuffer) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewStreamBuffer() GOPBuffer {
|
func NewStreamBuffer() GOPBuffer {
|
||||||
return &streamBuffer{buffer: collections.NewRingBuffer(1000), existVideoKeyFrame: false}
|
return &streamBuffer{buffer: collections.NewRingBuffer[*avformat.AVPacket](1000), existVideoKeyFrame: false}
|
||||||
}
|
}
|
||||||
|
|||||||
173
stream/m3u8_writer.go
Normal file
173
stream/m3u8_writer.go
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
package stream
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/lkmio/avformat/collections"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExtM3u = "EXTM3U"
|
||||||
|
ExtXVersion = "EXT-X-VERSION" //在文件中唯一
|
||||||
|
|
||||||
|
ExtINF = "EXTINF" //<duration>(浮点类型, 版本小于3用整型),[<title>]
|
||||||
|
ExXByteRange = "EXT-X-BYTERANGE" //版本4及以上,分片位置
|
||||||
|
ExtXDiscontinuity = "EXT-X-DISCONTINUITY" //后面的切片不连续, 文件格式、时间戳发生变化
|
||||||
|
ExtXKey = "EXT-X-KEY" //加密使用
|
||||||
|
ExtXMap = "EXT-X-MAP" //音视频的元数据
|
||||||
|
ExtXProgramDateTime = "EXT-X-PROGRAM-DATE-TIME"
|
||||||
|
ExtXDateRange = "EXT-X-DATERANGE"
|
||||||
|
|
||||||
|
ExtXTargetDuration = "EXT-X-TARGETDURATION" //切片最大时长, 整型单位秒
|
||||||
|
ExtXMediaSequence = "EXT-X-MEDIA-SEQUENCE" //第一个切片序号
|
||||||
|
ExtXDiscontinuitySequence = "EXT-X-DISCONTINUITY-SEQUENCE"
|
||||||
|
ExtXEndList = "EXT-X-ENDLIST"
|
||||||
|
ExtXPlaylistType = "EXT-X-PLAYLIST-TYPE"
|
||||||
|
ExtXIFramesOnly = "EXT-X-I-FRAMES-ONLY"
|
||||||
|
|
||||||
|
ExtXMedia = "EXT-X-MEDIA"
|
||||||
|
ExtXStreamINF = "EXT-X-STREAM-INF"
|
||||||
|
ExtXIFrameStreamINF = "EXT-X-I-FRAME-STREAM-INF"
|
||||||
|
ExtXSessionData = "EXT-X-SESSION-DATA"
|
||||||
|
ExtXSessionKey = "EXT-X-SESSION-KEY"
|
||||||
|
|
||||||
|
ExtXIndependentSegments = "EXT-X-INDEPENDENT-SEGMENTS"
|
||||||
|
ExtXStart = "EXT-X-START"
|
||||||
|
)
|
||||||
|
|
||||||
|
//HttpContent-Type头必须是"application/vnd.apple.mpegurl"或"audio/mpegurl"
|
||||||
|
//无BOM
|
||||||
|
|
||||||
|
type Segment struct {
|
||||||
|
Duration float32
|
||||||
|
Url string
|
||||||
|
Sequence int
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type M3U8Writer interface {
|
||||||
|
// AddSegment 添加切片
|
||||||
|
//@Params duration 切片时长
|
||||||
|
//@Params url m3u8列表中切片的url
|
||||||
|
//@Params sequence m3u8列表中的切片序号
|
||||||
|
//@Params path 切片位于磁盘中的绝对路径
|
||||||
|
AddSegment(duration float32, url string, sequence int, path string)
|
||||||
|
|
||||||
|
String() string
|
||||||
|
|
||||||
|
// Size 返回切片文件数量
|
||||||
|
Size() int
|
||||||
|
|
||||||
|
// Get Head 返回指定索引切片文件
|
||||||
|
Get(index int) Segment
|
||||||
|
}
|
||||||
|
|
||||||
|
type m3u8Writer struct {
|
||||||
|
stringBuffer *bytes.Buffer
|
||||||
|
segments *collections.Queue[Segment]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *m3u8Writer) AddSegment(duration float32 /*title string,*/, url string, sequence int, path string) {
|
||||||
|
if m.segments.IsFull() {
|
||||||
|
m.segments.Pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
m.segments.Push(Segment{Duration: duration, Url: url, Sequence: sequence, Path: path})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回切片时长最长的值(秒)
|
||||||
|
func (m *m3u8Writer) targetDuration() int {
|
||||||
|
var targetDuration int
|
||||||
|
head, tail := m.segments.Data()
|
||||||
|
|
||||||
|
compute := func(playlist []Segment) {
|
||||||
|
for _, segment := range playlist {
|
||||||
|
// 会影响播放器缓存.
|
||||||
|
round := int(math.Ceil(float64(segment.Duration)))
|
||||||
|
if round > targetDuration {
|
||||||
|
targetDuration = round
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if head != nil {
|
||||||
|
compute(head)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tail != nil {
|
||||||
|
compute(tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *m3u8Writer) String() string {
|
||||||
|
// 仅实现简单的播放列表
|
||||||
|
head, tail := m.segments.Data()
|
||||||
|
if head == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
m.stringBuffer.Reset()
|
||||||
|
m.stringBuffer.WriteString("#EXTM3U\r\n")
|
||||||
|
// 仅实现第三个版本
|
||||||
|
m.stringBuffer.WriteString("#EXT-X-VERSION:3\r\n")
|
||||||
|
m.stringBuffer.WriteString("#EXT-X-TARGETDURATION:")
|
||||||
|
m.stringBuffer.WriteString(strconv.Itoa(m.targetDuration()))
|
||||||
|
m.stringBuffer.WriteString("\r\n")
|
||||||
|
m.stringBuffer.WriteString("#EXT-X-MEDIA-SEQUENCE:")
|
||||||
|
m.stringBuffer.WriteString(strconv.Itoa(head[0].Sequence))
|
||||||
|
m.stringBuffer.WriteString("\r\n")
|
||||||
|
|
||||||
|
appendSegments := func(playlist []Segment) {
|
||||||
|
for _, segment := range playlist {
|
||||||
|
m.stringBuffer.WriteString("#EXTINF:")
|
||||||
|
m.stringBuffer.WriteString(strconv.FormatFloat(float64(segment.Duration), 'f', -1, 32))
|
||||||
|
m.stringBuffer.WriteString(",\r\n")
|
||||||
|
m.stringBuffer.WriteString(segment.Url + "%s") // %s用于替换每个sink的拉流key
|
||||||
|
m.stringBuffer.WriteString("\r\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if head != nil {
|
||||||
|
appendSegments(head)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tail != nil {
|
||||||
|
appendSegments(tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.stringBuffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *m3u8Writer) Size() int {
|
||||||
|
var size int
|
||||||
|
head, tail := m.segments.Data()
|
||||||
|
|
||||||
|
if head != nil {
|
||||||
|
size += len(head)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tail != nil {
|
||||||
|
size += len(tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *m3u8Writer) Get(index int) Segment {
|
||||||
|
head, tail := m.segments.Data()
|
||||||
|
if index >= len(head) {
|
||||||
|
return tail[index-len(head)]
|
||||||
|
} else {
|
||||||
|
return head[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewM3U8Writer(len int) M3U8Writer {
|
||||||
|
return &m3u8Writer{
|
||||||
|
stringBuffer: bytes.NewBuffer(make([]byte, 0, 1024*10)),
|
||||||
|
segments: collections.NewQueue[Segment](len),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,9 @@ package stream
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/log"
|
"github.com/lkmio/lkm/log"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
|||||||
224
stream/source.go
224
stream/source.go
@@ -2,16 +2,16 @@ package stream
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lkmio/avformat/collections"
|
"github.com/lkmio/avformat"
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/lkm/log"
|
"github.com/lkmio/lkm/log"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lkmio/avformat/stream"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/transcode"
|
"github.com/lkmio/lkm/transcode"
|
||||||
)
|
)
|
||||||
@@ -64,23 +64,8 @@ type Source interface {
|
|||||||
// IsCompleted 所有推流track是否解析完毕
|
// IsCompleted 所有推流track是否解析完毕
|
||||||
IsCompleted() bool
|
IsCompleted() bool
|
||||||
|
|
||||||
// FindOrCreatePacketBuffer 查找或者创建AVPacket的内存池
|
|
||||||
FindOrCreatePacketBuffer(index int, mediaType utils.AVMediaType) collections.MemoryPool
|
|
||||||
|
|
||||||
// OnDiscardPacket GOP缓存溢出回调, 释放AVPacket
|
// OnDiscardPacket GOP缓存溢出回调, 释放AVPacket
|
||||||
OnDiscardPacket(pkt utils.AVPacket)
|
OnDiscardPacket(pkt *avformat.AVPacket)
|
||||||
|
|
||||||
// OnDeMuxStream 解析出AVStream回调
|
|
||||||
OnDeMuxStream(stream utils.AVStream)
|
|
||||||
|
|
||||||
// OnDeMuxStreamDone 所有track解析完毕回调, 后续的OnDeMuxStream回调不再处理
|
|
||||||
OnDeMuxStreamDone()
|
|
||||||
|
|
||||||
// OnDeMuxPacket 解析出AvPacket回调
|
|
||||||
OnDeMuxPacket(packet utils.AVPacket)
|
|
||||||
|
|
||||||
// OnDeMuxDone 所有流解析完毕回调
|
|
||||||
OnDeMuxDone()
|
|
||||||
|
|
||||||
Init(receiveQueueSize int)
|
Init(receiveQueueSize int)
|
||||||
|
|
||||||
@@ -135,7 +120,7 @@ type PublishSource struct {
|
|||||||
state SessionState
|
state SessionState
|
||||||
Conn net.Conn
|
Conn net.Conn
|
||||||
|
|
||||||
TransDeMuxer stream.DeMuxer // 负责从推流协议中解析出AVStream和AVPacket
|
TransDemuxer avformat.Demuxer // 负责从推流协议中解析出AVStream和AVPacket
|
||||||
recordSink Sink // 每个Source的录制流
|
recordSink Sink // 每个Source的录制流
|
||||||
recordFilePath string // 录制流文件路径
|
recordFilePath string // 录制流文件路径
|
||||||
hlsStream TransStream // HLS传输流, 如果开启, 在@see writeHeader 函数中直接创建, 如果等拉流时再创建, 会进一步加大HLS延迟.
|
hlsStream TransStream // HLS传输流, 如果开启, 在@see writeHeader 函数中直接创建, 如果等拉流时再创建, 会进一步加大HLS延迟.
|
||||||
@@ -143,7 +128,7 @@ type PublishSource struct {
|
|||||||
videoTranscoders []transcode.Transcoder // 视频解码器
|
videoTranscoders []transcode.Transcoder // 视频解码器
|
||||||
originTracks TrackManager // 推流的音视频Streams
|
originTracks TrackManager // 推流的音视频Streams
|
||||||
allStreamTracks TrackManager // 推流Streams+转码器获得的Stream
|
allStreamTracks TrackManager // 推流Streams+转码器获得的Stream
|
||||||
pktBuffers [8]collections.MemoryPool // 推流每路的AVPacket缓存, AVPacket的data从该内存池中分配. 在GOP缓存溢出时,释放池中内存.
|
//pktBuffers [8]collections.MemoryPool // 推流每路的AVPacket缓存, AVPacket的data从该内存池中分配. 在GOP缓存溢出时,释放池中内存.
|
||||||
gopBuffer GOPBuffer // GOP缓存, 音频和视频混合使用, 以视频关键帧为界, 缓存第二个视频关键帧时, 释放前一组gop. 如果不存在视频流, 不缓存音频
|
gopBuffer GOPBuffer // GOP缓存, 音频和视频混合使用, 以视频关键帧为界, 缓存第二个视频关键帧时, 释放前一组gop. 如果不存在视频流, 不缓存音频
|
||||||
|
|
||||||
closed atomic.Bool // source是否已经关闭
|
closed atomic.Bool // source是否已经关闭
|
||||||
@@ -152,7 +137,7 @@ type PublishSource struct {
|
|||||||
|
|
||||||
probeTimer *time.Timer // track解析超时计时器, 触发时执行@see writeHeader
|
probeTimer *time.Timer // track解析超时计时器, 触发时执行@see writeHeader
|
||||||
|
|
||||||
TransStreams map[TransStreamID]TransStream // 所有的输出流, 持有Sink
|
TransStreams map[TransStreamID]TransStream // 所有输出流
|
||||||
sinks map[SinkID]Sink // 保存所有Sink
|
sinks map[SinkID]Sink // 保存所有Sink
|
||||||
slowSinks map[SinkID]Sink // 因推流慢被挂起的sink队列
|
slowSinks map[SinkID]Sink // 因推流慢被挂起的sink队列
|
||||||
TransStreamSinks map[TransStreamID]map[SinkID]Sink // 输出流对应的Sink
|
TransStreamSinks map[TransStreamID]map[SinkID]Sink // 输出流对应的Sink
|
||||||
@@ -169,6 +154,7 @@ type PublishSource struct {
|
|||||||
urlValues url.Values // 推流url携带的参数
|
urlValues url.Values // 推流url携带的参数
|
||||||
createTime time.Time // source创建时间
|
createTime time.Time // source创建时间
|
||||||
statistics *BitrateStatistics // 码流统计
|
statistics *BitrateStatistics // 码流统计
|
||||||
|
streamLogger avformat.OnUnpackStream2FileHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishSource) SetLastPacketTime(time2 time.Time) {
|
func (s *PublishSource) SetLastPacketTime(time2 time.Time) {
|
||||||
@@ -220,6 +206,17 @@ func (s *PublishSource) Init(receiveQueueSize int) {
|
|||||||
s.slowSinks = make(map[SinkID]Sink, 12)
|
s.slowSinks = make(map[SinkID]Sink, 12)
|
||||||
s.TransStreamSinks = make(map[TransStreamID]map[SinkID]Sink, len(transStreamFactories)+1)
|
s.TransStreamSinks = make(map[TransStreamID]map[SinkID]Sink, len(transStreamFactories)+1)
|
||||||
s.statistics = NewBitrateStatistics()
|
s.statistics = NewBitrateStatistics()
|
||||||
|
|
||||||
|
// 此处设置的探测时长, 只是为了保证在probeTimeout触发前, 一直在探测
|
||||||
|
s.TransDemuxer.SetProbeDuration(60000)
|
||||||
|
|
||||||
|
// 启动探测超时计时器
|
||||||
|
s.probeTimer = time.AfterFunc(time.Duration(AppConfig.ProbeTimeout)*time.Millisecond, func() {
|
||||||
|
s.PostEvent(func() {
|
||||||
|
// s.writeHeader()
|
||||||
|
s.TransDemuxer.ProbeComplete()
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishSource) CreateDefaultOutStreams() {
|
func (s *PublishSource) CreateDefaultOutStreams() {
|
||||||
@@ -255,28 +252,6 @@ func (s *PublishSource) CreateDefaultOutStreams() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindOrCreatePacketBuffer 查找或者创建AVPacket的内存池
|
|
||||||
func (s *PublishSource) FindOrCreatePacketBuffer(index int, mediaType utils.AVMediaType) collections.MemoryPool {
|
|
||||||
if index >= cap(s.pktBuffers) {
|
|
||||||
panic("流路数过多...")
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.pktBuffers[index] == nil {
|
|
||||||
if utils.AVMediaTypeAudio == mediaType {
|
|
||||||
s.pktBuffers[index] = collections.NewRbMemoryPool(48000 * 12)
|
|
||||||
} else if AppConfig.GOPCache {
|
|
||||||
// 开启GOP缓存
|
|
||||||
s.pktBuffers[index] = collections.NewRbMemoryPool(AppConfig.GOPBufferSize)
|
|
||||||
} else {
|
|
||||||
// 未开启GOP缓存
|
|
||||||
// 1M缓存大小, 单帧绰绰有余
|
|
||||||
s.pktBuffers[index] = collections.NewRbMemoryPool(1024 * 1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.pktBuffers[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PublishSource) Input(data []byte) error {
|
func (s *PublishSource) Input(data []byte) error {
|
||||||
s.streamPipe <- data
|
s.streamPipe <- data
|
||||||
s.statistics.Input(len(data))
|
s.statistics.Input(len(data))
|
||||||
@@ -328,20 +303,20 @@ func (s *PublishSource) CreateTransStream(id TransStreamID, protocol TransStream
|
|||||||
|
|
||||||
func (s *PublishSource) DispatchGOPBuffer(transStream TransStream) {
|
func (s *PublishSource) DispatchGOPBuffer(transStream TransStream) {
|
||||||
if s.gopBuffer != nil {
|
if s.gopBuffer != nil {
|
||||||
s.gopBuffer.PeekAll(func(packet utils.AVPacket) {
|
s.gopBuffer.PeekAll(func(packet *avformat.AVPacket) {
|
||||||
s.DispatchPacket(transStream, packet)
|
s.DispatchPacket(transStream, packet)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DispatchPacket 分发AVPacket
|
// DispatchPacket 分发AVPacket
|
||||||
func (s *PublishSource) DispatchPacket(transStream TransStream, packet utils.AVPacket) {
|
func (s *PublishSource) DispatchPacket(transStream TransStream, packet *avformat.AVPacket) {
|
||||||
data, timestamp, videoKey, err := transStream.Input(packet)
|
data, timestamp, videoKey, err := transStream.Input(packet)
|
||||||
if err != nil || len(data) < 1 {
|
if err != nil || len(data) < 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.DispatchBuffer(transStream, packet.Index(), data, timestamp, videoKey)
|
s.DispatchBuffer(transStream, packet.Index, data, timestamp, videoKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DispatchBuffer 分发传输流
|
// DispatchBuffer 分发传输流
|
||||||
@@ -427,26 +402,26 @@ func (s *PublishSource) doAddSink(sink Sink) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !disableAudio && utils.AVCodecIdNONE == audioCodecId {
|
if !disableAudio && utils.AVCodecIdNONE == audioCodecId {
|
||||||
audioCodecId = audioTrack.Stream.CodecId()
|
audioCodecId = audioTrack.Stream.CodecID
|
||||||
}
|
}
|
||||||
if !disableVideo && utils.AVCodecIdNONE == videoCodecId {
|
if !disableVideo && utils.AVCodecIdNONE == videoCodecId {
|
||||||
videoCodecId = videoTrack.Stream.CodecId()
|
videoCodecId = videoTrack.Stream.CodecID
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建音频转码器
|
// 创建音频转码器
|
||||||
if !disableAudio && audioCodecId != audioTrack.Stream.CodecId() {
|
if !disableAudio && audioCodecId != audioTrack.Stream.CodecID {
|
||||||
utils.Assert(false)
|
utils.Assert(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建视频转码器
|
// 创建视频转码器
|
||||||
if !disableVideo && videoCodecId != videoTrack.Stream.CodecId() {
|
if !disableVideo && videoCodecId != videoTrack.Stream.CodecID {
|
||||||
utils.Assert(false)
|
utils.Assert(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找传输流需要的所有track
|
// 查找传输流需要的所有track
|
||||||
var tracks []*Track
|
var tracks []*Track
|
||||||
for _, track := range s.originTracks.All() {
|
for _, track := range s.originTracks.All() {
|
||||||
if disableVideo && track.Stream.Type() == utils.AVMediaTypeVideo {
|
if disableVideo && track.Stream.MediaType == utils.AVMediaTypeVideo {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,7 +479,6 @@ func (s *PublishSource) doAddSink(sink Sink) bool {
|
|||||||
_, ok := sink.GetConn().(*transport.Conn)
|
_, ok := sink.GetConn().(*transport.Conn)
|
||||||
if ok && sink.IsTCPStreaming() && transStream.OutStreamBufferCapacity() > 2 {
|
if ok && sink.IsTCPStreaming() && transStream.OutStreamBufferCapacity() > 2 {
|
||||||
length := transStream.OutStreamBufferCapacity() - 2
|
length := transStream.OutStreamBufferCapacity() - 2
|
||||||
fmt.Printf("发送缓冲区容量: %d\r\n", length)
|
|
||||||
sink.EnableAsyncWriteMode(length)
|
sink.EnableAsyncWriteMode(length)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,17 +591,17 @@ func (s *PublishSource) DoClose() {
|
|||||||
|
|
||||||
s.closed.Store(true)
|
s.closed.Store(true)
|
||||||
|
|
||||||
if s.TransDeMuxer != nil {
|
if s.TransDemuxer != nil {
|
||||||
s.TransDeMuxer.Close()
|
s.TransDemuxer.Close()
|
||||||
s.TransDeMuxer = nil
|
s.TransDemuxer = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空未写完的buffer
|
// 清空未写完的buffer
|
||||||
for _, buffer := range s.pktBuffers {
|
//for _, buffer := range s.pktBuffers {
|
||||||
if buffer != nil {
|
// if buffer != nil {
|
||||||
buffer.Reset()
|
// buffer.Reset()
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
// 释放GOP缓存
|
// 释放GOP缓存
|
||||||
if s.gopBuffer != nil {
|
if s.gopBuffer != nil {
|
||||||
@@ -735,40 +709,9 @@ func (s *PublishSource) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OnDiscardPacket GOP缓存溢出丢弃回调
|
// OnDiscardPacket GOP缓存溢出丢弃回调
|
||||||
func (s *PublishSource) OnDiscardPacket(packet utils.AVPacket) {
|
func (s *PublishSource) OnDiscardPacket(packet *avformat.AVPacket) {
|
||||||
s.FindOrCreatePacketBuffer(packet.Index(), packet.MediaType()).FreeHead()
|
if s.TransDemuxer != nil {
|
||||||
}
|
s.TransDemuxer.DiscardHeadPacket(packet.BufferIndex)
|
||||||
|
|
||||||
func (s *PublishSource) OnDeMuxStream(stream utils.AVStream) {
|
|
||||||
if s.completed {
|
|
||||||
log.Sugar.Warnf("添加%s track失败,已经WriteHeader. source: %s", stream.Type().ToString(), s.ID)
|
|
||||||
return
|
|
||||||
} else if !s.NotTrackAdded(stream.Index()) {
|
|
||||||
log.Sugar.Warnf("添加%s track失败,已经添加索引为%d的track. source: %s", stream.Type().ToString(), stream.Index(), s.ID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.originTracks.Add(NewTrack(stream, 0, 0))
|
|
||||||
s.allStreamTracks.Add(NewTrack(stream, 0, 0))
|
|
||||||
|
|
||||||
// 启动track解析超时计时器
|
|
||||||
if len(s.originTracks.All()) == 1 {
|
|
||||||
s.probeTimer = time.AfterFunc(time.Duration(AppConfig.ProbeTimeout)*time.Millisecond, func() {
|
|
||||||
s.PostEvent(func() {
|
|
||||||
s.writeHeader()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if utils.AVMediaTypeVideo == stream.Type() {
|
|
||||||
s.existVideo = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建GOPBuffer
|
|
||||||
if AppConfig.GOPCache && s.existVideo && s.gopBuffer == nil {
|
|
||||||
s.gopBuffer = NewStreamBuffer()
|
|
||||||
// 设置GOP缓存溢出回调
|
|
||||||
s.gopBuffer.SetDiscardHandler(s.OnDiscardPacket)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -797,7 +740,7 @@ func (s *PublishSource) writeHeader() {
|
|||||||
// 恢复每路track的时间戳
|
// 恢复每路track的时间戳
|
||||||
tracks := s.originTracks.All()
|
tracks := s.originTracks.All()
|
||||||
for _, track := range tracks {
|
for _, track := range tracks {
|
||||||
timestamps := streamInfo.Timestamps[track.Stream.CodecId()]
|
timestamps := streamInfo.Timestamps[track.Stream.CodecID]
|
||||||
track.Dts = timestamps[0]
|
track.Dts = timestamps[0]
|
||||||
track.Pts = timestamps[1]
|
track.Pts = timestamps[1]
|
||||||
}
|
}
|
||||||
@@ -805,7 +748,7 @@ func (s *PublishSource) writeHeader() {
|
|||||||
|
|
||||||
// 纠正GOP中的时间戳
|
// 纠正GOP中的时间戳
|
||||||
if s.gopBuffer != nil && s.gopBuffer.Size() != 0 {
|
if s.gopBuffer != nil && s.gopBuffer.Size() != 0 {
|
||||||
s.gopBuffer.PeekAll(func(packet utils.AVPacket) {
|
s.gopBuffer.PeekAll(func(packet *avformat.AVPacket) {
|
||||||
s.CorrectTimestamp(packet)
|
s.CorrectTimestamp(packet)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -833,7 +776,7 @@ func (s *PublishSource) IsCompleted() bool {
|
|||||||
// NotTrackAdded 返回该index对应的track是否没有添加
|
// NotTrackAdded 返回该index对应的track是否没有添加
|
||||||
func (s *PublishSource) NotTrackAdded(index int) bool {
|
func (s *PublishSource) NotTrackAdded(index int) bool {
|
||||||
for _, track := range s.originTracks.All() {
|
for _, track := range s.originTracks.All() {
|
||||||
if track.Stream.Index() == index {
|
if track.Stream.Index == index {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -841,39 +784,84 @@ func (s *PublishSource) NotTrackAdded(index int) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishSource) OnDeMuxStreamDone() {
|
func (s *PublishSource) CorrectTimestamp(packet *avformat.AVPacket) {
|
||||||
s.writeHeader()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PublishSource) CorrectTimestamp(packet utils.AVPacket) {
|
|
||||||
// 对比第一包的时间戳和上次推流的最后时间戳。如果小于上次的推流时间戳,则在原来的基础上累加。
|
// 对比第一包的时间戳和上次推流的最后时间戳。如果小于上次的推流时间戳,则在原来的基础上累加。
|
||||||
if s.streamEndInfo != nil && !s.timestampModeDecided {
|
if s.streamEndInfo != nil && !s.timestampModeDecided {
|
||||||
s.timestampModeDecided = true
|
s.timestampModeDecided = true
|
||||||
|
|
||||||
timestamps := s.streamEndInfo.Timestamps[packet.CodecId()]
|
timestamps := s.streamEndInfo.Timestamps[packet.CodecID]
|
||||||
s.accumulateTimestamps = true
|
s.accumulateTimestamps = true
|
||||||
log.Sugar.Infof("累加时间戳 上次推流dts: %d, pts: %d", timestamps[0], timestamps[1])
|
log.Sugar.Infof("累加时间戳 上次推流dts: %d, pts: %d", timestamps[0], timestamps[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
track := s.originTracks.Find(packet.CodecId())
|
track := s.originTracks.Find(packet.CodecID)
|
||||||
duration := packet.Duration(packet.Timebase())
|
duration := packet.GetDuration(packet.Timebase)
|
||||||
|
|
||||||
// 根据duration来累加时间戳
|
// 根据duration来累加时间戳
|
||||||
if s.accumulateTimestamps {
|
if s.accumulateTimestamps {
|
||||||
offset := packet.Pts() - packet.Dts()
|
offset := packet.Pts - packet.Dts
|
||||||
packet.SetDts(track.Dts + duration)
|
packet.Dts = track.Dts + duration
|
||||||
packet.SetPts(packet.Dts() + offset)
|
packet.Pts = packet.Dts + offset
|
||||||
}
|
}
|
||||||
|
|
||||||
track.Dts = packet.Dts()
|
track.Dts = packet.Dts
|
||||||
track.Pts = packet.Pts()
|
track.Pts = packet.Pts
|
||||||
track.FrameDuration = int(duration)
|
track.FrameDuration = int(duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishSource) OnDeMuxPacket(packet utils.AVPacket) {
|
func (s *PublishSource) OnNewTrack(track avformat.Track) {
|
||||||
|
if AppConfig.Debug {
|
||||||
|
s.streamLogger.Path = "dump/" + strings.ReplaceAll(s.ID, "/", "_")
|
||||||
|
s.streamLogger.OnNewTrack(track)
|
||||||
|
}
|
||||||
|
|
||||||
|
stream := track.GetStream()
|
||||||
|
|
||||||
|
if s.completed {
|
||||||
|
log.Sugar.Warnf("添加%s track失败,已经WriteHeader. source: %s", stream.MediaType, s.ID)
|
||||||
|
return
|
||||||
|
} else if !s.NotTrackAdded(stream.Index) {
|
||||||
|
log.Sugar.Warnf("添加%s track失败,已经添加索引为%d的track. source: %s", stream.MediaType, stream.Index, s.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.originTracks.Add(NewTrack(stream, 0, 0))
|
||||||
|
s.allStreamTracks.Add(NewTrack(stream, 0, 0))
|
||||||
|
|
||||||
|
if utils.AVMediaTypeVideo == stream.MediaType {
|
||||||
|
s.existVideo = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建GOPBuffer
|
||||||
|
if AppConfig.GOPCache && s.existVideo && s.gopBuffer == nil {
|
||||||
|
s.gopBuffer = NewStreamBuffer()
|
||||||
|
// 设置GOP缓存溢出回调
|
||||||
|
s.gopBuffer.SetDiscardHandler(s.OnDiscardPacket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishSource) OnTrackComplete() {
|
||||||
|
if AppConfig.Debug {
|
||||||
|
s.streamLogger.OnTrackComplete()
|
||||||
|
}
|
||||||
|
|
||||||
|
s.writeHeader()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishSource) OnTrackNotFind() {
|
||||||
|
if AppConfig.Debug {
|
||||||
|
s.streamLogger.OnTrackNotFind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishSource) OnPacket(packet *avformat.AVPacket) {
|
||||||
|
if AppConfig.Debug {
|
||||||
|
s.streamLogger.OnPacket(packet)
|
||||||
|
}
|
||||||
|
|
||||||
// track超时,忽略推流数据
|
// track超时,忽略推流数据
|
||||||
if s.NotTrackAdded(packet.Index()) {
|
if s.NotTrackAdded(packet.Index) {
|
||||||
s.FindOrCreatePacketBuffer(packet.Index(), packet.MediaType()).FreeTail()
|
s.TransDemuxer.DiscardBackPacket(packet.BufferIndex)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -883,7 +871,7 @@ func (s *PublishSource) OnDeMuxPacket(packet utils.AVPacket) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 遇到关键帧, 恢复推流
|
// 遇到关键帧, 恢复推流
|
||||||
if utils.AVMediaTypeVideo == packet.MediaType() && packet.KeyFrame() && len(s.slowSinks) > 0 {
|
if utils.AVMediaTypeVideo == packet.MediaType && packet.Key && len(s.slowSinks) > 0 {
|
||||||
s.ResumeStreaming()
|
s.ResumeStreaming()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -899,14 +887,10 @@ func (s *PublishSource) OnDeMuxPacket(packet utils.AVPacket) {
|
|||||||
|
|
||||||
// 未开启GOP缓存或只存在音频流, 释放掉内存
|
// 未开启GOP缓存或只存在音频流, 释放掉内存
|
||||||
if !AppConfig.GOPCache || !s.existVideo {
|
if !AppConfig.GOPCache || !s.existVideo {
|
||||||
s.FindOrCreatePacketBuffer(packet.Index(), packet.MediaType()).FreeTail()
|
s.TransDemuxer.DiscardBackPacket(packet.BufferIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishSource) OnDeMuxDone() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PublishSource) GetType() SourceType {
|
func (s *PublishSource) GetType() SourceType {
|
||||||
return s.Type
|
return s.Type
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package stream
|
package stream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/lkmio/avformat/libavc"
|
|
||||||
"github.com/lkmio/avformat/libhevc"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"github.com/lkmio/lkm/log"
|
"github.com/lkmio/lkm/log"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -94,7 +91,7 @@ func (s SessionState) String() string {
|
|||||||
panic(fmt.Sprintf("unknown session state %d", s))
|
panic(fmt.Sprintf("unknown session state %d", s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Path2SourceId(path string, suffix string) (string, error) {
|
func Path2SourceID(path string, suffix string) (string, error) {
|
||||||
source := strings.TrimSpace(path)
|
source := strings.TrimSpace(path)
|
||||||
if strings.HasPrefix(source, "/") {
|
if strings.HasPrefix(source, "/") {
|
||||||
source = source[1:]
|
source = source[1:]
|
||||||
@@ -129,95 +126,96 @@ func ParseUrl(name string) (string, url.Values) {
|
|||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtractVideoPacket(codec utils.AVCodecID, key, extractStream bool, data []byte, pts, dts int64, index, timebase int) (utils.AVStream, utils.AVPacket, error) {
|
//
|
||||||
var stream utils.AVStream
|
//func ExtractVideoPacket(codec utils.AVCodecID, key, extractStream bool, data []byte, pts, dts int64, index, timebase int) (*avformat.AVStream, *avformat.AVPacket, error) {
|
||||||
|
// var stream *avformat.AVStream
|
||||||
if utils.AVCodecIdH264 == codec {
|
//
|
||||||
//从关键帧中解析出sps和pps
|
// if utils.AVCodecIdH264 == codec {
|
||||||
if key && extractStream {
|
// //从关键帧中解析出sps和pps
|
||||||
sps, pps, err := libavc.ParseExtraDataFromKeyNALU(data)
|
// if key && extractStream {
|
||||||
if err != nil {
|
// sps, pps, err := avc.ParseExtraDataFromKeyNALU(data)
|
||||||
log.Sugar.Errorf("从关键帧中解析sps pps失败 data:%s", hex.EncodeToString(data))
|
// if err != nil {
|
||||||
return nil, nil, err
|
// log.Sugar.Errorf("从关键帧中解析sps pps失败 data:%s", hex.EncodeToString(data))
|
||||||
}
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
codecData, err := utils.NewAVCCodecData(sps, pps)
|
//
|
||||||
if err != nil {
|
// codecData, err := utils.NewAVCCodecData(sps, pps)
|
||||||
log.Sugar.Errorf("解析sps pps失败 data:%s sps:%s, pps:%s", hex.EncodeToString(data), hex.EncodeToString(sps), hex.EncodeToString(pps))
|
// if err != nil {
|
||||||
return nil, nil, err
|
// log.Sugar.Errorf("解析sps pps失败 data:%s sps:%s, pps:%s", hex.EncodeToString(data), hex.EncodeToString(sps), hex.EncodeToString(pps))
|
||||||
}
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
stream = utils.NewAVStream(utils.AVMediaTypeVideo, 0, codec, codecData.AnnexBExtraData(), codecData)
|
//
|
||||||
}
|
// stream = avformat.NewAVStream(utils.AVMediaTypeVideo, 0, codec, codecData.AnnexBExtraData(), codecData)
|
||||||
|
// }
|
||||||
} else if utils.AVCodecIdH265 == codec {
|
//
|
||||||
if key && extractStream {
|
// } else if utils.AVCodecIdH265 == codec {
|
||||||
vps, sps, pps, err := libhevc.ParseExtraDataFromKeyNALU(data)
|
// if key && extractStream {
|
||||||
if err != nil {
|
// vps, sps, pps, err := hevc.ParseExtraDataFromKeyNALU(data)
|
||||||
log.Sugar.Errorf("从关键帧中解析vps sps pps失败 data:%s", hex.EncodeToString(data))
|
// if err != nil {
|
||||||
return nil, nil, err
|
// log.Sugar.Errorf("从关键帧中解析vps sps pps失败 data:%s", hex.EncodeToString(data))
|
||||||
}
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
codecData, err := utils.NewHEVCCodecData(vps, sps, pps)
|
//
|
||||||
if err != nil {
|
// codecData, err := utils.NewHEVCCodecData(vps, sps, pps)
|
||||||
log.Sugar.Errorf("解析sps pps失败 data:%s vps:%s sps:%s, pps:%s", hex.EncodeToString(data), hex.EncodeToString(vps), hex.EncodeToString(sps), hex.EncodeToString(pps))
|
// if err != nil {
|
||||||
return nil, nil, err
|
// log.Sugar.Errorf("解析sps pps失败 data:%s vps:%s sps:%s, pps:%s", hex.EncodeToString(data), hex.EncodeToString(vps), hex.EncodeToString(sps), hex.EncodeToString(pps))
|
||||||
}
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
stream = utils.NewAVStream(utils.AVMediaTypeVideo, 0, codec, codecData.AnnexBExtraData(), codecData)
|
//
|
||||||
}
|
// stream = avformat.NewAVStream(utils.AVMediaTypeVideo, 0, codec, codecData.AnnexBExtraData(), codecData)
|
||||||
|
// }
|
||||||
}
|
//
|
||||||
|
// }
|
||||||
packet := utils.NewVideoPacket(data, dts, pts, key, utils.PacketTypeAnnexB, codec, index, timebase)
|
//
|
||||||
return stream, packet, nil
|
// packet := avformat.NewVideoPacket(data, dts, pts, key, utils.PacketTypeAnnexB, codec, index, timebase)
|
||||||
}
|
// return stream, packet, nil
|
||||||
|
//}
|
||||||
func ExtractAudioPacket(codec utils.AVCodecID, extractStream bool, data []byte, pts, dts int64, index, timebase int) (utils.AVStream, utils.AVPacket, error) {
|
//
|
||||||
var stream utils.AVStream
|
//func ExtractAudioPacket(codec utils.AVCodecID, extractStream bool, data []byte, pts, dts int64, index, timebase int) (*avformat.AVStream, *avformat.AVPacket, error) {
|
||||||
var packet utils.AVPacket
|
// var stream *avformat.AVStream
|
||||||
if utils.AVCodecIdAAC == codec {
|
// var packet *avformat.AVPacket
|
||||||
//必须包含ADTSHeader
|
// if utils.AVCodecIdAAC == codec {
|
||||||
if len(data) < 7 {
|
// //必须包含ADTSHeader
|
||||||
return nil, nil, fmt.Errorf("need more data")
|
// if len(data) < 7 {
|
||||||
}
|
// return nil, nil, fmt.Errorf("need more data")
|
||||||
|
// }
|
||||||
var skip int
|
//
|
||||||
header, err := utils.ReadADtsFixedHeader(data)
|
// var skip int
|
||||||
if err != nil {
|
// header, err := utils.ReadADtsFixedHeader(data)
|
||||||
log.Sugar.Errorf("读取ADTSHeader失败 data:%s", hex.EncodeToString(data[:7]))
|
// if err != nil {
|
||||||
return nil, nil, err
|
// log.Sugar.Errorf("读取ADTSHeader失败 data:%s", hex.EncodeToString(data[:7]))
|
||||||
} else {
|
// return nil, nil, err
|
||||||
skip = 7
|
// } else {
|
||||||
//跳过ADtsHeader长度
|
// skip = 7
|
||||||
if header.ProtectionAbsent() == 0 {
|
// //跳过ADtsHeader长度
|
||||||
skip += 2
|
// if header.ProtectionAbsent() == 0 {
|
||||||
}
|
// skip += 2
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
if extractStream {
|
//
|
||||||
configData, err := utils.ADtsHeader2MpegAudioConfigData(header)
|
// if extractStream {
|
||||||
config, err := utils.ParseMpeg4AudioConfig(configData)
|
// configData, err := utils.ADtsHeader2MpegAudioConfigData(header)
|
||||||
println(config)
|
// config, err := utils.ParseMpeg4AudioConfig(configData)
|
||||||
if err != nil {
|
// println(config)
|
||||||
log.Sugar.Errorf("adt头转m4ac失败 data:%s", hex.EncodeToString(data[:7]))
|
// if err != nil {
|
||||||
return nil, nil, err
|
// log.Sugar.Errorf("adt头转m4ac失败 data:%s", hex.EncodeToString(data[:7]))
|
||||||
}
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
stream = utils.NewAVStream(utils.AVMediaTypeAudio, index, codec, configData, nil)
|
//
|
||||||
}
|
// stream = avformat.NewAVStream(utils.AVMediaTypeAudio, index, codec, configData, nil)
|
||||||
|
// }
|
||||||
packet = utils.NewAudioPacket(data[skip:], dts, pts, codec, index, timebase)
|
//
|
||||||
} else if utils.AVCodecIdPCMALAW == codec || utils.AVCodecIdPCMMULAW == codec {
|
// packet = utils.NewAudioPacket(data[skip:], dts, pts, codec, index, timebase)
|
||||||
if extractStream {
|
// } else if utils.AVCodecIdPCMALAW == codec || utils.AVCodecIdPCMMULAW == codec {
|
||||||
stream = utils.NewAVStream(utils.AVMediaTypeAudio, index, codec, nil, nil)
|
// if extractStream {
|
||||||
}
|
// stream = avformat.NewAVStream(utils.AVMediaTypeAudio, index, codec, nil, nil)
|
||||||
|
// }
|
||||||
packet = utils.NewAudioPacket(data, dts, pts, codec, index, timebase)
|
//
|
||||||
}
|
// packet = utils.NewAudioPacket(data, dts, pts, codec, index, timebase)
|
||||||
|
// }
|
||||||
return stream, packet, nil
|
//
|
||||||
}
|
// return stream, packet, nil
|
||||||
|
//}
|
||||||
|
|
||||||
// StartReceiveDataTimer 启动收流超时计时器
|
// StartReceiveDataTimer 启动收流超时计时器
|
||||||
func StartReceiveDataTimer(source Source) *time.Timer {
|
func StartReceiveDataTimer(source Source) *time.Timer {
|
||||||
@@ -308,7 +306,7 @@ func LoopEvent(source Source) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := source.Input(data); err != nil {
|
if err := source.Input(data); err != nil {
|
||||||
log.Sugar.Errorf("处理输入流失败 释放source:%s err:%s", source.GetID(), err.Error())
|
log.Sugar.Errorf("解析推流数据发生err: %s 释放source: %s", err.Error(), source.GetID())
|
||||||
source.DoClose()
|
source.DoClose()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package stream
|
package stream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/libhls"
|
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
@@ -14,13 +13,13 @@ func init() {
|
|||||||
streamEndInfoManager = &StreamEndInfoManager{sources: make(map[string]*StreamEndInfo, 32)}
|
streamEndInfoManager = &StreamEndInfoManager{sources: make(map[string]*StreamEndInfo, 32)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamEndInfo 保留结束推流Source的推流信息
|
// StreamEndInfo 保存Source结束推流时的推流信息
|
||||||
// 在结束推流时,如果还有拉流端没有断开,则保留一些推流信息(目前有时间戳、ts切片序号等等)。在下次推流时,使用该时间戳作为新传输流的起始时间戳,保证拉流端在拉流时不会重现pts和dts错误.
|
// 在结束推流时,如果还有拉流端没有断开,则保留一些推流信息(时间戳、ts切片序号等等)。在下次推流时,使用该时间戳作为新传输流的起始时间戳,保证拉流端在拉流时不会出现pts和dts错误.
|
||||||
// 如果在此之前,陆续有拉流端断开,直至sink计数为0,则会不再保留该信息。
|
// 如果重新推流之前,陆续有拉流端断开,直至sink计数为0,删除保存的推流信息。
|
||||||
type StreamEndInfo struct {
|
type StreamEndInfo struct {
|
||||||
ID string
|
ID string
|
||||||
Timestamps map[utils.AVCodecID][2]int64 // 每路track结束时间戳
|
Timestamps map[utils.AVCodecID][2]int64 // 每路track结束时间戳
|
||||||
M3U8Writer libhls.M3U8Writer // 保存M3U8生成器
|
M3U8Writer M3U8Writer // 保存M3U8生成器
|
||||||
PlaylistFormat *string // M3U8播放列表
|
PlaylistFormat *string // M3U8播放列表
|
||||||
RtspTracks map[byte]uint16 // rtsp每路track的结束序号
|
RtspTracks map[byte]uint16 // rtsp每路track的结束序号
|
||||||
FLVPrevTagSize uint32 // flv的最后一个tag大小, 下次生成flv时作为prev tag size
|
FLVPrevTagSize uint32 // flv的最后一个tag大小, 下次生成flv时作为prev tag size
|
||||||
@@ -32,7 +31,7 @@ func EqualsTracks(info *StreamEndInfo, tracks []*Track) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, track := range tracks {
|
for _, track := range tracks {
|
||||||
if _, ok := info.Timestamps[track.Stream.CodecId()]; !ok {
|
if _, ok := info.Timestamps[track.Stream.CodecID]; !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package stream
|
package stream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/lkmio/avformat/transport"
|
|
||||||
"github.com/lkmio/lkm/log"
|
"github.com/lkmio/lkm/log"
|
||||||
|
"github.com/lkmio/transport"
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
package stream
|
package stream
|
||||||
|
|
||||||
import "github.com/lkmio/avformat/utils"
|
import (
|
||||||
|
"github.com/lkmio/avformat"
|
||||||
|
)
|
||||||
|
|
||||||
type Track struct {
|
type Track struct {
|
||||||
Stream utils.AVStream
|
Stream *avformat.AVStream
|
||||||
Pts int64 // 最新的PTS
|
Pts int64 // 最新的PTS
|
||||||
Dts int64 // 最新的DTS
|
Dts int64 // 最新的DTS
|
||||||
FrameDuration int // 单帧时长, timebase和推流一致
|
FrameDuration int // 单帧时长, timebase和推流一致
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTrack(stream utils.AVStream, dts, pts int64) *Track {
|
func NewTrack(stream *avformat.AVStream, dts, pts int64) *Track {
|
||||||
return &Track{stream, dts, pts, 0}
|
return &Track{stream, dts, pts, 0}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ type TrackManager struct {
|
|||||||
|
|
||||||
func (s *TrackManager) Add(track *Track) {
|
func (s *TrackManager) Add(track *Track) {
|
||||||
for _, t := range s.tracks {
|
for _, t := range s.tracks {
|
||||||
utils.Assert(t.Stream.Type() != track.Stream.Type())
|
utils.Assert(t.Stream.MediaType != track.Stream.MediaType)
|
||||||
utils.Assert(t.Stream.CodecId() != track.Stream.CodecId())
|
utils.Assert(t.Stream.CodecID != track.Stream.CodecID)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.tracks = append(s.tracks, track)
|
s.tracks = append(s.tracks, track)
|
||||||
@@ -19,7 +19,7 @@ func (s *TrackManager) Add(track *Track) {
|
|||||||
|
|
||||||
func (s *TrackManager) Find(id utils.AVCodecID) *Track {
|
func (s *TrackManager) Find(id utils.AVCodecID) *Track {
|
||||||
for _, track := range s.tracks {
|
for _, track := range s.tracks {
|
||||||
if track.Stream.CodecId() == id {
|
if track.Stream.CodecID == id {
|
||||||
return track
|
return track
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ func (s *TrackManager) Find(id utils.AVCodecID) *Track {
|
|||||||
|
|
||||||
func (s *TrackManager) FindWithType(mediaType utils.AVMediaType) *Track {
|
func (s *TrackManager) FindWithType(mediaType utils.AVMediaType) *Track {
|
||||||
for _, track := range s.tracks {
|
for _, track := range s.tracks {
|
||||||
if track.Stream.Type() == mediaType {
|
if track.Stream.MediaType == mediaType {
|
||||||
return track
|
return track
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ func (s *TrackManager) FindWithType(mediaType utils.AVMediaType) *Track {
|
|||||||
func (s *TrackManager) FindTracks(id utils.AVCodecID) []*Track {
|
func (s *TrackManager) FindTracks(id utils.AVCodecID) []*Track {
|
||||||
var tracks []*Track
|
var tracks []*Track
|
||||||
for _, track := range s.tracks {
|
for _, track := range s.tracks {
|
||||||
if track.Stream.CodecId() == id {
|
if track.Stream.CodecID == id {
|
||||||
tracks = append(tracks, track)
|
tracks = append(tracks, track)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ func (s *TrackManager) FindTracks(id utils.AVCodecID) []*Track {
|
|||||||
func (s *TrackManager) FindTracksWithType(mediaType utils.AVMediaType) []*Track {
|
func (s *TrackManager) FindTracksWithType(mediaType utils.AVMediaType) []*Track {
|
||||||
var tracks []*Track
|
var tracks []*Track
|
||||||
for _, track := range s.tracks {
|
for _, track := range s.tracks {
|
||||||
if track.Stream.Type() == mediaType {
|
if track.Stream.MediaType == mediaType {
|
||||||
tracks = append(tracks, track)
|
tracks = append(tracks, track)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package stream
|
package stream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/lkmio/avformat"
|
||||||
"github.com/lkmio/avformat/utils"
|
"github.com/lkmio/avformat/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ type TransStream interface {
|
|||||||
SetID(id TransStreamID)
|
SetID(id TransStreamID)
|
||||||
|
|
||||||
// Input 封装传输流, 返回合并写块、时间戳、合并写块是否包含视频关键帧
|
// Input 封装传输流, 返回合并写块、时间戳、合并写块是否包含视频关键帧
|
||||||
Input(packet utils.AVPacket) ([][]byte, int64, bool, error)
|
Input(packet *avformat.AVPacket) ([][]byte, int64, bool, error)
|
||||||
|
|
||||||
AddTrack(track *Track) error
|
AddTrack(track *Track) error
|
||||||
|
|
||||||
@@ -67,13 +68,13 @@ func (t *BaseTransStream) SetID(id TransStreamID) {
|
|||||||
t.ID = id
|
t.ID = id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *BaseTransStream) Input(packet utils.AVPacket) ([][]byte, int64, bool, error) {
|
func (t *BaseTransStream) Input(packet *avformat.AVPacket) ([][]byte, int64, bool, error) {
|
||||||
return nil, -1, false, nil
|
return nil, -1, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *BaseTransStream) AddTrack(track *Track) error {
|
func (t *BaseTransStream) AddTrack(track *Track) error {
|
||||||
t.Tracks = append(t.Tracks, track)
|
t.Tracks = append(t.Tracks, track)
|
||||||
if utils.AVMediaTypeVideo == track.Stream.Type() {
|
if utils.AVMediaTypeVideo == track.Stream.MediaType {
|
||||||
t.ExistVideo = true
|
t.ExistVideo = true
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func GenerateTransStreamID(protocol TransStreamProtocol, tracks ...*Track) Trans
|
|||||||
streamId = uint64(protocol) << 56
|
streamId = uint64(protocol) << 56
|
||||||
|
|
||||||
for i, track := range tracks {
|
for i, track := range tracks {
|
||||||
id, ok := narrowCodecIds[int(track.Stream.CodecId())]
|
id, ok := narrowCodecIds[int(track.Stream.CodecID)]
|
||||||
utils.Assert(ok)
|
utils.Assert(ok)
|
||||||
|
|
||||||
streamId |= uint64(id) << (48 - i*8)
|
streamId |= uint64(id) << (48 - i*8)
|
||||||
|
|||||||
Reference in New Issue
Block a user