mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-12-24 13:48:04 +08:00
refactor: frame converter and mp4 track improvements
- Refactor frame converter implementation - Update mp4 track to use ICodex - General refactoring and code improvements 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
11
plugin/hls/pkg/codec.go
Normal file
11
plugin/hls/pkg/codec.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package hls
|
||||
|
||||
import (
|
||||
"m7s.live/v5/pkg"
|
||||
mpegts "m7s.live/v5/pkg/format/ts"
|
||||
)
|
||||
|
||||
type VideoCodecCtx struct {
|
||||
pkg.IVideoCodecCtx
|
||||
mpegts.MpegtsPESFrame
|
||||
}
|
||||
@@ -13,20 +13,35 @@ import (
|
||||
|
||||
"github.com/quangngotan95/go-m3u8/m3u8"
|
||||
"m7s.live/v5"
|
||||
"m7s.live/v5/pkg"
|
||||
"m7s.live/v5/pkg/codec"
|
||||
pkg "m7s.live/v5/pkg"
|
||||
"m7s.live/v5/pkg/config"
|
||||
mpegts "m7s.live/v5/pkg/format/ts"
|
||||
"m7s.live/v5/pkg/task"
|
||||
"m7s.live/v5/pkg/util"
|
||||
mpegts "m7s.live/v5/plugin/hls/pkg/ts"
|
||||
)
|
||||
|
||||
// Plugin-specific progress step names for HLS
|
||||
const (
|
||||
StepM3U8Fetch pkg.StepName = "m3u8_fetch"
|
||||
StepM3U8Parse pkg.StepName = "parse" // hls playlist parse
|
||||
StepTsDownload pkg.StepName = "ts_download"
|
||||
)
|
||||
|
||||
// Fixed progress steps for HLS pull workflow
|
||||
var hlsPullSteps = []pkg.StepDef{
|
||||
{Name: pkg.StepPublish, Description: "Publishing stream"},
|
||||
{Name: StepM3U8Fetch, Description: "Fetching M3U8 playlist"},
|
||||
{Name: StepM3U8Parse, Description: "Parsing M3U8 playlist"},
|
||||
{Name: StepTsDownload, Description: "Downloading TS segments"},
|
||||
{Name: pkg.StepStreaming, Description: "Processing and streaming"},
|
||||
}
|
||||
|
||||
func NewPuller(conf config.Pull) m7s.IPuller {
|
||||
return &Puller{}
|
||||
}
|
||||
|
||||
type Puller struct {
|
||||
task.Job
|
||||
task.Task
|
||||
PullJob m7s.PullJob
|
||||
Video M3u8Info
|
||||
Audio M3u8Info
|
||||
@@ -44,9 +59,16 @@ func (p *Puller) GetTs(key string) (any, bool) {
|
||||
}
|
||||
|
||||
func (p *Puller) Start() (err error) {
|
||||
// Initialize progress tracking for pull operations
|
||||
p.PullJob.SetProgressStepsDefs(hlsPullSteps)
|
||||
|
||||
if err = p.PullJob.Publish(); err != nil {
|
||||
p.PullJob.Fail(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
p.PullJob.GoToStepConst(StepM3U8Fetch)
|
||||
|
||||
p.PullJob.Publisher.Speed = 1
|
||||
if p.PullJob.PublishConfig.RelayMode != config.RelayModeRemux {
|
||||
MemoryTs.Store(p.PullJob.StreamPath, p)
|
||||
@@ -65,99 +87,28 @@ func (p *Puller) Run() (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Video.Req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0 Monibuca/5.0")
|
||||
return p.pull(&p.Video)
|
||||
}
|
||||
|
||||
func (p *Puller) writePublisher(t *mpegts.MpegTsStream) {
|
||||
var audioCodec codec.FourCC
|
||||
var audioStreamType, videoStreamType byte
|
||||
for pes := range t.PESChan {
|
||||
if p.Err() != nil {
|
||||
continue
|
||||
}
|
||||
if pes.Header.Dts == 0 {
|
||||
pes.Header.Dts = pes.Header.Pts
|
||||
}
|
||||
switch pes.Header.StreamID & 0xF0 {
|
||||
case mpegts.STREAM_ID_VIDEO:
|
||||
if videoStreamType == 0 {
|
||||
for _, s := range t.PMT.Stream {
|
||||
videoStreamType = s.StreamType
|
||||
break
|
||||
}
|
||||
}
|
||||
switch videoStreamType {
|
||||
case mpegts.STREAM_TYPE_H264:
|
||||
var annexb pkg.AnnexB
|
||||
annexb.PTS = time.Duration(pes.Header.Pts)
|
||||
annexb.DTS = time.Duration(pes.Header.Dts)
|
||||
annexb.AppendOne(pes.Payload)
|
||||
p.PullJob.Publisher.WriteVideo(&annexb)
|
||||
case mpegts.STREAM_TYPE_H265:
|
||||
var annexb pkg.AnnexB
|
||||
annexb.PTS = time.Duration(pes.Header.Pts)
|
||||
annexb.DTS = time.Duration(pes.Header.Dts)
|
||||
annexb.Hevc = true
|
||||
annexb.AppendOne(pes.Payload)
|
||||
p.PullJob.Publisher.WriteVideo(&annexb)
|
||||
default:
|
||||
if audioStreamType == 0 {
|
||||
for _, s := range t.PMT.Stream {
|
||||
audioStreamType = s.StreamType
|
||||
switch s.StreamType {
|
||||
case mpegts.STREAM_TYPE_AAC:
|
||||
audioCodec = codec.FourCC_MP4A
|
||||
case mpegts.STREAM_TYPE_G711A:
|
||||
audioCodec = codec.FourCC_ALAW
|
||||
case mpegts.STREAM_TYPE_G711U:
|
||||
audioCodec = codec.FourCC_ULAW
|
||||
}
|
||||
}
|
||||
}
|
||||
switch audioStreamType {
|
||||
case mpegts.STREAM_TYPE_AAC:
|
||||
var adts pkg.ADTS
|
||||
adts.DTS = time.Duration(pes.Header.Dts)
|
||||
adts.AppendOne(pes.Payload)
|
||||
p.PullJob.Publisher.WriteAudio(&adts)
|
||||
default:
|
||||
var raw pkg.RawAudio
|
||||
raw.FourCC = audioCodec
|
||||
raw.Timestamp = time.Duration(pes.Header.Pts) * time.Millisecond / 90
|
||||
raw.AppendOne(pes.Payload)
|
||||
p.PullJob.Publisher.WriteAudio(&raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Puller) pull(info *M3u8Info) (err error) {
|
||||
//请求失败自动退出
|
||||
req := info.Req.WithContext(p.Context)
|
||||
client := p.PullJob.HTTPClient
|
||||
sequence := -1
|
||||
lastTs := make(map[string]bool)
|
||||
tsbuffer := make(chan io.ReadCloser)
|
||||
tsRing := util.NewRing[string](6)
|
||||
var tsReader *mpegts.MpegTsStream
|
||||
var closer io.Closer
|
||||
p.OnDispose(func() {
|
||||
if closer != nil {
|
||||
closer.Close()
|
||||
}
|
||||
})
|
||||
if p.PullJob.PublishConfig.RelayMode != config.RelayModeRelay {
|
||||
tsReader = &mpegts.MpegTsStream{
|
||||
PESChan: make(chan *mpegts.MpegTsPESPacket, 50),
|
||||
PESBuffer: make(map[uint16]*mpegts.MpegTsPESPacket),
|
||||
Allocator: util.NewScalableMemoryAllocator(1 << util.MinPowerOf2),
|
||||
}
|
||||
go p.writePublisher(tsReader)
|
||||
defer close(tsReader.PESChan)
|
||||
tsReader.Publisher = p.PullJob.Publisher
|
||||
defer tsReader.Allocator.Recycle()
|
||||
}
|
||||
defer close(tsbuffer)
|
||||
var maxResolution *m3u8.PlaylistItem
|
||||
for errcount := 0; err == nil; err = p.Err() {
|
||||
p.Debug("pull m3u8", "url", req.URL.String())
|
||||
resp, err1 := client.Do(req)
|
||||
if err1 != nil {
|
||||
return err1
|
||||
@@ -211,6 +162,7 @@ func (p *Puller) pull(info *M3u8Info) (err error) {
|
||||
p.Video.Req, _ = http.NewRequest("GET", url.String(), nil)
|
||||
p.Video.Req.Header = req.Header
|
||||
req = p.Video.Req
|
||||
sequence = -1
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -254,7 +206,7 @@ func (p *Puller) pull(info *M3u8Info) (err error) {
|
||||
if v.res != nil {
|
||||
info.TSCount++
|
||||
var reader io.Reader = v.res.Body
|
||||
closer = v.res.Body
|
||||
closer := v.res.Body
|
||||
if p.SaveContext != nil && p.SaveContext.Err() == nil {
|
||||
savePath := p.SaveContext.Value("path").(string)
|
||||
os.MkdirAll(filepath.Join(savePath, p.PullJob.StreamPath), 0766)
|
||||
|
||||
@@ -6,11 +6,10 @@ import (
|
||||
"time"
|
||||
|
||||
"m7s.live/v5"
|
||||
"m7s.live/v5/pkg"
|
||||
"m7s.live/v5/pkg/codec"
|
||||
"m7s.live/v5/pkg/config"
|
||||
"m7s.live/v5/pkg/util"
|
||||
mpegts "m7s.live/v5/plugin/hls/pkg/ts"
|
||||
"m7s.live/v5/pkg/format"
|
||||
mpegts "m7s.live/v5/pkg/format/ts"
|
||||
)
|
||||
|
||||
func NewRecorder(conf config.Record) m7s.IRecorder {
|
||||
@@ -19,9 +18,7 @@ func NewRecorder(conf config.Record) m7s.IRecorder {
|
||||
|
||||
type Recorder struct {
|
||||
m7s.DefaultRecorder
|
||||
ts *TsInFile
|
||||
pesAudio *mpegts.MpegtsPESFrame
|
||||
pesVideo *mpegts.MpegtsPESFrame
|
||||
ts TsInFile
|
||||
segmentCount uint32
|
||||
lastTs time.Duration
|
||||
firstSegment bool
|
||||
@@ -40,32 +37,29 @@ func (r *Recorder) createStream(start time.Time) (err error) {
|
||||
}
|
||||
|
||||
func (r *Recorder) writeTailer(end time.Time) {
|
||||
if !r.RecordJob.RecConf.RealTime {
|
||||
if r.ts.file != nil {
|
||||
r.ts.WriteTo(r.ts.file)
|
||||
r.ts.Recycle()
|
||||
}
|
||||
}
|
||||
r.ts.Close()
|
||||
r.WriteTail(end, nil)
|
||||
}
|
||||
|
||||
func (r *Recorder) Dispose() {
|
||||
// 如果当前有未完成的片段,先保存
|
||||
if r.ts != nil {
|
||||
r.ts.Close()
|
||||
}
|
||||
r.writeTailer(time.Now())
|
||||
}
|
||||
|
||||
func (r *Recorder) createNewTs() {
|
||||
var oldPMT util.Buffer
|
||||
if r.ts != nil {
|
||||
oldPMT = r.ts.PMT
|
||||
r.ts.Close()
|
||||
}
|
||||
var err error
|
||||
r.ts, err = NewTsInFile(r.Event.FilePath)
|
||||
if err != nil {
|
||||
r.Error("create ts file failed", "err", err, "path", r.Event.FilePath)
|
||||
return
|
||||
}
|
||||
if oldPMT.Len() > 0 {
|
||||
r.ts.PMT = oldPMT
|
||||
func (r *Recorder) createNewTs() (err error) {
|
||||
if r.RecordJob.RecConf.RealTime {
|
||||
if err = r.ts.Open(r.Event.FilePath); err != nil {
|
||||
r.Error("create ts file failed", "err", err, "path", r.Event.FilePath)
|
||||
}
|
||||
} else {
|
||||
r.ts.path = r.Event.FilePath
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Recorder) writeSegment(ts time.Duration, writeTime time.Time) (err error) {
|
||||
@@ -91,7 +85,13 @@ func (r *Recorder) writeSegment(ts time.Duration, writeTime time.Time) (err erro
|
||||
}
|
||||
|
||||
// 创建新的ts文件
|
||||
r.createNewTs()
|
||||
if err = r.createNewTs(); err != nil {
|
||||
return
|
||||
}
|
||||
if r.RecordJob.RecConf.RealTime {
|
||||
r.ts.file.Write(mpegts.DefaultPATPacket)
|
||||
r.ts.file.Write(r.ts.PMT)
|
||||
}
|
||||
r.segmentCount++
|
||||
r.lastTs = ts
|
||||
}
|
||||
@@ -108,13 +108,10 @@ func (r *Recorder) Run() (err error) {
|
||||
}
|
||||
|
||||
// 初始化HLS相关结构
|
||||
r.createNewTs()
|
||||
r.pesAudio = &mpegts.MpegtsPESFrame{
|
||||
Pid: mpegts.PID_AUDIO,
|
||||
}
|
||||
r.pesVideo = &mpegts.MpegtsPESFrame{
|
||||
Pid: mpegts.PID_VIDEO,
|
||||
if err = r.createNewTs(); err != nil {
|
||||
return
|
||||
}
|
||||
pesAudio, pesVideo := mpegts.CreatePESWriters()
|
||||
r.firstSegment = true
|
||||
|
||||
var audioCodec, videoCodec codec.FourCC
|
||||
@@ -125,20 +122,37 @@ func (r *Recorder) Run() (err error) {
|
||||
videoCodec = suber.Publisher.VideoTrack.FourCC()
|
||||
}
|
||||
r.ts.WritePMTPacket(audioCodec, videoCodec)
|
||||
|
||||
return m7s.PlayBlock(suber, r.ProcessADTS, r.ProcessAnnexB)
|
||||
}
|
||||
|
||||
func (r *Recorder) ProcessADTS(audio *pkg.ADTS) (err error) {
|
||||
return r.ts.WriteAudioFrame( r.RecordJob.Subscriber.AudioReader.AbsTime, audio, r.pesAudio)
|
||||
}
|
||||
|
||||
func (r *Recorder) ProcessAnnexB(video *pkg.AnnexB) (err error) {
|
||||
vr := r.RecordJob.Subscriber.VideoReader
|
||||
if vr.Value.IDR {
|
||||
if err = r.writeSegment(time.Duration(vr.AbsTime)*time.Millisecond, vr.Value.WriteTime); err != nil {
|
||||
return
|
||||
}
|
||||
if ctx.RecConf.RealTime {
|
||||
r.ts.file.Write(mpegts.DefaultPATPacket)
|
||||
r.ts.file.Write(r.ts.PMT)
|
||||
}
|
||||
return r.ts.WriteVideoFrame(vr.AbsTime, video, r.pesVideo)
|
||||
return m7s.PlayBlock(suber, func(audio *format.Mpeg2Audio) (err error) {
|
||||
pesAudio.Pts = uint64(suber.AudioReader.AbsTime) * 90
|
||||
err = pesAudio.WritePESPacket(audio.Memory, &r.ts.RecyclableMemory)
|
||||
if err == nil {
|
||||
if ctx.RecConf.RealTime {
|
||||
r.ts.RecyclableMemory.WriteTo(r.ts.file)
|
||||
r.ts.RecyclableMemory.Recycle()
|
||||
}
|
||||
}
|
||||
return
|
||||
}, func(video *mpegts.VideoFrame) (err error) {
|
||||
vr := r.RecordJob.Subscriber.VideoReader
|
||||
if vr.Value.IDR {
|
||||
if err = r.writeSegment(video.Timestamp, vr.Value.WriteTime); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
pesVideo.IsKeyFrame = video.IDR
|
||||
pesVideo.Pts = uint64(vr.AbsTime+video.GetCTS32()) * 90
|
||||
pesVideo.Dts = uint64(vr.AbsTime) * 90
|
||||
err = pesVideo.WritePESPacket(video.Memory, &r.ts.RecyclableMemory)
|
||||
if err == nil {
|
||||
if ctx.RecConf.RealTime {
|
||||
r.ts.RecyclableMemory.WriteTo(r.ts.file)
|
||||
r.ts.RecyclableMemory.Recycle()
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,187 +1,34 @@
|
||||
package hls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"m7s.live/v5/pkg"
|
||||
"m7s.live/v5/pkg/codec"
|
||||
"m7s.live/v5/pkg/util"
|
||||
mpegts "m7s.live/v5/plugin/hls/pkg/ts"
|
||||
)
|
||||
|
||||
type TsInFile struct {
|
||||
PMT util.Buffer
|
||||
TsInMemory
|
||||
file *os.File
|
||||
path string
|
||||
closed bool
|
||||
}
|
||||
|
||||
func NewTsInFile(path string) (*TsInFile, error) {
|
||||
// 确保目录存在
|
||||
func (ts *TsInFile) Open(path string) (err error) {
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, err
|
||||
if err = os.MkdirAll(dir, 0755); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Create(path)
|
||||
ts.file, err = os.Create(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
ts := &TsInFile{
|
||||
path: path,
|
||||
file: file,
|
||||
}
|
||||
return ts, nil
|
||||
ts.path = path
|
||||
return
|
||||
}
|
||||
|
||||
func (ts *TsInFile) Close() error {
|
||||
if ts.closed {
|
||||
if ts.closed || ts.file == nil {
|
||||
return nil
|
||||
}
|
||||
ts.closed = true
|
||||
return ts.file.Close()
|
||||
}
|
||||
|
||||
func (ts *TsInFile) WritePMTPacket(audio, video codec.FourCC) {
|
||||
ts.PMT.Reset()
|
||||
mpegts.WritePMTPacket(&ts.PMT, video, audio)
|
||||
// 写入PAT和PMT
|
||||
ts.file.Write(mpegts.DefaultPATPacket)
|
||||
ts.file.Write(ts.PMT)
|
||||
}
|
||||
|
||||
func (ts *TsInFile) WritePESPacket(frame *mpegts.MpegtsPESFrame, packet mpegts.MpegTsPESPacket) (err error) {
|
||||
if packet.Header.PacketStartCodePrefix != 0x000001 {
|
||||
err = errors.New("packetStartCodePrefix != 0x000001")
|
||||
return
|
||||
}
|
||||
|
||||
var pesHeadItem util.Buffer
|
||||
pesHeadItem.Reset()
|
||||
_, err = mpegts.WritePESHeader(&pesHeadItem, packet.Header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pesBuffers := append(net.Buffers{pesHeadItem}, packet.Buffers...)
|
||||
pesPktLength := int64(util.SizeOfBuffers(pesBuffers))
|
||||
|
||||
for i := 0; pesPktLength > 0; i++ {
|
||||
var tsBuffer util.Buffer
|
||||
tsBuffer.Reset()
|
||||
|
||||
tsHeader := mpegts.MpegTsHeader{
|
||||
SyncByte: 0x47,
|
||||
TransportErrorIndicator: 0,
|
||||
PayloadUnitStartIndicator: 0,
|
||||
TransportPriority: 0,
|
||||
Pid: frame.Pid,
|
||||
TransportScramblingControl: 0,
|
||||
AdaptionFieldControl: 1,
|
||||
ContinuityCounter: frame.ContinuityCounter,
|
||||
}
|
||||
|
||||
frame.ContinuityCounter++
|
||||
frame.ContinuityCounter = frame.ContinuityCounter % 16
|
||||
|
||||
if i == 0 {
|
||||
tsHeader.PayloadUnitStartIndicator = 1
|
||||
if frame.IsKeyFrame {
|
||||
tsHeader.AdaptionFieldControl = 0x03
|
||||
tsHeader.AdaptationFieldLength = 7
|
||||
tsHeader.PCRFlag = 1
|
||||
tsHeader.RandomAccessIndicator = 1
|
||||
tsHeader.ProgramClockReferenceBase = frame.ProgramClockReferenceBase
|
||||
}
|
||||
}
|
||||
|
||||
if pesPktLength < mpegts.TS_PACKET_SIZE-4 {
|
||||
var tsStuffingLength uint8
|
||||
tsHeader.AdaptionFieldControl = 0x03
|
||||
tsHeader.AdaptationFieldLength = uint8(mpegts.TS_PACKET_SIZE - 4 - 1 - pesPktLength)
|
||||
|
||||
if tsHeader.AdaptationFieldLength >= 1 {
|
||||
tsStuffingLength = tsHeader.AdaptationFieldLength - 1
|
||||
}
|
||||
|
||||
tsHeaderLength, err := mpegts.WriteTsHeader(&tsBuffer, tsHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tsStuffingLength > 0 {
|
||||
if _, err = tsBuffer.Write(mpegts.Stuffing[:tsStuffingLength]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tsPayloadLength := mpegts.TS_PACKET_SIZE - tsHeaderLength - int(tsStuffingLength)
|
||||
written, _ := io.CopyN(&tsBuffer, &pesBuffers, int64(tsPayloadLength))
|
||||
pesPktLength -= written
|
||||
|
||||
} else {
|
||||
tsHeaderLength, err := mpegts.WriteTsHeader(&tsBuffer, tsHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tsPayloadLength := mpegts.TS_PACKET_SIZE - tsHeaderLength
|
||||
written, _ := io.CopyN(&tsBuffer, &pesBuffers, int64(tsPayloadLength))
|
||||
pesPktLength -= written
|
||||
}
|
||||
|
||||
// 直接写入文件
|
||||
if _, err = ts.file.Write(tsBuffer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *TsInFile) WriteAudioFrame(absTime uint32, frame *pkg.ADTS, pes *mpegts.MpegtsPESFrame) (err error) {
|
||||
var packet mpegts.MpegTsPESPacket
|
||||
packet.Header.PesPacketLength = uint16(frame.Size + 8)
|
||||
packet.Buffers = slices.Clone(frame.Buffers)
|
||||
packet.Header.Pts = uint64(absTime) * 90
|
||||
packet.Header.PacketStartCodePrefix = 0x000001
|
||||
packet.Header.ConstTen = 0x80
|
||||
packet.Header.StreamID = mpegts.STREAM_ID_AUDIO
|
||||
pes.ProgramClockReferenceBase = packet.Header.Pts
|
||||
packet.Header.PtsDtsFlags = 0x80
|
||||
packet.Header.PesHeaderDataLength = 5
|
||||
return ts.WritePESPacket(pes, packet)
|
||||
}
|
||||
|
||||
func (ts *TsInFile) WriteVideoFrame(absTime uint32, frame *pkg.AnnexB, pes *mpegts.MpegtsPESFrame) (err error) {
|
||||
var buffer net.Buffers
|
||||
if frame.Hevc {
|
||||
buffer = append(buffer, codec.AudNalu)
|
||||
} else {
|
||||
buffer = append(buffer, codec.NALU_AUD_BYTE)
|
||||
}
|
||||
buffer = append(buffer, frame.Buffers...)
|
||||
pktLength := util.SizeOfBuffers(buffer) + 10 + 3
|
||||
if pktLength > 0xffff {
|
||||
pktLength = 0
|
||||
}
|
||||
|
||||
var packet mpegts.MpegTsPESPacket
|
||||
packet.Header.PacketStartCodePrefix = 0x000001
|
||||
packet.Header.ConstTen = 0x80
|
||||
packet.Header.StreamID = mpegts.STREAM_ID_VIDEO
|
||||
packet.Header.PesPacketLength = uint16(pktLength)
|
||||
packet.Header.Dts = uint64(absTime) * 90
|
||||
packet.Header.Pts = uint64(frame.PTS - frame.DTS) + packet.Header.Dts
|
||||
pes.ProgramClockReferenceBase = packet.Header.Pts
|
||||
packet.Header.PtsDtsFlags = 0xC0
|
||||
packet.Header.PesHeaderDataLength = 10
|
||||
packet.Buffers = buffer
|
||||
return ts.WritePESPacket(pes, packet)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
package hls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"slices"
|
||||
|
||||
"m7s.live/v5/pkg"
|
||||
"m7s.live/v5/pkg/codec"
|
||||
mpegts "m7s.live/v5/pkg/format/ts"
|
||||
"m7s.live/v5/pkg/util"
|
||||
mpegts "m7s.live/v5/plugin/hls/pkg/ts"
|
||||
)
|
||||
|
||||
type TsInMemory struct {
|
||||
@@ -26,151 +21,5 @@ func (ts *TsInMemory) WritePMTPacket(audio, video codec.FourCC) {
|
||||
func (ts *TsInMemory) WriteTo(w io.Writer) (int64, error) {
|
||||
w.Write(mpegts.DefaultPATPacket)
|
||||
w.Write(ts.PMT)
|
||||
cloneBuffers := slices.Clone(ts.Buffers)
|
||||
return cloneBuffers.WriteTo(w)
|
||||
}
|
||||
|
||||
func (ts *TsInMemory) WritePESPacket(frame *mpegts.MpegtsPESFrame, packet mpegts.MpegTsPESPacket) (err error) {
|
||||
if packet.Header.PacketStartCodePrefix != 0x000001 {
|
||||
err = errors.New("packetStartCodePrefix != 0x000001")
|
||||
return
|
||||
}
|
||||
var pesHeadItem util.Buffer = ts.GetAllocator().Borrow(32)
|
||||
pesHeadItem.Reset()
|
||||
_, err = mpegts.WritePESHeader(&pesHeadItem, packet.Header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pesBuffers := append(net.Buffers{pesHeadItem}, packet.Buffers...)
|
||||
pesPktLength := int64(util.SizeOfBuffers(pesBuffers))
|
||||
var tsHeaderLength int
|
||||
for i := 0; pesPktLength > 0; i++ {
|
||||
var buffer util.Buffer = ts.NextN(mpegts.TS_PACKET_SIZE)
|
||||
bwTsHeader := &buffer
|
||||
bwTsHeader.Reset()
|
||||
tsHeader := mpegts.MpegTsHeader{
|
||||
SyncByte: 0x47,
|
||||
TransportErrorIndicator: 0,
|
||||
PayloadUnitStartIndicator: 0,
|
||||
TransportPriority: 0,
|
||||
Pid: frame.Pid,
|
||||
TransportScramblingControl: 0,
|
||||
AdaptionFieldControl: 1,
|
||||
ContinuityCounter: frame.ContinuityCounter,
|
||||
}
|
||||
|
||||
frame.ContinuityCounter++
|
||||
frame.ContinuityCounter = frame.ContinuityCounter % 16
|
||||
|
||||
// 每一帧的开头,当含有pcr的时候,包含调整字段
|
||||
if i == 0 {
|
||||
tsHeader.PayloadUnitStartIndicator = 1
|
||||
|
||||
// 当PCRFlag为1的时候,包含调整字段
|
||||
if frame.IsKeyFrame {
|
||||
tsHeader.AdaptionFieldControl = 0x03
|
||||
tsHeader.AdaptationFieldLength = 7
|
||||
tsHeader.PCRFlag = 1
|
||||
tsHeader.RandomAccessIndicator = 1
|
||||
tsHeader.ProgramClockReferenceBase = frame.ProgramClockReferenceBase
|
||||
}
|
||||
}
|
||||
|
||||
// 每一帧的结尾,当不满足188个字节的时候,包含调整字段
|
||||
if pesPktLength < mpegts.TS_PACKET_SIZE-4 {
|
||||
var tsStuffingLength uint8
|
||||
|
||||
tsHeader.AdaptionFieldControl = 0x03
|
||||
tsHeader.AdaptationFieldLength = uint8(mpegts.TS_PACKET_SIZE - 4 - 1 - pesPktLength)
|
||||
|
||||
// TODO:如果第一个TS包也是最后一个TS包,是不是需要考虑这个情况?
|
||||
// MpegTsHeader最少占6个字节.(前4个走字节 + AdaptationFieldLength(1 byte) + 3个指示符5个标志位(1 byte))
|
||||
if tsHeader.AdaptationFieldLength >= 1 {
|
||||
tsStuffingLength = tsHeader.AdaptationFieldLength - 1
|
||||
} else {
|
||||
tsStuffingLength = 0
|
||||
}
|
||||
// error
|
||||
tsHeaderLength, err = mpegts.WriteTsHeader(bwTsHeader, tsHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if tsStuffingLength > 0 {
|
||||
if _, err = bwTsHeader.Write(mpegts.Stuffing[:tsStuffingLength]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
tsHeaderLength += int(tsStuffingLength)
|
||||
} else {
|
||||
|
||||
tsHeaderLength, err = mpegts.WriteTsHeader(bwTsHeader, tsHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tsPayloadLength := mpegts.TS_PACKET_SIZE - tsHeaderLength
|
||||
|
||||
//fmt.Println("tsPayloadLength :", tsPayloadLength)
|
||||
|
||||
// 这里不断的减少PES包
|
||||
written, _ := io.CopyN(bwTsHeader, &pesBuffers, int64(tsPayloadLength))
|
||||
// tmp := tsHeaderByte[3] << 2
|
||||
// tmp = tmp >> 6
|
||||
// if tmp == 2 {
|
||||
// fmt.Println("fuck you mother.")
|
||||
// }
|
||||
pesPktLength -= written
|
||||
tsPktByteLen := bwTsHeader.Len()
|
||||
|
||||
if tsPktByteLen != mpegts.TS_PACKET_SIZE {
|
||||
err = errors.New(fmt.Sprintf("%s, packet size=%d", "TS_PACKET_SIZE != 188,", tsPktByteLen))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *TsInMemory) WriteAudioFrame(frame *pkg.ADTS, pes *mpegts.MpegtsPESFrame) (err error) {
|
||||
// packetLength = 原始音频流长度 + adts(7) + MpegTsOptionalPESHeader长度(8 bytes, 因为只含有pts)
|
||||
var packet mpegts.MpegTsPESPacket
|
||||
packet.Header.PesPacketLength = uint16(frame.Size + 8)
|
||||
packet.Buffers = slices.Clone(frame.Buffers)
|
||||
packet.Header.Pts = uint64(frame.DTS)
|
||||
packet.Header.PacketStartCodePrefix = 0x000001
|
||||
packet.Header.ConstTen = 0x80
|
||||
packet.Header.StreamID = mpegts.STREAM_ID_AUDIO
|
||||
pes.ProgramClockReferenceBase = packet.Header.Pts
|
||||
packet.Header.PtsDtsFlags = 0x80
|
||||
packet.Header.PesHeaderDataLength = 5
|
||||
return ts.WritePESPacket(pes, packet)
|
||||
}
|
||||
|
||||
func (ts *TsInMemory) WriteVideoFrame(frame *pkg.AnnexB, pes *mpegts.MpegtsPESFrame) (err error) {
|
||||
var buffer net.Buffers
|
||||
//需要对原始数据(ES),进行一些预处理,视频需要分割nalu(H264编码),并且打上sps,pps,nalu_aud信息.
|
||||
if frame.Hevc {
|
||||
buffer = append(buffer, codec.AudNalu)
|
||||
} else {
|
||||
buffer = append(buffer, codec.NALU_AUD_BYTE)
|
||||
}
|
||||
buffer = append(buffer, frame.Buffers...)
|
||||
pktLength := util.SizeOfBuffers(buffer) + 10 + 3
|
||||
if pktLength > 0xffff {
|
||||
pktLength = 0
|
||||
}
|
||||
|
||||
var packet mpegts.MpegTsPESPacket
|
||||
packet.Header.PacketStartCodePrefix = 0x000001
|
||||
packet.Header.ConstTen = 0x80
|
||||
packet.Header.StreamID = mpegts.STREAM_ID_VIDEO
|
||||
packet.Header.PesPacketLength = uint16(pktLength)
|
||||
packet.Header.Pts = uint64(frame.PTS)
|
||||
pes.ProgramClockReferenceBase = packet.Header.Pts
|
||||
packet.Header.Dts = uint64(frame.DTS)
|
||||
packet.Header.PtsDtsFlags = 0xC0
|
||||
packet.Header.PesHeaderDataLength = 10
|
||||
packet.Buffers = buffer
|
||||
return ts.WritePESPacket(pes, packet)
|
||||
return ts.RecyclableMemory.WriteTo(w)
|
||||
}
|
||||
|
||||
@@ -1,577 +0,0 @@
|
||||
package mpegts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"m7s.live/v5/pkg/util"
|
||||
//"sync"
|
||||
)
|
||||
|
||||
// NALU AUD 00 00 00 01 09 F0
|
||||
|
||||
const (
|
||||
TS_PACKET_SIZE = 188
|
||||
TS_DVHS_PACKET_SIZE = 192
|
||||
TS_FEC_PACKET_SIZE = 204
|
||||
|
||||
TS_MAX_PACKET_SIZE = 204
|
||||
|
||||
PID_PAT = 0x0000
|
||||
PID_CAT = 0x0001
|
||||
PID_TSDT = 0x0002
|
||||
PID_RESERVED1 = 0x0003
|
||||
PID_RESERVED2 = 0x000F
|
||||
PID_NIT_ST = 0x0010
|
||||
PID_SDT_BAT_ST = 0x0011
|
||||
PID_EIT_ST = 0x0012
|
||||
PID_RST_ST = 0x0013
|
||||
PID_TDT_TOT_ST = 0x0014
|
||||
PID_NET_SYNC = 0x0015
|
||||
PID_RESERVED3 = 0x0016
|
||||
PID_RESERVED4 = 0x001B
|
||||
PID_SIGNALLING = 0x001C
|
||||
PID_MEASURE = 0x001D
|
||||
PID_DIT = 0x001E
|
||||
PID_SIT = 0x001F
|
||||
PID_PMT = 0x0100
|
||||
PID_VIDEO = 0x0101
|
||||
PID_AUDIO = 0x0102
|
||||
// 0x0003 - 0x000F Reserved
|
||||
// 0x0010 - 0x1FFE May be assigned as network_PID, Program_map_PID, elementary_PID, or for other purposes
|
||||
// 0x1FFF Null Packet
|
||||
|
||||
// program_association_section
|
||||
// conditional_access_section
|
||||
// TS_program_map_section
|
||||
// TS_description_section
|
||||
// ISO_IEC_14496_scene_description_section
|
||||
// ISO_IEC_14496_object_descriptor_section
|
||||
// Metadata_section
|
||||
// IPMP_Control_Information_section (defined in ISO/IEC 13818-11)
|
||||
TABLE_PAS = 0x00
|
||||
TABLE_CAS = 0x01
|
||||
TABLE_TSPMS = 0x02
|
||||
TABLE_TSDS = 0x03
|
||||
TABLE_ISO_IEC_14496_SDC = 0x04
|
||||
TABLE_ISO_IEC_14496_ODC = 0x05
|
||||
TABLE_MS = 0x06
|
||||
TABLE_IPMP_CIS = 0x07
|
||||
// 0x06 - 0x37 ITU-T Rec. H.222.0 | ISO/IEC 13818-1 reserved
|
||||
// 0x38 - 0x3F Defined in ISO/IEC 13818-6
|
||||
// 0x40 - 0xFE User private
|
||||
// 0xFF Forbidden
|
||||
STREAM_TYPE_VIDEO_MPEG1 = 0x01
|
||||
STREAM_TYPE_VIDEO_MPEG2 = 0x02
|
||||
STREAM_TYPE_AUDIO_MPEG1 = 0x03
|
||||
STREAM_TYPE_AUDIO_MPEG2 = 0x04
|
||||
STREAM_TYPE_PRIVATE_SECTIONS = 0x05
|
||||
STREAM_TYPE_PRIVATE_DATA = 0x06
|
||||
STREAM_TYPE_MHEG = 0x07
|
||||
|
||||
STREAM_TYPE_H264 = 0x1B
|
||||
STREAM_TYPE_H265 = 0x24
|
||||
STREAM_TYPE_AAC = 0x0F
|
||||
STREAM_TYPE_G711A = 0x90
|
||||
STREAM_TYPE_G711U = 0x91
|
||||
STREAM_TYPE_G722_1 = 0x92
|
||||
STREAM_TYPE_G723_1 = 0x93
|
||||
STREAM_TYPE_G726 = 0x94
|
||||
STREAM_TYPE_G729 = 0x99
|
||||
|
||||
STREAM_TYPE_ADPCM = 0x11
|
||||
STREAM_TYPE_PCM = 0x0A
|
||||
STREAM_TYPE_AC3 = 0x81
|
||||
STREAM_TYPE_DTS = 0x8A
|
||||
STREAM_TYPE_LPCM = 0x8B
|
||||
// 1110 xxxx
|
||||
// 110x xxxx
|
||||
STREAM_ID_VIDEO = 0xE0 // ITU-T Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 11172-2 or ISO/IEC14496-2 video stream number xxxx
|
||||
STREAM_ID_AUDIO = 0xC0 // ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 or ISO/IEC14496-3 audio stream number x xxxx
|
||||
|
||||
PAT_PKT_TYPE = 0
|
||||
PMT_PKT_TYPE = 1
|
||||
PES_PKT_TYPE = 2
|
||||
)
|
||||
|
||||
//
|
||||
// MPEGTS -> PAT + PMT + PES
|
||||
// ES -> PES -> TS
|
||||
//
|
||||
|
||||
type MpegTsStream struct {
|
||||
PAT MpegTsPAT // PAT表信息
|
||||
PMT MpegTsPMT // PMT表信息
|
||||
PESBuffer map[uint16]*MpegTsPESPacket
|
||||
PESChan chan *MpegTsPESPacket
|
||||
}
|
||||
|
||||
// ios13818-1-CN.pdf 33/165
|
||||
//
|
||||
// TS
|
||||
//
|
||||
|
||||
// Packet == Header + Payload == 188 bytes
|
||||
type MpegTsPacket struct {
|
||||
Header MpegTsHeader
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
// 前面32bit的数据即TS分组首部,它指出了这个分组的属性
|
||||
type MpegTsHeader struct {
|
||||
SyncByte byte // 8 bits 同步字节,固定为0x47,表示后面是一个TS分组
|
||||
TransportErrorIndicator byte // 1 bit 传输错误标志位
|
||||
PayloadUnitStartIndicator byte // 1 bit 负载单元开始标志(packet不满188字节时需填充).为1时,表示在4个字节后,有一个调整字节
|
||||
TransportPriority byte // 1 bit 传输优先级
|
||||
Pid uint16 // 13 bits Packet ID号码,唯一的号码对应不同的包.为0表示携带的是PAT表
|
||||
TransportScramblingControl byte // 2 bits 加密标志位(00:未加密;其他表示已加密)
|
||||
AdaptionFieldControl byte // 2 bits 附加区域控制.表示TS分组首部后面是否跟随有调整字段和有效负载.01仅含有效负载(没有adaptation_field),10仅含调整字段(没有Payload),11含有调整字段和有效负载(有adaptation_field,adaptation_field之后是Payload).为00的话解码器不进行处理.空分组没有调整字段
|
||||
ContinuityCounter byte // 4 bits 包递增计数器.范围0-15,具有相同的PID的TS分组传输时每次加1,到15后清0.不过,有些情况下是不计数的.
|
||||
|
||||
MpegTsHeaderAdaptationField
|
||||
}
|
||||
|
||||
// 调整字段,只可能出现在每一帧的开头(当含有pcr的时候),或者结尾(当不满足188个字节的时候)
|
||||
// adaptionFieldControl 00 -> 高字节代表调整字段, 低字节代表负载字段 0x20 0x10
|
||||
// PCR字段编码在MPEG-2 TS包的自适应字段(Adaptation field)的6个Byte中,其中6 bits为预留位,42 bits为有效位()
|
||||
// MpegTsHeaderAdaptationField + stuffing bytes
|
||||
type MpegTsHeaderAdaptationField struct {
|
||||
AdaptationFieldLength byte // 8bits 本区域除了本字节剩下的长度(不包含本字节!!!切记), if adaptationFieldLength > 0, 那么就有下面8个字段. adaptation_field_length 值必须在 0 到 182 的区间内.当 adaptation_field_control 值为'10'时,adaptation_field_length 值必须为 183
|
||||
DiscontinuityIndicator byte // 1bit 置于"1"时,指示当前传输流包的不连续性状态为真.当 discontinuity_indicator 设置为"0"或不存在时,不连续性状态为假.不连续性指示符用于指示两种类型的不连续性,系统时间基不连续性和 continuity_counter 不连续性.
|
||||
RandomAccessIndicator byte // 1bit 指示当前的传输流包以及可能的具有相同 PID 的后续传输流包,在此点包含有助于随机接入的某些信息.特别的,该比特置于"1"时,在具有当前 PID 的传输流包的有效载荷中起始的下一个 PES 包必须包含一个 discontinuity_indicator 字段中规定的基本流接入点.此外,在视频情况中,显示时间标记必须在跟随基本流接入点的第一图像中存在
|
||||
ElementaryStreamPriorityIndicator byte // 1bit 在具有相同 PID 的包之间,它指示此传输流包有效载荷内承载的基本流数据的优先级.1->指示该有效载荷具有比其他传输流包有效载荷更高的优先级
|
||||
PCRFlag byte // 1bit 1->指示 adaptation_field 包含以两部分编码的 PCR 字段.0->指示自适应字段不包含任何 PCR 字段
|
||||
OPCRFlag byte // 1bit 1->指示 adaptation_field 包含以两部分编码的 OPCR字段.0->指示自适应字段不包含任何 OPCR 字段
|
||||
SplicingPointFlag byte // 1bit 1->指示 splice_countdown 字段必须在相关自适应字段中存在,指定拼接点的出现.0->指示自适应字段中 splice_countdown 字段不存在
|
||||
TrasportPrivateDataFlag byte // 1bit 1->指示自适应字段包含一个或多个 private_data 字节.0->指示自适应字段不包含任何 private_data 字节
|
||||
AdaptationFieldExtensionFlag byte // 1bit 1->指示自适应字段扩展的存在.0->指示自适应字段中自适应字段扩展不存在
|
||||
|
||||
// Optional Fields
|
||||
ProgramClockReferenceBase uint64 // 33 bits pcr
|
||||
Reserved1 byte // 6 bits
|
||||
ProgramClockReferenceExtension uint16 // 9 bits
|
||||
OriginalProgramClockReferenceBase uint64 // 33 bits opcr
|
||||
Reserved2 byte // 6 bits
|
||||
OriginalProgramClockReferenceExtension uint16 // 9 bits
|
||||
SpliceCountdown byte // 8 bits
|
||||
TransportPrivateDataLength byte // 8 bits 指定紧随传输private_data_length 字段的 private_data 字节数. private_data 字节数不能使专用数据扩展超出自适应字段的范围
|
||||
PrivateDataByte byte // 8 bits 不通过 ITU-T|ISO/IEC 指定
|
||||
AdaptationFieldExtensionLength byte // 8 bits 指定紧随此字段的扩展的自适应字段数据的字节数,包括要保留的字节(如果存在)
|
||||
LtwFlag byte // 1 bit 1->指示 ltw_offset 字段存在
|
||||
PiecewiseRateFlag byte // 1 bit 1->指示 piecewise_rate 字段存在
|
||||
SeamlessSpliceFlag byte // 1 bit 1->指示 splice_type 以及 DTS_next_AU 字段存在. 0->指示无论是 splice_type 字段还是 DTS_next_AU 字段均不存在
|
||||
|
||||
// Optional Fields
|
||||
LtwValidFlag byte // 1 bit 1->指示 ltw_offset 的值必将生效.0->指示 ltw_offset 字段中该值未定义
|
||||
LtwOffset uint16 // 15 bits 其值仅当 ltw_valid 标志字段具有'1'值时才定义.定义时,法定时间窗补偿以(300/fs)秒为度量单位,其中 fs 为此 PID 所归属的节目的系统时钟频率
|
||||
Reserved3 byte // 2 bits 保留
|
||||
PiecewiseRate uint32 // 22 bits 只要当 ltw_flag 和 ltw_valid_flag 均置于‘1’时,此 22 比特字段的含义才确定
|
||||
SpliceType byte // 4 bits
|
||||
DtsNextAU uint64 // 33 bits (解码时间标记下一个存取单元)
|
||||
|
||||
// stuffing bytes
|
||||
// 此为固定的 8 比特值等于'1111 1111',能够通过编码器插入.它亦能被解码器丢弃
|
||||
}
|
||||
|
||||
// ios13818-1-CN.pdf 77
|
||||
//
|
||||
// Descriptor
|
||||
//
|
||||
|
||||
type MpegTsDescriptor struct {
|
||||
Tag byte // 8 bits 标识每一个描述符
|
||||
Length byte // 8 bits 指定紧随 descriptor_length 字段的描述符的字节数
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func ReadTsPacket(r io.Reader) (packet MpegTsPacket, err error) {
|
||||
lr := &io.LimitedReader{R: r, N: TS_PACKET_SIZE}
|
||||
|
||||
// header
|
||||
packet.Header, err = ReadTsHeader(lr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// payload
|
||||
packet.Payload = make([]byte, lr.N)
|
||||
_, err = lr.Read(packet.Payload)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ReadTsHeader(r io.Reader) (header MpegTsHeader, err error) {
|
||||
var h uint32
|
||||
|
||||
// MPEGTS Header 4个字节
|
||||
h, err = util.ReadByteToUint32(r, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// payloadUnitStartIndicator
|
||||
// 为1时,表示在4个字节后,有一个调整字节.包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1")
|
||||
// header.payloadUnitStartIndicator = uint8(h & 0x400000)
|
||||
|
||||
// | 1111 1111 | 0000 0000 | 0000 0000 | 0000 0000 |
|
||||
|
||||
// | 1111 1111 | 0000 0000 | 0000 0000 | 0000 0000 |
|
||||
header.SyncByte = byte((h & 0xff000000) >> 24)
|
||||
|
||||
if header.SyncByte != 0x47 {
|
||||
err = errors.New("mpegts header sync error!")
|
||||
return
|
||||
}
|
||||
|
||||
// | 0000 0000 | 1000 0000 | 0000 0000 | 0000 0000 |
|
||||
header.TransportErrorIndicator = byte((h & 0x800000) >> 23)
|
||||
|
||||
// | 0000 0000 | 0100 0000 | 0000 0000 | 0000 0000 |
|
||||
header.PayloadUnitStartIndicator = byte((h & 0x400000) >> 22)
|
||||
|
||||
// | 0000 0000 | 0010 0000 | 0000 0000 | 0000 0000 |
|
||||
header.TransportPriority = byte((h & 0x200000) >> 21)
|
||||
|
||||
// | 0000 0000 | 0001 1111 | 1111 1111 | 0000 0000 |
|
||||
header.Pid = uint16((h & 0x1fff00) >> 8)
|
||||
|
||||
// | 0000 0000 | 0000 0000 | 0000 0000 | 1100 0000 |
|
||||
header.TransportScramblingControl = byte((h & 0xc0) >> 6)
|
||||
|
||||
// | 0000 0000 | 0000 0000 | 0000 0000 | 0011 0000 |
|
||||
// 0x30 , 0x20 -> adaptation_field, 0x10 -> Payload
|
||||
header.AdaptionFieldControl = byte((h & 0x30) >> 4)
|
||||
|
||||
// | 0000 0000 | 0000 0000 | 0000 0000 | 0000 1111 |
|
||||
header.ContinuityCounter = byte(h & 0xf)
|
||||
|
||||
// | 0010 0000 |
|
||||
// adaptionFieldControl
|
||||
// 表示TS分组首部后面是否跟随有调整字段和有效负载.
|
||||
// 01仅含有效负载(没有adaptation_field)
|
||||
// 10仅含调整字段(没有Payload)
|
||||
// 11含有调整字段和有效负载(有adaptation_field,adaptation_field之后是Payload).
|
||||
// 为00的话解码器不进行处理.空分组没有调整字段
|
||||
// 当值为'11时,adaptation_field_length 值必须在0 到182 的区间内.
|
||||
// 当值为'10'时,adaptation_field_length 值必须为183.
|
||||
// 对于承载PES 包的传输流包,只要存在欠充足的PES 包数据就需要通过填充来完全填满传输流包的有效载荷字节.
|
||||
// 填充通过规定自适应字段长度比自适应字段中数据元的长度总和还要长来实现,以致于自适应字段在完全容纳有效的PES 包数据后,有效载荷字节仍有剩余.自适应字段中额外空间采用填充字节填满.
|
||||
if header.AdaptionFieldControl >= 2 {
|
||||
// adaptationFieldLength
|
||||
header.AdaptationFieldLength, err = util.ReadByteToUint8(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if header.AdaptationFieldLength > 0 {
|
||||
lr := &io.LimitedReader{R: r, N: int64(header.AdaptationFieldLength)}
|
||||
|
||||
// discontinuityIndicator(1)
|
||||
// randomAccessIndicator(1)
|
||||
// elementaryStreamPriorityIndicator
|
||||
// PCRFlag
|
||||
// OPCRFlag
|
||||
// splicingPointFlag
|
||||
// trasportPrivateDataFlag
|
||||
// adaptationFieldExtensionFlag
|
||||
var flags uint8
|
||||
flags, err = util.ReadByteToUint8(lr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header.DiscontinuityIndicator = flags & 0x80
|
||||
header.RandomAccessIndicator = flags & 0x40
|
||||
header.ElementaryStreamPriorityIndicator = flags & 0x20
|
||||
header.PCRFlag = flags & 0x10
|
||||
header.OPCRFlag = flags & 0x08
|
||||
header.SplicingPointFlag = flags & 0x04
|
||||
header.TrasportPrivateDataFlag = flags & 0x02
|
||||
header.AdaptationFieldExtensionFlag = flags & 0x01
|
||||
|
||||
// randomAccessIndicator
|
||||
// 在此点包含有助于随机接入的某些信息.
|
||||
// 特别的,该比特置于"1"时,在具有当前 PID 的传输流包的有效载荷中起始的下一个 PES 包必须包含一个 discontinuity_indicator 字段中规定的基本流接入点.
|
||||
// 此外,在视频情况中,显示时间标记必须在跟随基本流接入点的第一图像中存在
|
||||
if header.RandomAccessIndicator != 0 {
|
||||
}
|
||||
|
||||
// PCRFlag
|
||||
// 1->指示 adaptation_field 包含以两部分编码的 PCR 字段.
|
||||
// 0->指示自适应字段不包含任何 PCR 字段
|
||||
if header.PCRFlag != 0 {
|
||||
var pcr uint64
|
||||
pcr, err = util.ReadByteToUint48(lr, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// PCR(i) = PCR_base(i)*300 + PCR_ext(i)
|
||||
// afd.programClockReferenceBase * 300 + afd.programClockReferenceExtension
|
||||
header.ProgramClockReferenceBase = pcr >> 15 // 9 bits + 6 bits
|
||||
header.ProgramClockReferenceExtension = uint16(pcr & 0x1ff) // 9 bits -> | 0000 0001 | 1111 1111 |
|
||||
}
|
||||
|
||||
// OPCRFlag
|
||||
if header.OPCRFlag != 0 {
|
||||
var opcr uint64
|
||||
opcr, err = util.ReadByteToUint48(lr, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// OPCR(i) = OPCR_base(i)*300 + OPCR_ext(i)
|
||||
// afd.originalProgramClockReferenceBase * 300 + afd.originalProgramClockReferenceExtension
|
||||
header.OriginalProgramClockReferenceBase = opcr >> 15 // 9 bits + 6 bits
|
||||
header.OriginalProgramClockReferenceExtension = uint16(opcr & 0x1ff) // 9 bits -> | 0000 0001 | 1111 1111 |
|
||||
}
|
||||
|
||||
// splicingPointFlag
|
||||
// 1->指示 splice_countdown 字段必须在相关自适应字段中存在,指定拼接点的出现.
|
||||
// 0->指示自适应字段中 splice_countdown 字段不存在
|
||||
if header.SplicingPointFlag != 0 {
|
||||
header.SpliceCountdown, err = util.ReadByteToUint8(lr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// trasportPrivateDataFlag
|
||||
// 1->指示自适应字段包含一个或多个 private_data 字节.
|
||||
// 0->指示自适应字段不包含任何 private_data 字节
|
||||
if header.TrasportPrivateDataFlag != 0 {
|
||||
header.TransportPrivateDataLength, err = util.ReadByteToUint8(lr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// privateDataByte
|
||||
b := make([]byte, header.TransportPrivateDataLength)
|
||||
if _, err = lr.Read(b); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// adaptationFieldExtensionFlag
|
||||
if header.AdaptationFieldExtensionFlag != 0 {
|
||||
}
|
||||
|
||||
// 消耗掉剩下的数据,我们不关心
|
||||
if lr.N > 0 {
|
||||
// Discard 是一个 io.Writer,对它进行的任何 Write 调用都将无条件成功
|
||||
// 但是ioutil.Discard不记录copy得到的数值
|
||||
// 用于发送需要读取但不想存储的数据,目的是耗尽读取端的数据
|
||||
if _, err = io.CopyN(ioutil.Discard, lr, int64(lr.N)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func WriteTsHeader(w io.Writer, header MpegTsHeader) (written int, err error) {
|
||||
if header.SyncByte != 0x47 {
|
||||
err = errors.New("mpegts header sync error!")
|
||||
return
|
||||
}
|
||||
|
||||
h := uint32(header.SyncByte)<<24 + uint32(header.TransportErrorIndicator)<<23 + uint32(header.PayloadUnitStartIndicator)<<22 + uint32(header.TransportPriority)<<21 + uint32(header.Pid)<<8 + uint32(header.TransportScramblingControl)<<6 + uint32(header.AdaptionFieldControl)<<4 + uint32(header.ContinuityCounter)
|
||||
if err = util.WriteUint32ToByte(w, h, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written += 4
|
||||
|
||||
if header.AdaptionFieldControl >= 2 {
|
||||
// adaptationFieldLength(8)
|
||||
if err = util.WriteUint8ToByte(w, header.AdaptationFieldLength); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written += 1
|
||||
|
||||
if header.AdaptationFieldLength > 0 {
|
||||
|
||||
// discontinuityIndicator(1)
|
||||
// randomAccessIndicator(1)
|
||||
// elementaryStreamPriorityIndicator(1)
|
||||
// PCRFlag(1)
|
||||
// OPCRFlag(1)
|
||||
// splicingPointFlag(1)
|
||||
// trasportPrivateDataFlag(1)
|
||||
// adaptationFieldExtensionFlag(1)
|
||||
threeIndicatorFiveFlags := uint8(header.DiscontinuityIndicator<<7) + uint8(header.RandomAccessIndicator<<6) + uint8(header.ElementaryStreamPriorityIndicator<<5) + uint8(header.PCRFlag<<4) + uint8(header.OPCRFlag<<3) + uint8(header.SplicingPointFlag<<2) + uint8(header.TrasportPrivateDataFlag<<1) + uint8(header.AdaptationFieldExtensionFlag)
|
||||
if err = util.WriteUint8ToByte(w, threeIndicatorFiveFlags); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written += 1
|
||||
|
||||
// PCR(i) = PCR_base(i)*300 + PCR_ext(i)
|
||||
if header.PCRFlag != 0 {
|
||||
pcr := header.ProgramClockReferenceBase<<15 | 0x3f<<9 | uint64(header.ProgramClockReferenceExtension)
|
||||
if err = util.WriteUint48ToByte(w, pcr, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written += 6
|
||||
}
|
||||
|
||||
// OPCRFlag
|
||||
if header.OPCRFlag != 0 {
|
||||
opcr := header.OriginalProgramClockReferenceBase<<15 | 0x3f<<9 | uint64(header.OriginalProgramClockReferenceExtension)
|
||||
if err = util.WriteUint48ToByte(w, opcr, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written += 6
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
//func (s *MpegTsStream) TestWrite(fileName string) error {
|
||||
//
|
||||
// if fileName != "" {
|
||||
// file, err := os.Create(fileName)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer file.Close()
|
||||
//
|
||||
// patTsHeader := []byte{0x47, 0x40, 0x00, 0x10}
|
||||
//
|
||||
// if err := WritePATPacket(file, patTsHeader, *s.pat); err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// // TODO:这里的pid应该是由PAT给的
|
||||
// pmtTsHeader := []byte{0x47, 0x41, 0x00, 0x10}
|
||||
//
|
||||
// if err := WritePMTPacket(file, pmtTsHeader, *s.pmt); err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var videoFrame int
|
||||
// var audioFrame int
|
||||
// for {
|
||||
// tsPesPkt, ok := <-s.TsPesPktChan
|
||||
// if !ok {
|
||||
// fmt.Println("frame index, video , audio :", videoFrame, audioFrame)
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// if tsPesPkt.PesPkt.Header.StreamID == STREAM_ID_AUDIO {
|
||||
// audioFrame++
|
||||
// }
|
||||
//
|
||||
// if tsPesPkt.PesPkt.Header.StreamID == STREAM_ID_VIDEO {
|
||||
// println(tsPesPkt.PesPkt.Header.Pts)
|
||||
// videoFrame++
|
||||
// }
|
||||
//
|
||||
// fmt.Sprintf("%s", tsPesPkt)
|
||||
//
|
||||
// // if err := WritePESPacket(file, tsPesPkt.TsPkt.Header, tsPesPkt.PesPkt); err != nil {
|
||||
// // return err
|
||||
// // }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
|
||||
func (s *MpegTsStream) ReadPAT(packet *MpegTsPacket, pr io.Reader) (err error) {
|
||||
// 首先找到PID==0x00的TS包(PAT)
|
||||
if PID_PAT == packet.Header.Pid {
|
||||
if len(packet.Payload) == 188 {
|
||||
pr = &util.Crc32Reader{R: pr, Crc32: 0xffffffff}
|
||||
}
|
||||
// Header + PSI + Paylod
|
||||
s.PAT, err = ReadPAT(pr)
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MpegTsStream) ReadPMT(packet *MpegTsPacket, pr io.Reader) (err error) {
|
||||
// 在读取PAT中已经将所有频道节目信息(PMT_PID)保存了起来
|
||||
// 接着读取所有TS包里面的PID,找出PID==PMT_PID的TS包,就是PMT表
|
||||
for _, v := range s.PAT.Program {
|
||||
if v.ProgramMapPID == packet.Header.Pid {
|
||||
if len(packet.Payload) == 188 {
|
||||
pr = &util.Crc32Reader{R: pr, Crc32: 0xffffffff}
|
||||
}
|
||||
// Header + PSI + Paylod
|
||||
s.PMT, err = ReadPMT(pr)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MpegTsStream) Feed(ts io.Reader) (err error) {
|
||||
var reader bytes.Reader
|
||||
var lr io.LimitedReader
|
||||
lr.R = &reader
|
||||
var tsHeader MpegTsHeader
|
||||
tsData := make([]byte, TS_PACKET_SIZE)
|
||||
for {
|
||||
_, err = io.ReadFull(ts, tsData)
|
||||
if err == io.EOF {
|
||||
// 文件结尾 把最后面的数据发出去
|
||||
for _, pesPkt := range s.PESBuffer {
|
||||
if pesPkt != nil {
|
||||
s.PESChan <- pesPkt
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return
|
||||
}
|
||||
reader.Reset(tsData)
|
||||
lr.N = TS_PACKET_SIZE
|
||||
if tsHeader, err = ReadTsHeader(&lr); err != nil {
|
||||
return
|
||||
}
|
||||
if tsHeader.Pid == PID_PAT {
|
||||
if s.PAT, err = ReadPAT(&lr); err != nil {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(s.PMT.Stream) == 0 {
|
||||
for _, v := range s.PAT.Program {
|
||||
if v.ProgramMapPID == tsHeader.Pid {
|
||||
if s.PMT, err = ReadPMT(&lr); err != nil {
|
||||
return
|
||||
}
|
||||
for _, v := range s.PMT.Stream {
|
||||
s.PESBuffer[v.ElementaryPID] = nil
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
} else if pesPkt, ok := s.PESBuffer[tsHeader.Pid]; ok {
|
||||
if tsHeader.PayloadUnitStartIndicator == 1 {
|
||||
if pesPkt != nil {
|
||||
s.PESChan <- pesPkt
|
||||
}
|
||||
pesPkt = &MpegTsPESPacket{}
|
||||
s.PESBuffer[tsHeader.Pid] = pesPkt
|
||||
if pesPkt.Header, err = ReadPESHeader(&lr); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
io.Copy(&pesPkt.Payload, &lr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,520 +0,0 @@
|
||||
#MPEGTS
|
||||
|
||||
----------
|
||||
|
||||
Name:苏荣
|
||||
Data:2016/5/27 09:03:30
|
||||
|
||||
|
||||
----------
|
||||
|
||||
## PSI(Program Specific Information) 节目特定信息
|
||||
PSI 可以认为属于 6 个表:
|
||||
1) 节目相关表(PAT)
|
||||
2) TS 节目映射表(PMT)
|
||||
3) 网络信息表(NIT)
|
||||
4) 有条件访问表(CAT)
|
||||
5) 传输流描述表
|
||||
6) IPMP 控制信息表
|
||||
|
||||
##ES流(Elementary Stream):基本码流,不分段的音频、视频或其他信息的连续码流.
|
||||
|
||||
##PES流:把基本流ES分割成段,并加上相应头文件打包成形的打包基本码流
|
||||
|
||||
##PS流(Program Stream):节目流,将具有共同时间基准的一个或多个PES组合(复合)而成的单一数据流(用于播放或编辑系统,如m2p).
|
||||
|
||||
##TS流(Transport Stream):传输流,将具有共同时间基准或独立时间基准的一个或多个PES组合(复合)而成的单一数据流(用于数据传输).
|
||||
|
||||
##PES ES TS
|
||||
视频压缩成H264码流,可以称之为ES流,将其每帧打包为PES流,然后分拆为多个188字节,称为TS流.
|
||||
|
||||
H264(ES) = PES1(一帧ES打包) + PES2(一帧ES打包) + PES3(一帧ES打包) + ...
|
||||
|
||||
PES1 = PES1 Header + PES1 Payload = PES1 Packet Start Code Prefix + Stream ID + PES1 Packet Length + Send PES1 Header(不确定大小) + PES1 Payload
|
||||
|
||||
PES1 Payload = TS1 Payload + TS2 Payload + TS3 Payload + ...
|
||||
|
||||
PES1 = TS1 + TS2 + TS3 + ....
|
||||
|
||||
PES1 = TS1(TS1 Header + PES1 Header + TS1 Payload) + TS2(有三种可能) + TS3(有三种可能) + ......
|
||||
|
||||
TS1(TS流第一个包) = TS1 Header + PES1 Header + TS1 Payload
|
||||
|
||||
TS2(TS流第二个包,第一种情况) = TS2 Header + 自适应字段 + TS2 Payload (出现概率 1%)
|
||||
|
||||
TS2(TS流第二个包,第二种情况) = TS2 Header + 自适应字段 (出现概率 0.1%)
|
||||
|
||||
TS2(TS流第二个包,第三种情况) = TS2 Header + TS2 Payload (出现概率 98.9%)
|
||||
|
||||
一段ES流 = N个PES(N帧)
|
||||
|
||||
同一个PES的TS的PID是相同的
|
||||
|
||||
##寻找第一个TS包
|
||||
Header PID = 0x000 说明数据包是PAT表信息
|
||||
第一个TS包 一般叫做 PAT (Program Association Table,节目相关表)
|
||||
|
||||
TS流 : PID=005 + PID=002 + PID=000
|
||||
|
||||
一般来说第一个TS包一般在第一个位置,本例举出一个特殊情况(第一个TS包在第三)
|
||||
|
||||
在寻找第一个TS包时,不断读取TS包,直到找到pid=000的位置,并将读取过的TS包置入缓冲区
|
||||
|
||||
##寻找下一个TS包
|
||||
第二个TS包 一般叫做PMT(Program Map Table,节目映射表)
|
||||
|
||||
##解析TS包
|
||||
payload_unit_start_indicator : 该字段用来表示TS包的有效净荷有PES包或者PSI数据的情况.
|
||||
|
||||
当TS包带有PES包数据时(出现概率99.9%).不带PES包(出现概率0.1%).
|
||||
|
||||
1. 当TS包带有PES包数据时,payload_unit_start_indicator具有以下的特点:
|
||||
a. 置为1,标识TS包的有效净荷以PES包的第一个字节开始.
|
||||
b. 置为0,表示TS包的开始不是PES包.
|
||||
|
||||
2. 当TS包带有PSI数据时,payload_unit_start_indicator具有以下特点:
|
||||
a. 置为1,表示TS包带有PSI部分的第一个字节,即第一个字节带有指针pointer_field.
|
||||
b. 置为0,表示TS包不带有一个PSI部分的第一个字节,即在有效净荷中没有指针point_field.
|
||||
c. 对于空包的包,payload_unit_start_indicator应该置为0
|
||||
|
||||
adaptionFieldControl:
|
||||
01 -> 仅含有效负载(TS包第三种情况)
|
||||
10 -> 仅含调整字段(TS包第二种情况)
|
||||
11 -> 含有调整字段和有效负载(TS包第一种情况)
|
||||
|
||||
TS流,通过一个个的TS包来传送. TS包可以是传送PSI SI等各表的数据包,也可以是传送节目音视频数据(携带的PES包:音视频基本流包)的包;TS携带 PSI SI等表的数据时,各个表以各表对应的Section语法格式做为传输单元存放到TS包中 以便传输;
|
||||
TS包,有一个TS包的PID,系统就是根据这个PID来找对应的TS包;对于包含音视频数据(PES包)的TS包,系统通过TS的PID找到对应TS数据包,提取其中的数据组合成节目的音视频;对于携带PSI SI等数据的TS包,系统通过TS的PID找到对应TS数据包,提取各个PSI SI数据表格,用来指导系统;因此其中部分PID用来固定传输某些数据内容.
|
||||
|
||||
有了TS的PID后, 如果TS包携带的是PSI SI等表格的Section数据时,有时还不能确定该PID的TS包中携带的数据是什么,SDT BAT ST 等表传送时,都用的是PID为0X0011的TS数据包,对于这种携带PSI SI Section单元的TS包,对应的数据(表的Section语法中)还有一个 TABLE_ID字段,用来可以确定是具体的什么表
|
||||
|
||||
因此PID+TableID就可以确定负载带了什么,是PES还是PSI.
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
1. 第一个包:
|
||||
|
||||
包头 : 47 60 00 10
|
||||
0x47 : syncByte
|
||||
0x6 : 0110(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1.
|
||||
0x000 : 0 0000 0000 0000, pid = 0,说明是第一个TS包(PAT表)
|
||||
0x10 : 0001 0000, adaptionFieldControl = 01,说明仅含有效负载(TS包第三种情况)
|
||||
|
||||
负载 : 00 00 B0 0D 00 00 C1 00 00 00 01 E0
|
||||
81 0C 8C BE 32 FF FF......FF
|
||||
|
||||
指针 : 00
|
||||
table id : 00
|
||||
固定值 : B (1011)
|
||||
section_length : 0 0D(值:13)
|
||||
transport_stream_id : 00 00
|
||||
version number & current_next_indicator : C1
|
||||
section_number : 00
|
||||
last_section_number : 00
|
||||
program_number : 00 01
|
||||
program_map_PID : E0 81(因为program_number > 0)
|
||||
CRC_32 : 0C 8C BE 32
|
||||
|
||||
if (program_number == 0)
|
||||
{
|
||||
network_PID
|
||||
}else
|
||||
{
|
||||
program_map_PID
|
||||
}
|
||||
|
||||
E0 81 = reserved3 + program_map_PID = | 1110 0000 | 1000 0001 |
|
||||
program_map_PID = 0x81(说明PMT的pid为081)
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
2. 第二个包
|
||||
|
||||
包头 : 47 60 81 10
|
||||
0x47 : syncByte
|
||||
0x6 : 0110(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1.
|
||||
0x081 : 0 0000 1000 0001, pid = 0x081(说明是PMT表,因为前面的PAT表给出了)
|
||||
0x10 : 0001 0000, adaptionFieldControl = 01,说明仅含有效负载(TS包第三种情况)
|
||||
|
||||
负载 : 00 02 B0 17 00 01 C1 00 00 E8 10 F0 00 1B E8 10
|
||||
F0 00 03 E8 14 F0 00 66 74 A4 2D FF FF FF FF FF......FF
|
||||
|
||||
指针 : 00
|
||||
table id : 02
|
||||
固定值 : B
|
||||
section_length : 0 17(值:23,表示到后面FF FF FF FF FF FF之前总共有23个字节)
|
||||
program_number : 00 01
|
||||
reserved2 & version_number & current_next_indicator : C1
|
||||
section_number : 00
|
||||
last_section_number : 00
|
||||
PCR_PID : E8 10
|
||||
program_info_length : F0 00 前4位为保留位 后12位为描述信息长度 此处为0
|
||||
|
||||
第一流分析 : 1B E8 10 F0 00
|
||||
stream_type : 1B 视频流(H264)(ITU-T H.264建议书| SO/IEC 14496-10 视频中定义的 AVC 视频流)
|
||||
elementary_PID : E8 10 前3位为保留位取后13位 则PID=810 表示此PID的都是视频流
|
||||
ES_info_length : F0 00 前4位为保留位 后12位为描述信息长度 此处为0
|
||||
|
||||
第二流分析 : 03 E8 14 F0 00
|
||||
stream_type : 03 音频流(MP3)
|
||||
elementary_PID : E8 14 前3位为保留位取后13位 则PID=814 表示此PID的都是音频流
|
||||
ES_info_length : F0 00 前4位为保留位 后12位为描述信息长度 此处为0
|
||||
|
||||
|
||||
|
||||
CRC : 66 74 A4 2D
|
||||
|
||||
|
||||
reserved4 + program_info_length = | 1111 0000 | 0000 0000 |
|
||||
program_info_length = 0
|
||||
|
||||
stream_type : 03 表示流是音频流 MP3 格式 814 表示 pid=814 的TS包存储的是MP3格式的音频流.
|
||||
stream_type : 01 表示流是视频流 h264格式 810 表示 pid=810 的TS包存储的是h264格式的视频流
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
3. 第三个包
|
||||
包头 : 47 48 14 10
|
||||
0x47 : syncByte
|
||||
0x4 : 0100(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1.
|
||||
0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3)
|
||||
0x10 : 0001 0000, adaptionFieldControl = 01
|
||||
|
||||
这里:
|
||||
payload_unit_start_indicator = 1, 说明有效载荷起始符为1,含有PES包头
|
||||
adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况)
|
||||
|
||||
负载 : 00 00 01 C0 01 88 80 80 05 21 00 01 96 07 FF FD 85 00 33 22 22 11 22 11 11 11 11 11 11 24 82 41 00 90 40 00 00 00 00 00 40 00 ....... 70 34 5B CE 64 B7 D2 F5 4E 07 50 8E 11 1E 60 61 21 32 11 59
|
||||
|
||||
packetStartCodePrefix : 00 00 01
|
||||
streamID : C0
|
||||
pes_PacketLength : 01 88(值为392,占用392个字节,一帧数据长度,也可以置为0)
|
||||
Sned PES HEADER : 占用不确定位 本例为:80 80 05 21 00 01 96 07
|
||||
|
||||
|
||||
Sned PES HEADER 包括以下几个字段: 80 80 05 21 00 01 96 07(解析为二进制显示)
|
||||
| 8 0 | 8 0 | 0 5 | 2 1 | 0 0 | 0 1 | 9 6 | 0 7 |
|
||||
| 1000 0000| 1000 0000 | 0000 0101 | 0010 0001 | 0000 0000 | 0000 0001 | 1001 0110 | 0000 1110 |
|
||||
|
||||
(注意,下面的数值是用二进制表示,不特别声明,都是用16进制表示)
|
||||
(0x80)
|
||||
constTen : 10 固定
|
||||
PES_scrambling_control : 00 PES加扰控制
|
||||
PES_priority : 0 PES 包中该有效载荷的优先级
|
||||
data_alignment_indicator : 0 数据定位指示符
|
||||
copyright : 0 PES 包有效载荷的素材依靠版权所保护
|
||||
original_or_copy : 0 PES 包有效载荷的内容是原始的
|
||||
|
||||
(0x80)
|
||||
PTS_DTS_flags : 10 PES 包头中 PTS 字段存在
|
||||
ESCR_flag : 0
|
||||
ES_rate_flag : 0
|
||||
DSM_trick_mode_flag : 0
|
||||
additional_copy_info_flag : 0
|
||||
PES_CRC_flag : 0
|
||||
PES_extension_flag : 0
|
||||
|
||||
(0x05)
|
||||
PES_header_data_length : 0000 0101(值为5)PES头数据长度,表示后面还有5个字节,之后就是一帧的数据
|
||||
|
||||
(0x4200032C)(十进制:1107297068)
|
||||
PTS(presentation time stamp): 0010 0001 0000 0000 0000 0001 1001 0110 0
|
||||
|
||||
下面字段在本例中都没有:
|
||||
ESCR(42) = ESCR_base(33) + ESCR_extension(9)
|
||||
ES_rate(22)
|
||||
DSM特技方式(8)
|
||||
additional_copy_info(7)
|
||||
previous_PES_packet_CRC(16)
|
||||
PES_Extension(不确定)
|
||||
|
||||
|
||||
因为 PTS_DTS_flags == 10,所以本例中只有PTS没有DTS.
|
||||
|
||||
|
||||
注意 : 本TS包 包含PES头信息 说明开始下一帧
|
||||
|
||||
----------
|
||||
|
||||
|
||||
4. 第四个包
|
||||
包头 : 47 08 14 11
|
||||
0x47 : syncByte
|
||||
0x0 : 0000(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 0.
|
||||
0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3)
|
||||
0x11 : 0001 0001, adaptionFieldControl = 01
|
||||
|
||||
这里:
|
||||
payload_unit_start_indicator = 0, 说明有效载荷起始符为0,不含有PES包头
|
||||
adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况)
|
||||
|
||||
----------
|
||||
|
||||
|
||||
5. 第五个包
|
||||
包头 : 47 08 14 32
|
||||
0x47 : syncByte
|
||||
0x0 : 0000(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 0.
|
||||
0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3)
|
||||
0x32 : 0011 0010, adaptionFieldControl = 11
|
||||
|
||||
这里:
|
||||
payload_unit_start_indicator = 0, 说明有效载荷起始符为0,不含有PES包头
|
||||
adaptionFieldControl = 11, 说明先有自适应字段,再有有效载荷(TS包第一种情况)
|
||||
|
||||
负载 : 99 00 FF FF FF ... FF 52 DE E6 B5 D0 76 CD CB B2 24 B3 92 AD 4E CD 19 D2 CC 82 D4 78 10 80 6C 0E 99 49 A4 59 C0
|
||||
|
||||
adaptation_field_length : 99(值为153,表示占用153个字节)
|
||||
|
||||
discontinuity_indicator & random_access_indicator &
|
||||
elementary_stream_priority_indicator & PCR_flag &
|
||||
OPCR_flag & splicing_point_flag &
|
||||
transport_private_data_flag & adaptation_field_extension_flag : 00 剩下的所有字段都为0
|
||||
|
||||
(00 FF FF FF ... FF)这里都是调整字段,从52 DE E6 B5 D0(从00(FF之前,99之后) 开始算是第1个字节,跳到第153个字节)开始,就是真正的帧数据了
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
6. 第六个包
|
||||
包头 : 47 48 14 13
|
||||
0x47 : syncByte
|
||||
0x4 : 0100(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1.
|
||||
0x814 : 0 1000 0001 0100, pid = 0x814(音频MP3)
|
||||
0x13 : 0001 0011, adaptionFieldControl = 01,说明仅含有效负载(TS包第三种情况)
|
||||
|
||||
这里:
|
||||
payload_unit_start_indicator = 1, 说明有效载荷起始符为1,含有PES包头
|
||||
adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况)
|
||||
|
||||
负载 : 00 00 01 C0 01 88 80 80 05 21 00 01 A6 E7 FF FD
|
||||
|
||||
packetStartCodePrefix : 00 00 01
|
||||
streamID : C0
|
||||
pes_PacketLength : 01 88(值为392,占用392个字节)
|
||||
Sned PES HEADER : 占用不确定位
|
||||
|
||||
所以本包数据流ID 和 第二个包的流ID是一样的
|
||||
|
||||
注意 : 本TS包 又包含PES头信息 说明开始下一帧
|
||||
|
||||
|
||||
----------
|
||||
|
||||
7. 第七个包
|
||||
包头 : 47 48 10 30
|
||||
0x47 : syncByte
|
||||
0x4 : 0100(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 1.
|
||||
0x810 : 0 1000 0001 0000, pid = 0x810(视频H264)
|
||||
0x30 : 0011 0000, adaptionFieldControl = 11,说明含有调整字段和有效负载(TS包第一种情况)
|
||||
|
||||
这里:
|
||||
payload_unit_start_indicator = 1, 说明有效载荷起始符为1,含有PES包头
|
||||
adaptionFieldControl = 11, 说明含有调整字段和有效负载(TS包第一种情况)
|
||||
|
||||
负载 : 07 10 00 00 01 0F 7E 88 00 00 01 E0 00 00 80 C0 0A 31 00 01 96 07 11 00 01 7E 91 00 00 00 01 67 4D 40 1E 96 ...... D2 99 71 F3
|
||||
|
||||
adaptation_field_length : 07(值为7,表示占用153个字节)
|
||||
|
||||
discontinuity_indicator & random_access_indicator &
|
||||
elementary_stream_priority_indicator & PCR_flag &
|
||||
OPCR_flag & splicing_point_flag &
|
||||
transport_private_data_flag & adaptation_field_extension_flag : 10
|
||||
|
||||
(10 00 00 01 0F 7E 88)调整字段
|
||||
|
||||
packetStartCodePrefix : 00 00 01
|
||||
streamID : EO
|
||||
pes_PacketLength : 00 00(值为0,占用0个字节,一帧数据长度,也可以置为0,此时需要自己去计算)
|
||||
Sned PES HEADER : 占用不确定位
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
8. 第八个包
|
||||
包头 : 47 08 10 11
|
||||
0x47 : syncByte
|
||||
0x0 : 0000(这里的最后一个字节,要给到下面),payload_unit_start_indicator = 0.
|
||||
0x810 : 0 1000 0001 0000, pid = 0x810(视频H264)
|
||||
0x11 : 0001 0001, adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况)
|
||||
|
||||
这里:
|
||||
payload_unit_start_indicator = 0, 说明有效载荷起始符为0,不含有PES包头
|
||||
adaptionFieldControl = 01, 说明仅含有效负载(TS包第三种情况)
|
||||
|
||||
|
||||
----------
|
||||
|
||||
总结这个八个包:
|
||||
|
||||
第一个TS包(PID:0X00) : 包含了PAT.
|
||||
第二个TS包(PID:0X81) : 包含了PMT.
|
||||
第三个TS包(PID:0x814) : 音频PES包头所有的TS包.
|
||||
第四个TS包(PID:0x814) : 音频TS包.
|
||||
第五个TS包(PID:0x814) : 音频TS包.
|
||||
第六个TS包(PID:0x814) : 音频PES包头所有的TS包.
|
||||
第七个TS包(PID:0x810) : 视频PES包头所有的TS包.
|
||||
第八个TS包(PID:0x810) : 视频TS包.
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
// Packet Header:
|
||||
// PID是TS流中唯一识别标志,Packet Data是什么内容就是由PID决定的.如果一个TS流中的一个Packet的Packet Header中的PID是0x0000,
|
||||
// 那么这个Packet的Packet Data就是DVB的PAT表而非其他类型数据(如Video,Audio或其他业务信息).
|
||||
|
||||
// 分析一个Header:
|
||||
// 二进制: 0100 0111 0000 0111 1110 0101 0001 0010
|
||||
// 十六进制: 4 7 0 7 e 5 1 2
|
||||
|
||||
// syncByte = 0x47 就是0x47,这是DVB TS规定的同步字节,固定是0x47
|
||||
// transportErrorIndicator = 0 表示当前包没有发生传输错误
|
||||
// payloadUnitStartIndicator = 0 具体含义参考ISO13818-1标准文档
|
||||
// transportPriority = 0 表示当前包是低优先级
|
||||
// pid = 0x07e5(0 0111 1110 0101) Video PID
|
||||
// transportScramblingControl = 00 表示节目没有加密
|
||||
// adaptionFieldControl = 01 具体含义参考ISO13818-1标准文档
|
||||
// continuityCounter = 0010 表示当前传送的相同类型的包是第3个
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
// 分析一段TS流:(PAT)
|
||||
// Packet Header : 0x47 0x40 0x00 0x10
|
||||
// Packet Data : 00 00 b0 11 00 01 c1 00 00 00 00 e0 1f 00 01 e1 00 24 ac48 84 ff ff ... ff ff
|
||||
|
||||
// Header PID = 0x0000 说明数据包是PAT表信息,包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1")
|
||||
// 所以,Packet Data就应该是 : 00 b0 11 00 01 c1 00 00 00 00 e0 1f 00 01 e1 00 24 ac48 84 ff ff ... ff ff
|
||||
|
||||
//
|
||||
// 00 | b0 11 | 00 01 | c1 | 00 | 00 | 00 00 | e0 1f | 00 01 e1 00 |
|
||||
//
|
||||
|
||||
// table_id = 0000 0000
|
||||
|
||||
// section_syntax_indicator = 1
|
||||
// zero = 0
|
||||
// reserved1 = 11
|
||||
// sectionLength = 0000 0001 0001
|
||||
|
||||
// transportStreamID = 0000 0000 0000 0001
|
||||
|
||||
// reserved2 = 11
|
||||
// versionNumber = 0000 0
|
||||
// currentNextIndicator 1
|
||||
|
||||
// sectionNumber = 0000 0000
|
||||
|
||||
// lastSectionNumber = 0000 0000
|
||||
|
||||
// programNumber = 0000 0000 0000 0000
|
||||
|
||||
// reserved3 = 111
|
||||
// networkPID = 0 0000 0001 1111
|
||||
|
||||
// crc32
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
// 分析一段TS流:(PMT)
|
||||
// Packet Header : 0x47 0x43 0xe8 0x12
|
||||
// Packet Data : 00 02 b0 12 00 01 c1 00 00 e3 e9 f0 00 1b e3 e9 f0 00 f0 af b4 4f ff ff ... ff ff
|
||||
|
||||
// Header PID = 0x03e8 说明数据包是PMT表信息,包头后需要除去一个字节才是有效数据(payload_unit_start_indicator="1")
|
||||
// 所以,Packet Data就应该是 : 02 b0 12 00 01 c1 00 00 e3 e9 f0 00 1b e3 e9 f0 00 f0 af b4 4f ff ff ... ff ff
|
||||
|
||||
// 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
// 02 | b0 12 | 00 01 | c1 | 00 | 00 | e3 e9 | f0 00 | 1b | e3 e9 | f0 00 | f0 af b4 4f |
|
||||
//
|
||||
|
||||
// 1:
|
||||
// table_id = 0000 0010
|
||||
|
||||
// 2:
|
||||
// section_syntax_indicator = 1
|
||||
// zero = 0
|
||||
// reserved1 = 11
|
||||
// section_length = 0000 0001 0010
|
||||
|
||||
// 3:
|
||||
// program_number = 0000 0000 0000 0001
|
||||
|
||||
// 4:
|
||||
// reserved2 = 11
|
||||
// version_number = 00 000
|
||||
// current_next_indicator = 1
|
||||
|
||||
// 5:
|
||||
// section_number = 0000 0000
|
||||
|
||||
// 6:
|
||||
// last_section_number = 0000 0000
|
||||
|
||||
// 7:
|
||||
// reserved3 = 111
|
||||
// PCR_PID = 0 0011 1110 1001
|
||||
|
||||
// 8:
|
||||
// reserved4 = 1111
|
||||
// program_info_length = 0000 0000 0000
|
||||
|
||||
// 9:
|
||||
// stream_type = 0001 1011
|
||||
|
||||
// 10:
|
||||
// reserved5 = 111
|
||||
// elementary_PID = 0 0011 1110 1001
|
||||
|
||||
// 11:
|
||||
// reserved6 = 1111
|
||||
// ES_info_length = 0000 0000 0000
|
||||
|
||||
// 12:
|
||||
// crc
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
##TS流解码过程
|
||||
1. 获取TS中的PAT
|
||||
2. 获取TS中的PMT
|
||||
3. 根据PMT可以知道当前网络中传输的视频(音频)类型(H264),相应的PID,PCR的PID等信息.
|
||||
4. 设置demux 模块的视频Filter 为相应视频的PID和stream type等.
|
||||
5. 从视频Demux Filter 后得到的TS数据包中的payload 数据就是 one piece of PES,在TS header中有一些关于此 payload属于哪个 PES的 第多少个数据包. 因此软件中应该将此payload中的数据copy到PES的buffer中,用于拼接一个PES包.
|
||||
6. 拼接好的PES包的包头会有 PTS,DTS信息,去掉PES的header就是 ES.
|
||||
7. 直接将 被被拔掉 PES包头的ES包送给decoder就可以进行解码.解码出来的数据就是一帧一帧的视频数据,这些数据至少应当与PES中的PTS关联一下,以便进行视音频同步.
|
||||
8. I,B,B,P 信息是在ES中的.
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
1. 首先找到PID为0x00的TS包,找到里面的节目映射表(PMT)PID,因为可能有几个节目信息.所以可能有几个PMT_PID,以一个为例
|
||||
2. 接着查找该PMT_PID的TS包,通常就紧接着.在该PMT包中找音频和视频的PID.以视频为例.
|
||||
3. 开始提取一帧ES数据
|
||||
3.1 查找视频PID的TS包
|
||||
3.2 找PES包头,方法:TS包头第2个字节的高6位(有效载荷单元起始指示符)为1的TS包,跳过自适应字段,找到PES包头,提取时间戳,再跳至ES数据,这就是一帧ES数据的开始部分.
|
||||
3.3 查找有效载荷单元起始指示符为0的TS包.跳过TS包头,跳过自适应字段,提取后面的ES数据
|
||||
3.4 同3.3接着查找
|
||||
3.5 当碰到有效载荷单元起始指示符又变为1的视频TS包,就知道这是下一帧的开始了,将前面的所有ES数据组合成一帧数据.开始下一轮组帧.
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
##参考文档:
|
||||
|
||||
1. [TS流](http://blog.csdn.net/cabbage2008/article/category/5885203)
|
||||
1. [TS各个表 与 SECTION 的解析 CAS原理 ](http://blog.sina.com.cn/s/blog_6b94d5680101r5l6.html)
|
||||
@@ -1,72 +0,0 @@
|
||||
package mpegts
|
||||
|
||||
import "net"
|
||||
|
||||
// http://www.stmc.edu.hk/~vincent/ffmpeg_0.4.9-pre1/libavformat/mpegtsenc.c
|
||||
|
||||
var Crc32_Table = []uint32{
|
||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
|
||||
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
|
||||
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7,
|
||||
0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
|
||||
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3,
|
||||
0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
|
||||
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef,
|
||||
0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
|
||||
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb,
|
||||
0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
|
||||
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
|
||||
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
|
||||
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4,
|
||||
0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
|
||||
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08,
|
||||
0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
||||
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc,
|
||||
0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
|
||||
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050,
|
||||
0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
|
||||
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
|
||||
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
|
||||
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1,
|
||||
0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
|
||||
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5,
|
||||
0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
|
||||
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9,
|
||||
0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
|
||||
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd,
|
||||
0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
|
||||
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
|
||||
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
|
||||
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2,
|
||||
0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
|
||||
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e,
|
||||
0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
||||
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a,
|
||||
0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
|
||||
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676,
|
||||
0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
|
||||
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
|
||||
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
|
||||
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4,
|
||||
}
|
||||
|
||||
func GetCRC32(data []byte) (crc uint32) {
|
||||
crc = 0xffffffff
|
||||
|
||||
for _, v := range data {
|
||||
crc = (crc << 8) ^ Crc32_Table[((crc>>24)^uint32(v))&0xff]
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GetCRC32_2(data net.Buffers) (crc uint32) {
|
||||
crc = 0xffffffff
|
||||
for _, v := range data {
|
||||
for _, v2 := range v {
|
||||
crc = (crc << 8) ^ Crc32_Table[((crc>>24)^uint32(v2))&0xff]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
package mpegts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"m7s.live/v5/pkg/util"
|
||||
)
|
||||
|
||||
// ios13818-1-CN.pdf 43(57)/166
|
||||
//
|
||||
// PAT
|
||||
//
|
||||
|
||||
var DefaultPATPacket = []byte{
|
||||
// TS Header
|
||||
0x47, 0x40, 0x00, 0x10,
|
||||
|
||||
// Pointer Field
|
||||
0x00,
|
||||
|
||||
// PSI
|
||||
0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00,
|
||||
|
||||
// PAT
|
||||
0x00, 0x01, 0xe1, 0x00,
|
||||
|
||||
// CRC
|
||||
0xe8, 0xf9, 0x5e, 0x7d,
|
||||
|
||||
// Stuffing 167 bytes
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
}
|
||||
|
||||
// TS Header :
|
||||
// SyncByte = 0x47
|
||||
// TransportErrorIndicator = 0(B:0), PayloadUnitStartIndicator = 1(B:0), TransportPriority = 0(B:0),
|
||||
// Pid = 0,
|
||||
// TransportScramblingControl = 0(B:00), AdaptionFieldControl = 1(B:01), ContinuityCounter = 0(B:0000),
|
||||
|
||||
// PSI :
|
||||
// TableID = 0x00,
|
||||
// SectionSyntaxIndicator = 1(B:1), Zero = 0(B:0), Reserved1 = 3(B:11),
|
||||
// SectionLength = 13(0x00d)
|
||||
// TransportStreamID = 0x0001
|
||||
// Reserved2 = 3(B:11), VersionNumber = (B:00000), CurrentNextIndicator = 1(B:0),
|
||||
// SectionNumber = 0x00
|
||||
// LastSectionNumber = 0x00
|
||||
|
||||
// PAT :
|
||||
// ProgramNumber = 0x0001
|
||||
// Reserved3 = 15(B:1110), ProgramMapPID = 4097(0x1001)
|
||||
|
||||
// PAT表主要包含频道号码和每一个频道对应的PMT的PID号码,这些信息我们在处理PAT表格的时候会保存起来,以后会使用到这些数据
|
||||
type MpegTsPATProgram struct {
|
||||
ProgramNumber uint16 // 16 bit 节目号
|
||||
Reserved3 byte // 3 bits 保留位
|
||||
NetworkPID uint16 // 13 bits 网络信息表(NIT)的PID,节目号为0时对应的PID为network_PID
|
||||
ProgramMapPID uint16 // 13 bit 节目映射表的PID,节目号大于0时对应的PID.每个节目对应一个
|
||||
}
|
||||
|
||||
// Program Association Table (节目关联表)
|
||||
// 节目号为0x0000时,表示这是NIT,PID=0x001f,即3.
|
||||
// 节目号为0x0001时,表示这是PMT,PID=0x100,即256
|
||||
type MpegTsPAT struct {
|
||||
// PSI
|
||||
TableID byte // 8 bits 0x00->PAT,0x02->PMT
|
||||
SectionSyntaxIndicator byte // 1 bit 段语法标志位,固定为1
|
||||
Zero byte // 1 bit 0
|
||||
Reserved1 byte // 2 bits 保留位
|
||||
SectionLength uint16 // 12 bits 该字段的头两比特必为'00',剩余 10 比特指定该分段的字节数,紧随 section_length 字段开始,并包括 CRC.此字段中的值应不超过 1021(0x3FD)
|
||||
TransportStreamID uint16 // 16 bits 该字段充当标签,标识网络内此传输流有别于任何其他多路复用流.其值由用户规定
|
||||
Reserved2 byte // 2 bits 保留位
|
||||
VersionNumber byte // 5 bits 范围0-31,表示PAT的版本号
|
||||
CurrentNextIndicator byte // 1 bit 发送的PAT是当前有效还是下一个PAT有效,0则要等待下一个表
|
||||
SectionNumber byte // 8 bits 分段的号码.PAT可能分为多段传输.第一段为00,以后每个分段加1,最多可能有256个分段
|
||||
LastSectionNumber byte // 8 bits 最后一个分段的号码
|
||||
|
||||
// N Loop
|
||||
Program []MpegTsPATProgram // PAT表里面的所有频道索引信息
|
||||
|
||||
Crc32 uint32 // 32 bits 包含处理全部传输流节目映射分段之后,在附件 B 规定的解码器中给出寄存器零输出的 CRC 值
|
||||
}
|
||||
|
||||
func ReadPAT(r io.Reader) (pat MpegTsPAT, err error) {
|
||||
lr, psi, err := ReadPSI(r, PSI_TYPE_PAT)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pat = psi.Pat
|
||||
|
||||
// N Loop
|
||||
// 一直循环去读4个字节,用lr的原因是确保不会读过头了.
|
||||
for lr.N > 0 {
|
||||
|
||||
// 获取每一个频道的节目信息,保存起来
|
||||
programs := MpegTsPATProgram{}
|
||||
|
||||
programs.ProgramNumber, err = util.ReadByteToUint16(lr, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果programNumber为0,则是NetworkPID,否则是ProgramMapPID(13)
|
||||
if programs.ProgramNumber == 0 {
|
||||
programs.NetworkPID, err = util.ReadByteToUint16(lr, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
programs.NetworkPID = programs.NetworkPID & 0x1fff
|
||||
} else {
|
||||
programs.ProgramMapPID, err = util.ReadByteToUint16(lr, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
programs.ProgramMapPID = programs.ProgramMapPID & 0x1fff
|
||||
}
|
||||
|
||||
pat.Program = append(pat.Program, programs)
|
||||
}
|
||||
if cr, ok := r.(*util.Crc32Reader); ok {
|
||||
err = cr.ReadCrc32UIntAndCheck()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func WritePAT(w io.Writer, pat MpegTsPAT) (err error) {
|
||||
bw := &bytes.Buffer{}
|
||||
|
||||
// 将pat(所有的节目索引信息)写入到缓冲区中
|
||||
for _, pats := range pat.Program {
|
||||
if err = util.WriteUint16ToByte(bw, pats.ProgramNumber, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pats.ProgramNumber == 0 {
|
||||
if err = util.WriteUint16ToByte(bw, pats.NetworkPID&0x1fff|7<<13, true); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// | 0001 1111 | 1111 1111 |
|
||||
// 7 << 13 -> 1110 0000 0000 0000
|
||||
if err = util.WriteUint16ToByte(bw, pats.ProgramMapPID&0x1fff|7<<13, true); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pat.SectionLength == 0 {
|
||||
pat.SectionLength = 2 + 3 + 4 + uint16(len(bw.Bytes()))
|
||||
}
|
||||
|
||||
psi := MpegTsPSI{}
|
||||
|
||||
psi.Pat = pat
|
||||
|
||||
if err = WritePSI(w, PSI_TYPE_PAT, psi, bw.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func WritePATPacket(w io.Writer, tsHeader []byte, pat MpegTsPAT) (err error) {
|
||||
if pat.TableID != TABLE_PAS {
|
||||
err = errors.New("PAT table ID error")
|
||||
return
|
||||
}
|
||||
|
||||
// 将所有要写的数据(PAT),全部放入到buffer中去.
|
||||
// buffer 里面已经写好了整个pat表(PointerField+PSI+PAT+CRC)
|
||||
bw := &bytes.Buffer{}
|
||||
if err = WritePAT(bw, pat); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO:如果Pat.Program里面包含的信息很大,大于188?
|
||||
stuffingBytes := util.GetFillBytes(0xff, TS_PACKET_SIZE-4-bw.Len())
|
||||
|
||||
// PATPacket = TsHeader + PAT + Stuffing Bytes
|
||||
var PATPacket []byte
|
||||
PATPacket = append(PATPacket, tsHeader...)
|
||||
PATPacket = append(PATPacket, bw.Bytes()...)
|
||||
PATPacket = append(PATPacket, stuffingBytes...)
|
||||
|
||||
fmt.Println("-------------------------")
|
||||
fmt.Println("Write PAT :", PATPacket)
|
||||
fmt.Println("-------------------------")
|
||||
|
||||
// 写PAT负载
|
||||
if _, err = w.Write(PATPacket); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func WriteDefaultPATPacket(w io.Writer) (err error) {
|
||||
_, err = w.Write(DefaultPATPacket)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,454 +0,0 @@
|
||||
package mpegts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"m7s.live/v5/pkg/util"
|
||||
"net"
|
||||
)
|
||||
|
||||
// ios13818-1-CN.pdf 45/166
|
||||
//
|
||||
// PES
|
||||
//
|
||||
|
||||
// 每个传输流和节目流在逻辑上都是由 PES 包构造的
|
||||
type MpegTsPesStream struct {
|
||||
TsPkt MpegTsPacket
|
||||
PesPkt MpegTsPESPacket
|
||||
}
|
||||
|
||||
// PES--Packetized Elementary Streams (分组的ES),ES形成的分组称为PES分组,是用来传递ES的一种数据结构
|
||||
// 1110 xxxx 为视频流(0xE0)
|
||||
// 110x xxxx 为音频流(0xC0)
|
||||
type MpegTsPESPacket struct {
|
||||
Header MpegTsPESHeader
|
||||
Payload util.Buffer //从TS包中读取的数据
|
||||
Buffers net.Buffers //用于写TS包
|
||||
}
|
||||
|
||||
type MpegTsPESHeader struct {
|
||||
PacketStartCodePrefix uint32 // 24 bits 同跟随它的 stream_id 一起组成标识包起始端的包起始码.packet_start_code_prefix 为比特串"0000 0000 0000 0000 0000 0001"(0x000001)
|
||||
StreamID byte // 8 bits stream_id 指示基本流的类型和编号,如 stream_id 表 2-22 所定义的.传输流中,stream_id 可以设置为准确描述基本流类型的任何有效值,如表 2-22 所规定的.传输流中,基本流类型在 2.4.4 中所指示的节目特定信息中指定
|
||||
PesPacketLength uint16 // 16 bits 指示 PES 包中跟随该字段最后字节的字节数.0->指示 PES 包长度既未指示也未限定并且仅在这样的 PES 包中才被允许,该 PES 包的有效载荷由来自传输流包中所包含的视频基本流的字节组成
|
||||
|
||||
MpegTsOptionalPESHeader
|
||||
|
||||
PayloadLength uint64 // 这个不是标准文档里面的字段,是自己添加的,方便计算
|
||||
}
|
||||
|
||||
// 可选的PES Header = MpegTsOptionalPESHeader + stuffing bytes(0xFF) m * 8
|
||||
type MpegTsOptionalPESHeader struct {
|
||||
ConstTen byte // 2 bits 常量10
|
||||
PesScramblingControl byte // 2 bit 指示 PES 包有效载荷的加扰方式.当加扰在 PES 等级上实施时, PES 包头,其中包括任选字段只要存在,应不加扰(见表 2-23)
|
||||
PesPriority byte // 1 bit 指示在此 PES 包中该有效载荷的优先级.1->指示该 PES 包有效载荷比具有此字段置于"0"的其他 PES 包有效载荷有更高的有效载荷优先级.多路复用器能够使用该PES_priority 比特最佳化基本流内的数据
|
||||
DataAlignmentIndicator byte // 1 bit 1->指示 PES 包头之后紧随 2.6.10 中data_stream_alignment_descriptor 字段中指示的视频句法单元或音频同步字,只要该描述符字段存在.若置于值"1"并且该描述符不存在,则要求表 2-53,表 2-54 或表 2-55 的 alignment_type"01"中所指示的那种校准.0->不能确定任何此类校准是否发生
|
||||
Copyright byte // 1 bit 1->指示相关 PES 包有效载荷的素材依靠版权所保护.0->不能确定该素材是否依靠版权所保护
|
||||
OriginalOrCopy byte // 1 bit 1->指示相关 PES 包有效载荷的内容是原始的.0->指示相关 PES 包有效载荷的内容是复制的
|
||||
PtsDtsFlags byte // 2 bits 10->PES 包头中 PTS 字段存在. 11->PES 包头中 PTS 字段和 DTS 字段均存在. 00->PES 包头中既无任何 PTS 字段也无任何 DTS 字段存在. 01->禁用
|
||||
EscrFlag byte // 1 bit 1->指示 PES 包头中 ESCR 基准字段和 ESCR 扩展字段均存在.0->指示无任何 ESCR 字段存在
|
||||
EsRateFlag byte // 1 bit 1->指示 PES 包头中 ES_rate 字段存在.0->指示无任何 ES_rate 字段存在
|
||||
DsmTrickModeFlag byte // 1 bit 1->指示 8 比特特技方式字段存在.0->指示此字段不存在
|
||||
AdditionalCopyInfoFlag byte // 1 bit 1->指示 additional_copy_info 存在.0->时指示此字段不存在
|
||||
PesCRCFlag byte // 1 bit 1->指示 PES 包中 CRC 字段存在.0->指示此字段不存在
|
||||
PesExtensionFlag byte // 1 bit 1->时指示 PES 包头中扩展字段存在.0->指示此字段不存在
|
||||
PesHeaderDataLength byte // 8 bits 指示在此 PES包头中包含的由任选字段和任意填充字节所占据的字节总数.任选字段的存在由前导 PES_header_data_length 字段的字节来指定
|
||||
|
||||
// Optional Field
|
||||
Pts uint64 // 33 bits 指示时间与解码时间的关系如下: PTS 为三个独立字段编码的 33 比特数.它指示基本流 n 的显示单元 k 在系统目标解码器中的显示时间 tpn(k).PTS 值以系统时钟频率除以 300(产生 90 kHz)的周期为单位指定.显示时间依照以下公式 2-11 从 PTS 中推出.有关编码显示时间标记频率上的限制参阅 2.7.4
|
||||
Dts uint64 // 33 bits 指示基本流 n 的存取单元 j 在系统目标解码器中的解码时间 tdn(j). DTS 的值以系统时钟频率除以 300(生成90 kHz)的周期为单位指定.依照以下公式 2-12 从 DTS 中推出解码时间
|
||||
EscrBase uint64 // 33 bits 其值由 ESCR_base(i) 给出,如公式 2-14 中给出的
|
||||
EscrExtension uint16 // 9 bits 其值由 ESCR_ext(i) 给出,如公式 2-15 中给出的. ESCR 字段指示包含 ESCR_base 最后比特的字节到达 PES流的 PES-STD 输入端的预期时间(参阅 2.5.2.4)
|
||||
EsRate uint32 // 22 bits 在PES 流情况中,指定系统目标解码器接收 PES 包字节的速率.ES_rate 在包括它的 PES 包以及相同 PES 流的后续 PES 包中持续有效直至遇到新的 ES_rate 字段时为止.ES 速率值以 50 字节/秒为度量单位.0 值禁用
|
||||
TrickModeControl byte // 3 bits 指示适用于相关视频流的特技方式.在其他类型基本流的情况中,此字段以及后随 5 比特所规定的那些含义未确定.对于 trick_mode 状态的定义,参阅 2.4.2.3 的特技方式段落
|
||||
TrickModeValue byte // 5 bits
|
||||
AdditionalCopyInfo byte // 7 bits 包含与版权信息有关的专用数据
|
||||
PreviousPESPacketCRC uint16 // 16 bits 包含产生解码器中 16 寄存器零输出的 CRC 值, 类似于附件 A 中定义的解码器. 但在处理先前的 PES 包数据字节之后, PES 包头除外,采用多项式
|
||||
|
||||
// PES Extension
|
||||
PesPrivateDataFlag byte // 1 bit 1->指示该 PES 包头包含专用数据. 0->指示 PES 包头中不存在专用数据
|
||||
PackHeaderFieldFlag byte // 1 bit 1->指示 ISO/IEC 11172-1 包头或节目流包头在此 PES包头中存储.若此字段处于节目流中包含的 PES 包中,则此字段应设置为"0.传输流中, 0->指示该 PES 头中无任何包头存在
|
||||
ProgramPacketSequenceCounterFlag byte // 1 bit 1->指示 program_packet_sequence_counter, MPEG1_MPEG2_identifier 以及 original_stuff_length 字段在 PES 包中存在.0->它指示这些字段在 PES 头中不存在
|
||||
PSTDBufferFlag byte // 1 bit 1->指示 P-STD_buffer_scale 和 P-STD_buffer_size 在 PES包头中存在.0->指示这些字段在 PES 头中不存在
|
||||
Reserved byte // 3 bits
|
||||
PesExtensionFlag2 byte // 1 bits 1->指示 PES_extension_field_length 字段及相关的字段存在.0->指示 PES_extension_field_length 字段以及任何相关的字段均不存在.
|
||||
|
||||
// Optional Field
|
||||
PesPrivateData [16]byte // 128 bits 此数据,同前后字段数据结合,应不能仿真packet_start_code_prefix (0x000001)
|
||||
PackHeaderField byte // 8 bits 指示 pack_header_field() 的长度,以字节为单位
|
||||
ProgramPacketSequenceCounter byte // 7 bits
|
||||
Mpeg1Mpeg2Identifier byte // 1 bit 1->指示此 PES 包承载来自 ISO/IEC 11172-1 流的信息.0->指示此 PES 包承载来自节目流的信息
|
||||
OriginalStuffLength byte // 6 bits 在原始 ITU-T H.222.0 建议书| ISO/IEC 13818-1 PES 包头或在原始 ISO/IEC 11172-1 包头中所使用的填充字节数
|
||||
PSTDBufferScale byte // 1bit 它的含义仅当节目流中包含此 PES 包时才规定.它指示所使用的标度因子用于解释后续的 P-STD_buffer_size 字段.若前导 stream_id 指示音频流,则P-STD 缓冲器标度字段必为"0"值.若前导 stream_id 指示视频流,则 P-STD_buffer_scale 字段必为"1"值.对于所有其他流类型,该值可为"1"或为"0"
|
||||
PSTDBufferSize uint16 // 13 bits 其含义仅当节目流中包含此 PES包时才规定.它规定在 P-STD 中,输入缓冲器 BSn 的尺寸.若 STD_buffer_scale 为 "0"值,则 P-STD_buffer_size以 128 字节为单位度量该缓冲器尺寸.若 P-STD_buffer_scale 为"1",则 P-STD_buffer_size 以 1024 字节为单位度量该缓冲器尺寸
|
||||
PesExtensionFieldLength byte // 7 bits 指示 PES 扩展字段中跟随此长度字段的直至并包括任何保留字节为止的数据长度,以字节为度量单位
|
||||
StreamIDExtensionFlag byte // 1 bits
|
||||
//pesExtensionField []byte // PES_extension_field_length bits
|
||||
//packField []byte // pack_field_length bits
|
||||
}
|
||||
|
||||
// pts_dts_Flags == "10" -> PTS
|
||||
// 0010 4
|
||||
// PTS[32...30] 3
|
||||
// marker_bit 1
|
||||
// PTS[29...15] 15
|
||||
// marker_bit 1
|
||||
// PTS[14...0] 15
|
||||
// marker_bit 1
|
||||
|
||||
// pts_dts_Flags == "11" -> PTS + DTS
|
||||
|
||||
type MpegtsPESFrame struct {
|
||||
Pid uint16
|
||||
IsKeyFrame bool
|
||||
ContinuityCounter byte
|
||||
ProgramClockReferenceBase uint64
|
||||
}
|
||||
|
||||
func ReadPESHeader(r io.Reader) (header MpegTsPESHeader, err error) {
|
||||
var flags uint8
|
||||
var length uint
|
||||
|
||||
// packetStartCodePrefix(24) (0x000001)
|
||||
header.PacketStartCodePrefix, err = util.ReadByteToUint24(r, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if header.PacketStartCodePrefix != 0x0000001 {
|
||||
err = errors.New("read PacketStartCodePrefix is not 0x0000001")
|
||||
return
|
||||
}
|
||||
|
||||
// streamID(8)
|
||||
header.StreamID, err = util.ReadByteToUint8(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// pes_PacketLength(16)
|
||||
header.PesPacketLength, err = util.ReadByteToUint16(r, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
length = uint(header.PesPacketLength)
|
||||
|
||||
// PES包长度可能为0,这个时候,需要自己去算
|
||||
// 0 <= len <= 65535
|
||||
// 如果当length为0,那么先设置为最大值,然后用LimitedReade去读,如果读到最后面剩下的字节数小于65536,才是正确的包大小.
|
||||
// 一个包一般情况下不可能会读1<<31个字节.
|
||||
if length == 0 {
|
||||
length = 1 << 31
|
||||
}
|
||||
|
||||
// lrPacket 和 lrHeader 位置指针是在同一位置的
|
||||
lrPacket := &io.LimitedReader{R: r, N: int64(length)}
|
||||
lrHeader := lrPacket
|
||||
|
||||
// constTen(2)
|
||||
// pes_ScramblingControl(2)
|
||||
// pes_Priority(1)
|
||||
// dataAlignmentIndicator(1)
|
||||
// copyright(1)
|
||||
// originalOrCopy(1)
|
||||
flags, err = util.ReadByteToUint8(lrHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header.ConstTen = flags & 0xc0
|
||||
header.PesScramblingControl = flags & 0x30
|
||||
header.PesPriority = flags & 0x08
|
||||
header.DataAlignmentIndicator = flags & 0x04
|
||||
header.Copyright = flags & 0x02
|
||||
header.OriginalOrCopy = flags & 0x01
|
||||
|
||||
// pts_dts_Flags(2)
|
||||
// escr_Flag(1)
|
||||
// es_RateFlag(1)
|
||||
// dsm_TrickModeFlag(1)
|
||||
// additionalCopyInfoFlag(1)
|
||||
// pes_CRCFlag(1)
|
||||
// pes_ExtensionFlag(1)
|
||||
flags, err = util.ReadByteToUint8(lrHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header.PtsDtsFlags = flags & 0xc0
|
||||
header.EscrFlag = flags & 0x20
|
||||
header.EsRateFlag = flags & 0x10
|
||||
header.DsmTrickModeFlag = flags & 0x08
|
||||
header.AdditionalCopyInfoFlag = flags & 0x04
|
||||
header.PesCRCFlag = flags & 0x02
|
||||
header.PesExtensionFlag = flags & 0x01
|
||||
|
||||
// pes_HeaderDataLength(8)
|
||||
header.PesHeaderDataLength, err = util.ReadByteToUint8(lrHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
length = uint(header.PesHeaderDataLength)
|
||||
|
||||
lrHeader = &io.LimitedReader{R: lrHeader, N: int64(length)}
|
||||
|
||||
// 00 -> PES 包头中既无任何PTS 字段也无任何DTS 字段存在
|
||||
// 10 -> PES 包头中PTS 字段存在
|
||||
// 11 -> PES 包头中PTS 字段和DTS 字段均存在
|
||||
// 01 -> 禁用
|
||||
|
||||
// PTS(33)
|
||||
if flags&0x80 != 0 {
|
||||
var pts uint64
|
||||
pts, err = util.ReadByteToUint40(lrHeader, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header.Pts = util.GetPtsDts(pts)
|
||||
}
|
||||
|
||||
// DTS(33)
|
||||
if flags&0x80 != 0 && flags&0x40 != 0 {
|
||||
var dts uint64
|
||||
dts, err = util.ReadByteToUint40(lrHeader, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header.Dts = util.GetPtsDts(dts)
|
||||
}
|
||||
|
||||
// reserved(2) + escr_Base1(3) + marker_bit(1) +
|
||||
// escr_Base2(15) + marker_bit(1) + escr_Base23(15) +
|
||||
// marker_bit(1) + escr_Extension(9) + marker_bit(1)
|
||||
if header.EscrFlag != 0 {
|
||||
_, err = util.ReadByteToUint48(lrHeader, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//s.pes.escr_Base = escrBaseEx & 0x3fffffffe00
|
||||
//s.pes.escr_Extension = uint16(escrBaseEx & 0x1ff)
|
||||
}
|
||||
|
||||
// es_Rate(22)
|
||||
if header.EsRateFlag != 0 {
|
||||
header.EsRate, err = util.ReadByteToUint24(lrHeader, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 不知道为什么这里不用
|
||||
/*
|
||||
// trickModeControl(3) + trickModeValue(5)
|
||||
if s.pes.dsm_TrickModeFlag != 0 {
|
||||
trickMcMv, err := util.ReadByteToUint8(lrHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.pes.trickModeControl = trickMcMv & 0xe0
|
||||
s.pes.trickModeValue = trickMcMv & 0x1f
|
||||
}
|
||||
*/
|
||||
|
||||
// marker_bit(1) + additionalCopyInfo(7)
|
||||
if header.AdditionalCopyInfoFlag != 0 {
|
||||
header.AdditionalCopyInfo, err = util.ReadByteToUint8(lrHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header.AdditionalCopyInfo = header.AdditionalCopyInfo & 0x7f
|
||||
}
|
||||
|
||||
// previous_PES_Packet_CRC(16)
|
||||
if header.PesCRCFlag != 0 {
|
||||
header.PreviousPESPacketCRC, err = util.ReadByteToUint16(lrHeader, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// pes_PrivateDataFlag(1) + packHeaderFieldFlag(1) + programPacketSequenceCounterFlag(1) +
|
||||
// p_STD_BufferFlag(1) + reserved(3) + pes_ExtensionFlag2(1)
|
||||
if header.PesExtensionFlag != 0 {
|
||||
var flags uint8
|
||||
flags, err = util.ReadByteToUint8(lrHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
header.PesPrivateDataFlag = flags & 0x80
|
||||
header.PackHeaderFieldFlag = flags & 0x40
|
||||
header.ProgramPacketSequenceCounterFlag = flags & 0x20
|
||||
header.PSTDBufferFlag = flags & 0x10
|
||||
header.PesExtensionFlag2 = flags & 0x01
|
||||
|
||||
// TODO:下面所有的标志位,可能获取到的数据,都简单的读取后,丢弃,如果日后需要,在这里处理
|
||||
|
||||
// pes_PrivateData(128)
|
||||
if header.PesPrivateDataFlag != 0 {
|
||||
if _, err = io.CopyN(io.Discard, lrHeader, int64(16)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// packFieldLength(8)
|
||||
if header.PackHeaderFieldFlag != 0 {
|
||||
if _, err = io.CopyN(io.Discard, lrHeader, int64(1)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// marker_bit(1) + programPacketSequenceCounter(7) + marker_bit(1) +
|
||||
// mpeg1_mpeg2_Identifier(1) + originalStuffLength(6)
|
||||
if header.ProgramPacketSequenceCounterFlag != 0 {
|
||||
if _, err = io.CopyN(io.Discard, lrHeader, int64(2)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 01 + p_STD_bufferScale(1) + p_STD_bufferSize(13)
|
||||
if header.PSTDBufferFlag != 0 {
|
||||
if _, err = io.CopyN(io.Discard, lrHeader, int64(2)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// marker_bit(1) + pes_Extension_Field_Length(7) +
|
||||
// streamIDExtensionFlag(1)
|
||||
if header.PesExtensionFlag != 0 {
|
||||
if _, err = io.CopyN(io.Discard, lrHeader, int64(2)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 把剩下的头的数据消耗掉
|
||||
if lrHeader.N > 0 {
|
||||
if _, err = io.CopyN(io.Discard, lrHeader, int64(lrHeader.N)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 2的16次方,16个字节
|
||||
if lrPacket.N < 65536 {
|
||||
// 这里得到的其实是负载长度,因为已经偏移过了Header部分.
|
||||
//header.pes_PacketLength = uint16(lrPacket.N)
|
||||
header.PayloadLength = uint64(lrPacket.N)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func WritePESHeader(w io.Writer, header MpegTsPESHeader) (written int, err error) {
|
||||
if header.PacketStartCodePrefix != 0x0000001 {
|
||||
err = errors.New("write PacketStartCodePrefix is not 0x0000001")
|
||||
return
|
||||
}
|
||||
|
||||
// packetStartCodePrefix(24) (0x000001)
|
||||
if err = util.WriteUint24ToByte(w, header.PacketStartCodePrefix, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written += 3
|
||||
|
||||
// streamID(8)
|
||||
if err = util.WriteUint8ToByte(w, header.StreamID); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written += 1
|
||||
|
||||
// pes_PacketLength(16)
|
||||
// PES包长度可能为0,这个时候,需要自己去算
|
||||
// 0 <= len <= 65535
|
||||
if err = util.WriteUint16ToByte(w, header.PesPacketLength, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//fmt.Println("Length :", payloadLength)
|
||||
//fmt.Println("PES Packet Length :", header.pes_PacketLength)
|
||||
|
||||
written += 2
|
||||
|
||||
// constTen(2)
|
||||
// pes_ScramblingControl(2)
|
||||
// pes_Priority(1)
|
||||
// dataAlignmentIndicator(1)
|
||||
// copyright(1)
|
||||
// originalOrCopy(1)
|
||||
// 1000 0001
|
||||
if header.ConstTen != 0x80 {
|
||||
err = errors.New("pes header ConstTen != 0x80")
|
||||
return
|
||||
}
|
||||
|
||||
flags := header.ConstTen | header.PesScramblingControl | header.PesPriority | header.DataAlignmentIndicator | header.Copyright | header.OriginalOrCopy
|
||||
if err = util.WriteUint8ToByte(w, flags); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written += 1
|
||||
|
||||
// pts_dts_Flags(2)
|
||||
// escr_Flag(1)
|
||||
// es_RateFlag(1)
|
||||
// dsm_TrickModeFlag(1)
|
||||
// additionalCopyInfoFlag(1)
|
||||
// pes_CRCFlag(1)
|
||||
// pes_ExtensionFlag(1)
|
||||
sevenFlags := header.PtsDtsFlags | header.EscrFlag | header.EsRateFlag | header.DsmTrickModeFlag | header.AdditionalCopyInfoFlag | header.PesCRCFlag | header.PesExtensionFlag
|
||||
if err = util.WriteUint8ToByte(w, sevenFlags); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written += 1
|
||||
|
||||
// pes_HeaderDataLength(8)
|
||||
if err = util.WriteUint8ToByte(w, header.PesHeaderDataLength); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written += 1
|
||||
|
||||
// PtsDtsFlags == 192(11), 128(10), 64(01)禁用, 0(00)
|
||||
if header.PtsDtsFlags&0x80 != 0 {
|
||||
// PTS和DTS都存在(11),否则只有PTS(10)
|
||||
if header.PtsDtsFlags&0x80 != 0 && header.PtsDtsFlags&0x40 != 0 {
|
||||
// 11:PTS和DTS
|
||||
// PTS(33) + 4 + 3
|
||||
pts := util.PutPtsDts(header.Pts) | 3<<36
|
||||
if err = util.WriteUint40ToByte(w, pts, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written += 5
|
||||
|
||||
// DTS(33) + 4 + 3
|
||||
dts := util.PutPtsDts(header.Dts) | 1<<36
|
||||
if err = util.WriteUint40ToByte(w, dts, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written += 5
|
||||
} else {
|
||||
// 10:只有PTS
|
||||
// PTS(33) + 4 + 3
|
||||
pts := util.PutPtsDts(header.Pts) | 2<<36
|
||||
if err = util.WriteUint40ToByte(w, pts, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
written += 5
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,367 +0,0 @@
|
||||
package mpegts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"m7s.live/v5/pkg/codec"
|
||||
"m7s.live/v5/pkg/util"
|
||||
"net"
|
||||
)
|
||||
|
||||
// ios13818-1-CN.pdf 46(60)-153(167)/page
|
||||
//
|
||||
// PMT
|
||||
|
||||
var (
|
||||
TSHeader = []byte{0x47, 0x40 | (PID_PMT >> 8), PID_PMT & 0xff, 0x10, 0x00} //PID:0x100
|
||||
PSI = []byte{0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00}
|
||||
PMT = []byte{0xe0 | (PID_VIDEO >> 8), PID_VIDEO & 0xff, 0xf0, 0x00} //PcrPID:0x101
|
||||
h264 = []byte{STREAM_TYPE_H264, 0xe0 | (PID_VIDEO >> 8), PID_VIDEO & 0xff, 0xf0, 0x00}
|
||||
h265 = []byte{STREAM_TYPE_H265, 0xe0 | (PID_VIDEO >> 8), PID_VIDEO & 0xff, 0xf0, 0x00}
|
||||
aac = []byte{STREAM_TYPE_AAC, 0xe0 | (PID_AUDIO >> 8), PID_AUDIO & 0xff, 0xf0, 0x00}
|
||||
pcma = []byte{STREAM_TYPE_G711A, 0xe0 | (PID_AUDIO >> 8), PID_AUDIO & 0xff, 0xf0, 0x00}
|
||||
pcmu = []byte{STREAM_TYPE_G711U, 0xe0 | (PID_AUDIO >> 8), PID_AUDIO & 0xff, 0xf0, 0x00}
|
||||
Stuffing []byte
|
||||
)
|
||||
|
||||
func init() {
|
||||
Stuffing = util.GetFillBytes(0xff, TS_PACKET_SIZE)
|
||||
}
|
||||
|
||||
// TS Header :
|
||||
// SyncByte = 0x47
|
||||
// TransportErrorIndicator = 0(B:0), PayloadUnitStartIndicator = 1(B:0), TransportPriority = 0(B:0),
|
||||
// Pid = 4097(0x1001),
|
||||
// TransportScramblingControl = 0(B:00), AdaptionFieldControl = 1(B:01), ContinuityCounter = 0(B:0000),
|
||||
|
||||
// PSI :
|
||||
// TableID = 0x02,
|
||||
// SectionSyntaxIndicator = 1(B:1), Zero = 0(B:0), Reserved1 = 3(B:11),
|
||||
// SectionLength = 23(0x17)
|
||||
// ProgramNumber = 0x0001
|
||||
// Reserved2 = 3(B:11), VersionNumber = (B:00000), CurrentNextIndicator = 1(B:0),
|
||||
// SectionNumber = 0x00
|
||||
// LastSectionNumber = 0x00
|
||||
|
||||
// PMT:
|
||||
// Reserved3 = 15(B:1110), PcrPID = 256(0x100)
|
||||
// Reserved4 = 16(B:1111), ProgramInfoLength = 0(0x000)
|
||||
// H264:
|
||||
// StreamType = 0x1b,
|
||||
// Reserved5 = 15(B:1110), ElementaryPID = 256(0x100)
|
||||
// Reserved6 = 16(B:1111), EsInfoLength = 0(0x000)
|
||||
// AAC:
|
||||
// StreamType = 0x0f,
|
||||
// Reserved5 = 15(B:1110), ElementaryPID = 257(0x101)
|
||||
// Reserved6 = 16(B:1111), EsInfoLength = 0(0x000)
|
||||
|
||||
type MpegTsPmtStream struct {
|
||||
StreamType byte // 8 bits 指示具有 PID值的包内承载的节目元类型,其 PID值由 elementary_PID所指定
|
||||
Reserved5 byte // 3 bits 保留位
|
||||
ElementaryPID uint16 // 13 bits 指定承载相关节目元的传输流包的 PID
|
||||
Reserved6 byte // 4 bits 保留位
|
||||
EsInfoLength uint16 // 12 bits 该字段的头两比特必为'00',剩余 10比特指示紧随 ES_info_length字段的相关节目元描述符的字节数
|
||||
|
||||
// N Loop Descriptors
|
||||
Descriptor []MpegTsDescriptor // 不确定字节数,可变
|
||||
}
|
||||
|
||||
// Program Map Table (节目映射表)
|
||||
type MpegTsPMT struct {
|
||||
// PSI
|
||||
TableID byte // 8 bits 0x00->PAT,0x02->PMT
|
||||
SectionSyntaxIndicator byte // 1 bit 段语法标志位,固定为1
|
||||
Zero byte // 1 bit 0
|
||||
Reserved1 byte // 2 bits 保留位
|
||||
SectionLength uint16 // 12 bits 该字段的头两比特必为'00',剩余 10 比特指定该分段的字节数,紧随 section_length 字段开始,并包括 CRC.此字段中的值应不超过 1021(0x3FD)
|
||||
ProgramNumber uint16 // 16 bits 指定 program_map_PID 所适用的节目
|
||||
Reserved2 byte // 2 bits 保留位
|
||||
VersionNumber byte // 5 bits 范围0-31,表示PAT的版本号
|
||||
CurrentNextIndicator byte // 1 bit 发送的PAT是当前有效还是下一个PAT有效
|
||||
SectionNumber byte // 8 bits 分段的号码.PAT可能分为多段传输.第一段为00,以后每个分段加1,最多可能有256个分段
|
||||
LastSectionNumber byte // 8 bits 最后一个分段的号码
|
||||
|
||||
Reserved3 byte // 3 bits 保留位 0x07
|
||||
PcrPID uint16 // 13 bits 指明TS包的PID值.该TS包含有PCR域,该PCR值对应于由节目号指定的对应节目.如果对于私有数据流的节目定义与PCR无关.这个域的值将为0x1FFF
|
||||
Reserved4 byte // 4 bits 预留位 0x0F
|
||||
ProgramInfoLength uint16 // 12 bits 前两位bit为00.该域指出跟随其后对节目信息的描述的byte数
|
||||
ProgramInfoDescriptor []MpegTsDescriptor // N Loop Descriptors 可变 节目信息描述
|
||||
|
||||
// N Loop
|
||||
Stream []MpegTsPmtStream // PMT表里面的所有音视频索引信息
|
||||
|
||||
Crc32 uint32 // 32 bits 包含处理全部传输流节目映射分段之后,在附件 B 规定的解码器中给出寄存器零输出的 CRC 值
|
||||
}
|
||||
|
||||
func ReadPMT(r io.Reader) (pmt MpegTsPMT, err error) {
|
||||
lr, psi, err := ReadPSI(r, PSI_TYPE_PMT)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pmt = psi.Pmt
|
||||
|
||||
// reserved3(3) + pcrPID(13)
|
||||
pcrPID, err := util.ReadByteToUint16(lr, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pmt.PcrPID = pcrPID & 0x1fff
|
||||
|
||||
// reserved4(4) + programInfoLength(12)
|
||||
// programInfoLength(12) == 0x00(固定为0) + programInfoLength(10)
|
||||
programInfoLength, err := util.ReadByteToUint16(lr, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pmt.ProgramInfoLength = programInfoLength & 0x3ff
|
||||
|
||||
// 如果length>0那么,紧跟programInfoLength后面就有length个字节
|
||||
if pmt.ProgramInfoLength > 0 {
|
||||
lr := &io.LimitedReader{R: lr, N: int64(pmt.ProgramInfoLength)}
|
||||
pmt.ProgramInfoDescriptor, err = ReadPMTDescriptor(lr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// N Loop
|
||||
// 开始N循环,读取所有的流的信息
|
||||
for lr.N > 0 {
|
||||
var streams MpegTsPmtStream
|
||||
// streamType(8)
|
||||
streams.StreamType, err = util.ReadByteToUint8(lr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// reserved5(3) + elementaryPID(13)
|
||||
streams.ElementaryPID, err = util.ReadByteToUint16(lr, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
streams.ElementaryPID = streams.ElementaryPID & 0x1fff
|
||||
|
||||
// reserved6(4) + esInfoLength(12)
|
||||
// esInfoLength(12) == 0x00(固定为0) + esInfoLength(10)
|
||||
streams.EsInfoLength, err = util.ReadByteToUint16(lr, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
streams.EsInfoLength = streams.EsInfoLength & 0x3ff
|
||||
|
||||
// 如果length>0那么,紧跟esInfoLength后面就有length个字节
|
||||
if streams.EsInfoLength > 0 {
|
||||
lr := &io.LimitedReader{R: lr, N: int64(streams.EsInfoLength)}
|
||||
streams.Descriptor, err = ReadPMTDescriptor(lr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 每读取一个流的信息(音频流或者视频流或者其他),都保存起来
|
||||
pmt.Stream = append(pmt.Stream, streams)
|
||||
}
|
||||
if cr, ok := r.(*util.Crc32Reader); ok {
|
||||
err = cr.ReadCrc32UIntAndCheck()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadPMTDescriptor(lr *io.LimitedReader) (Desc []MpegTsDescriptor, err error) {
|
||||
var desc MpegTsDescriptor
|
||||
for lr.N > 0 {
|
||||
// tag (8)
|
||||
desc.Tag, err = util.ReadByteToUint8(lr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// length (8)
|
||||
desc.Length, err = util.ReadByteToUint8(lr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
desc.Data = make([]byte, desc.Length)
|
||||
_, err = lr.Read(desc.Data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
Desc = append(Desc, desc)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func WritePMTDescriptor(w io.Writer, descs []MpegTsDescriptor) (err error) {
|
||||
for _, desc := range descs {
|
||||
// tag(8)
|
||||
if err = util.WriteUint8ToByte(w, desc.Tag); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// length (8)
|
||||
if err = util.WriteUint8ToByte(w, uint8(len(desc.Data))); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// data
|
||||
if _, err = w.Write(desc.Data); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func WritePMTBody(w io.Writer, pmt MpegTsPMT) (err error) {
|
||||
// reserved3(3) + pcrPID(13)
|
||||
if err = util.WriteUint16ToByte(w, pmt.PcrPID|7<<13, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// programInfoDescriptor 节目信息描述,字节数不能确定
|
||||
bw := &bytes.Buffer{}
|
||||
if err = WritePMTDescriptor(bw, pmt.ProgramInfoDescriptor); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pmt.ProgramInfoLength = uint16(bw.Len())
|
||||
|
||||
// reserved4(4) + programInfoLength(12)
|
||||
// programInfoLength(12) == 0x00(固定为0) + programInfoLength(10)
|
||||
if err = util.WriteUint16ToByte(w, pmt.ProgramInfoLength|0xf000, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// programInfoDescriptor
|
||||
if _, err = w.Write(bw.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 循环读取所有的流的信息(音频或者视频)
|
||||
for _, esinfo := range pmt.Stream {
|
||||
// streamType(8)
|
||||
if err = util.WriteUint8ToByte(w, esinfo.StreamType); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// reserved5(3) + elementaryPID(13)
|
||||
if err = util.WriteUint16ToByte(w, esinfo.ElementaryPID|7<<13, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// descriptor ES流信息描述,字节数不能确定
|
||||
bw := &bytes.Buffer{}
|
||||
if err = WritePMTDescriptor(bw, esinfo.Descriptor); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
esinfo.EsInfoLength = uint16(bw.Len())
|
||||
|
||||
// reserved6(4) + esInfoLength(12)
|
||||
// esInfoLength(12) == 0x00(固定为0) + esInfoLength(10)
|
||||
if err = util.WriteUint16ToByte(w, esinfo.EsInfoLength|0xf000, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// descriptor
|
||||
if _, err = w.Write(bw.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func WritePMT(w io.Writer, pmt MpegTsPMT) (err error) {
|
||||
bw := &bytes.Buffer{}
|
||||
|
||||
if err = WritePMTBody(bw, pmt); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pmt.SectionLength == 0 {
|
||||
pmt.SectionLength = 2 + 3 + 4 + uint16(len(bw.Bytes()))
|
||||
}
|
||||
|
||||
psi := MpegTsPSI{}
|
||||
|
||||
psi.Pmt = pmt
|
||||
|
||||
if err = WritePSI(w, PSI_TYPE_PMT, psi, bw.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// func WritePMTPacket(w io.Writer, tsHeader []byte, pmt MpegTsPMT) (err error) {
|
||||
// if pmt.TableID != TABLE_TSPMS {
|
||||
// err = errors.New("PMT table ID error")
|
||||
// return
|
||||
// }
|
||||
|
||||
// // 将所有要写的数据(PMT),全部放入到buffer中去.
|
||||
// // buffer 里面已经写好了整个PMT表(PointerField+PSI+PMT+CRC)
|
||||
// bw := &bytes.Buffer{}
|
||||
// if err = WritePMT(bw, pmt); err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// // TODO:如果Pmt.Stream里面包含的信息很大,大于188?
|
||||
// stuffingBytes := util.GetFillBytes(0xff, TS_PACKET_SIZE-4-bw.Len())
|
||||
|
||||
// var PMTPacket []byte
|
||||
// PMTPacket = append(PMTPacket, tsHeader...)
|
||||
// PMTPacket = append(PMTPacket, bw.Bytes()...)
|
||||
// PMTPacket = append(PMTPacket, stuffingBytes...)
|
||||
|
||||
// fmt.Println("-------------------------")
|
||||
// fmt.Println("Write PMT :", PMTPacket)
|
||||
// fmt.Println("-------------------------")
|
||||
|
||||
// // 写PMT负载
|
||||
// if _, err = w.Write(PMTPacket); err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// return
|
||||
// }
|
||||
|
||||
func WritePMTPacket(w io.Writer, videoCodec codec.FourCC, audioCodec codec.FourCC) {
|
||||
w.Write(TSHeader)
|
||||
crc := make([]byte, 4)
|
||||
paddingSize := TS_PACKET_SIZE - len(crc) - len(PSI) - len(PMT) - len(TSHeader) - 10
|
||||
pmt := net.Buffers{PSI, PMT}
|
||||
switch videoCodec {
|
||||
case codec.FourCC_H264:
|
||||
pmt = append(pmt, h264)
|
||||
case codec.FourCC_H265:
|
||||
pmt = append(pmt, h265)
|
||||
default:
|
||||
paddingSize += 5
|
||||
}
|
||||
switch audioCodec {
|
||||
case codec.FourCC_MP4A:
|
||||
pmt = append(pmt, aac)
|
||||
case codec.FourCC_ALAW:
|
||||
pmt = append(pmt, pcma)
|
||||
case codec.FourCC_ULAW:
|
||||
pmt = append(pmt, pcmu)
|
||||
default:
|
||||
paddingSize += 5
|
||||
}
|
||||
util.PutBE(crc, GetCRC32_2(pmt))
|
||||
pmt = append(pmt, crc, Stuffing[:paddingSize])
|
||||
pmt.WriteTo(w)
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
package mpegts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"m7s.live/v5/pkg/util"
|
||||
)
|
||||
|
||||
//
|
||||
// PSI
|
||||
//
|
||||
|
||||
const (
|
||||
PSI_TYPE_PAT = 1
|
||||
PSI_TYPE_PMT = 2
|
||||
PSI_TYPE_NIT = 3
|
||||
PSI_TYPE_CAT = 4
|
||||
PSI_TYPE_TST = 5
|
||||
PSI_TYPE_IPMP_CIT = 6
|
||||
)
|
||||
|
||||
type MpegTsPSI struct {
|
||||
// PAT
|
||||
// PMT
|
||||
// CAT
|
||||
// NIT
|
||||
Pat MpegTsPAT
|
||||
Pmt MpegTsPMT
|
||||
}
|
||||
|
||||
// 当传输流包有效载荷包含 PSI 数据时,payload_unit_start_indicator 具有以下意义:
|
||||
// 若传输流包承载 PSI分段的首字节,则 payload_unit_start_indicator 值必为 1,指示此传输流包的有效载荷的首字节承载pointer_field.
|
||||
// 若传输流包不承载 PSI 分段的首字节,则 payload_unit_start_indicator 值必为 0,指示在此有效载荷中不存在 pointer_field
|
||||
// 只要是PSI就一定会有pointer_field
|
||||
func ReadPSI(r io.Reader, pt uint32) (lr *io.LimitedReader, psi MpegTsPSI, err error) {
|
||||
// pointer field(8)
|
||||
cr, ok := r.(*util.Crc32Reader)
|
||||
if ok {
|
||||
r = cr.R
|
||||
}
|
||||
pointer_field, err := util.ReadByteToUint8(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pointer_field != 0 {
|
||||
// 无论如何都应该确保能将pointer_field读取到,并且io.Reader指针向下移动
|
||||
// ioutil.Discard常用在,http中,如果Get请求,获取到了很大的Body,要丢弃Body,就用这个方法.
|
||||
// 因为http默认重链接的时候,必须等body读取完成.
|
||||
// 用于发送需要读取但不想存储的数据,目的是耗尽读取端的数据
|
||||
if _, err = io.CopyN(io.Discard, r, int64(pointer_field)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
r = cr
|
||||
}
|
||||
|
||||
// table id(8)
|
||||
tableId, err := util.ReadByteToUint8(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// sectionSyntaxIndicator(1) + zero(1) + reserved1(2) + sectionLength(12)
|
||||
// sectionLength 前两个字节固定为00
|
||||
sectionSyntaxIndicatorAndSectionLength, err := util.ReadByteToUint16(r, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 指定该分段的字节数,紧随 section_length 字段开始,并包括 CRC
|
||||
// 因此剩下最多只能在读sectionLength长度的字节
|
||||
lr = &io.LimitedReader{R: r, N: int64(sectionSyntaxIndicatorAndSectionLength & 0x3FF)}
|
||||
|
||||
// PAT TransportStreamID(16) or PMT ProgramNumber(16)
|
||||
transportStreamIdOrProgramNumber, err := util.ReadByteToUint16(lr, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// reserved2(2) + versionNumber(5) + currentNextIndicator(1)
|
||||
versionNumberAndCurrentNextIndicator, err := util.ReadByteToUint8(lr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// sectionNumber(8)
|
||||
sectionNumber, err := util.ReadByteToUint8(lr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// lastSectionNumber(8)
|
||||
lastSectionNumber, err := util.ReadByteToUint8(lr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 因为lr.N是从sectionLength开始计算,所以要减去 pointer_field(8) + table id(8) + sectionSyntaxIndicator(1) + zero(1) + reserved1(2) + sectionLength(12)
|
||||
lr.N -= 4
|
||||
|
||||
switch pt {
|
||||
case PSI_TYPE_PAT:
|
||||
{
|
||||
if tableId != TABLE_PAS {
|
||||
err = errors.New(fmt.Sprintf("%s, id=%d", "read pmt table id != 2", tableId))
|
||||
return
|
||||
}
|
||||
|
||||
psi.Pat.TableID = tableId
|
||||
psi.Pat.SectionSyntaxIndicator = uint8((sectionSyntaxIndicatorAndSectionLength & 0x8000) >> 15)
|
||||
psi.Pat.SectionLength = sectionSyntaxIndicatorAndSectionLength & 0x3FF
|
||||
psi.Pat.TransportStreamID = transportStreamIdOrProgramNumber
|
||||
psi.Pat.VersionNumber = versionNumberAndCurrentNextIndicator & 0x3e
|
||||
psi.Pat.CurrentNextIndicator = versionNumberAndCurrentNextIndicator & 0x01
|
||||
psi.Pat.SectionNumber = sectionNumber
|
||||
psi.Pat.LastSectionNumber = lastSectionNumber
|
||||
}
|
||||
case PSI_TYPE_PMT:
|
||||
{
|
||||
if tableId != TABLE_TSPMS {
|
||||
err = errors.New(fmt.Sprintf("%s, id=%d", "read pmt table id != 2", tableId))
|
||||
return
|
||||
}
|
||||
|
||||
psi.Pmt.TableID = tableId
|
||||
psi.Pmt.SectionSyntaxIndicator = uint8((sectionSyntaxIndicatorAndSectionLength & 0x8000) >> 15)
|
||||
psi.Pmt.SectionLength = sectionSyntaxIndicatorAndSectionLength & 0x3FF
|
||||
psi.Pmt.ProgramNumber = transportStreamIdOrProgramNumber
|
||||
psi.Pmt.VersionNumber = versionNumberAndCurrentNextIndicator & 0x3e
|
||||
psi.Pmt.CurrentNextIndicator = versionNumberAndCurrentNextIndicator & 0x01
|
||||
psi.Pmt.SectionNumber = sectionNumber
|
||||
psi.Pmt.LastSectionNumber = lastSectionNumber
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func WritePSI(w io.Writer, pt uint32, psi MpegTsPSI, data []byte) (err error) {
|
||||
var tableId, versionNumberAndCurrentNextIndicator, sectionNumber, lastSectionNumber uint8
|
||||
var sectionSyntaxIndicatorAndSectionLength, transportStreamIdOrProgramNumber uint16
|
||||
|
||||
switch pt {
|
||||
case PSI_TYPE_PAT:
|
||||
{
|
||||
if psi.Pat.TableID != TABLE_PAS {
|
||||
err = errors.New(fmt.Sprintf("%s, id=%d", "write pmt table id != 0", tableId))
|
||||
return
|
||||
}
|
||||
|
||||
tableId = psi.Pat.TableID
|
||||
sectionSyntaxIndicatorAndSectionLength = uint16(psi.Pat.SectionSyntaxIndicator)<<15 | 3<<12 | psi.Pat.SectionLength
|
||||
transportStreamIdOrProgramNumber = psi.Pat.TransportStreamID
|
||||
versionNumberAndCurrentNextIndicator = psi.Pat.VersionNumber<<1 | psi.Pat.CurrentNextIndicator
|
||||
sectionNumber = psi.Pat.SectionNumber
|
||||
lastSectionNumber = psi.Pat.LastSectionNumber
|
||||
}
|
||||
case PSI_TYPE_PMT:
|
||||
{
|
||||
if psi.Pmt.TableID != TABLE_TSPMS {
|
||||
err = errors.New(fmt.Sprintf("%s, id=%d", "write pmt table id != 2", tableId))
|
||||
return
|
||||
}
|
||||
|
||||
tableId = psi.Pmt.TableID
|
||||
sectionSyntaxIndicatorAndSectionLength = uint16(psi.Pmt.SectionSyntaxIndicator)<<15 | 3<<12 | psi.Pmt.SectionLength
|
||||
transportStreamIdOrProgramNumber = psi.Pmt.ProgramNumber
|
||||
versionNumberAndCurrentNextIndicator = psi.Pmt.VersionNumber<<1 | psi.Pmt.CurrentNextIndicator
|
||||
sectionNumber = psi.Pmt.SectionNumber
|
||||
lastSectionNumber = psi.Pmt.LastSectionNumber
|
||||
}
|
||||
}
|
||||
|
||||
// pointer field(8)
|
||||
if err = util.WriteUint8ToByte(w, 0); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用buffer收集所有需要计算CRC32的数据
|
||||
bw := &bytes.Buffer{}
|
||||
|
||||
// table id(8)
|
||||
if err = util.WriteUint8ToByte(bw, tableId); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// sectionSyntaxIndicator(1) + zero(1) + reserved1(2) + sectionLength(12)
|
||||
// sectionLength 前两个字节固定为00
|
||||
// 1 0 11 sectionLength
|
||||
if err = util.WriteUint16ToByte(bw, sectionSyntaxIndicatorAndSectionLength, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// PAT TransportStreamID(16) or PMT ProgramNumber(16)
|
||||
if err = util.WriteUint16ToByte(bw, transportStreamIdOrProgramNumber, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// reserved2(2) + versionNumber(5) + currentNextIndicator(1)
|
||||
// 0x3 << 6 -> 1100 0000
|
||||
// 0x3 << 6 | 1 -> 1100 0001
|
||||
if err = util.WriteUint8ToByte(bw, versionNumberAndCurrentNextIndicator); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// sectionNumber(8)
|
||||
if err = util.WriteUint8ToByte(bw, sectionNumber); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// lastSectionNumber(8)
|
||||
if err = util.WriteUint8ToByte(bw, lastSectionNumber); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// data
|
||||
if _, err = bw.Write(data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 写入PSI数据
|
||||
if _, err = w.Write(bw.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用MPEG-TS CRC32算法计算CRC32
|
||||
crc32 := GetCRC32(bw.Bytes())
|
||||
if err = util.WriteUint32ToByte(w, crc32, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
"time"
|
||||
|
||||
"m7s.live/v5"
|
||||
"m7s.live/v5/pkg"
|
||||
"m7s.live/v5/pkg/codec"
|
||||
"m7s.live/v5/pkg/format"
|
||||
mpegts "m7s.live/v5/pkg/format/ts"
|
||||
"m7s.live/v5/pkg/util"
|
||||
mpegts "m7s.live/v5/plugin/hls/pkg/ts"
|
||||
)
|
||||
|
||||
func NewTransform() m7s.ITransformer {
|
||||
@@ -30,7 +30,6 @@ type HLSWriter struct {
|
||||
Fragment time.Duration
|
||||
M3u8 util.Buffer
|
||||
ts *TsInMemory
|
||||
pesAudio, pesVideo *mpegts.MpegtsPESFrame
|
||||
write_time time.Duration
|
||||
memoryTs sync.Map
|
||||
hls_segment_count uint32 // hls segment count
|
||||
@@ -83,27 +82,23 @@ func (w *HLSWriter) Run() (err error) {
|
||||
videoCodec = subscriber.Publisher.VideoTrack.FourCC()
|
||||
}
|
||||
w.ts = &TsInMemory{}
|
||||
w.pesAudio = &mpegts.MpegtsPESFrame{
|
||||
Pid: mpegts.PID_AUDIO,
|
||||
}
|
||||
w.pesVideo = &mpegts.MpegtsPESFrame{
|
||||
Pid: mpegts.PID_VIDEO,
|
||||
}
|
||||
pesAudio, pesVideo := mpegts.CreatePESWriters()
|
||||
w.ts.WritePMTPacket(audioCodec, videoCodec)
|
||||
return m7s.PlayBlock(subscriber, w.ProcessADTS, w.ProcessAnnexB)
|
||||
}
|
||||
|
||||
func (w *HLSWriter) ProcessADTS(audio *pkg.ADTS) (err error) {
|
||||
return w.ts.WriteAudioFrame(audio, w.pesAudio)
|
||||
}
|
||||
|
||||
func (w *HLSWriter) ProcessAnnexB(video *pkg.AnnexB) (err error) {
|
||||
if w.TransformJob.Subscriber.VideoReader.Value.IDR {
|
||||
if err = w.checkFragment(video.GetTimestamp()); err != nil {
|
||||
return
|
||||
return m7s.PlayBlock(subscriber, func(audio *format.Mpeg2Audio) error {
|
||||
pesAudio.Pts = uint64(subscriber.AudioReader.AbsTime) * 90
|
||||
return pesAudio.WritePESPacket(audio.Memory, &w.ts.RecyclableMemory)
|
||||
}, func(video *mpegts.VideoFrame) (err error) {
|
||||
vr := w.TransformJob.Subscriber.VideoReader
|
||||
if vr.Value.IDR {
|
||||
if err = w.checkFragment(video.Timestamp); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return w.ts.WriteVideoFrame(video, w.pesVideo)
|
||||
pesVideo.IsKeyFrame = video.IDR
|
||||
pesVideo.Pts = uint64(vr.AbsTime+video.GetCTS32()) * 90
|
||||
pesVideo.Dts = uint64(vr.AbsTime) * 90
|
||||
return pesVideo.WritePESPacket(video.Memory, &w.ts.RecyclableMemory)
|
||||
})
|
||||
}
|
||||
|
||||
func (w *HLSWriter) checkFragment(ts time.Duration) (err error) {
|
||||
|
||||
Reference in New Issue
Block a user