mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-26 23:05:55 +08:00
first commit
This commit is contained in:
18
config.toml
Normal file
18
config.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[Plugins.HDL]
|
||||
ListenAddr = ":2020"
|
||||
[Plugins.Jessica]
|
||||
ListenAddr = ":8080"
|
||||
[Plugins.RTMP]
|
||||
ListenAddr = ":1985"
|
||||
[Plugins.GateWay]
|
||||
ListenAddr = ":80"
|
||||
#[Plugins.Cluster]
|
||||
#Master = "localhost:2019"
|
||||
#ListenAddr = ":2019"
|
||||
#
|
||||
#[Plugins.Auth]
|
||||
#Key="www.monibuca.com"
|
||||
#[Plugins.RecordFlv]
|
||||
#Path="./resouce"
|
||||
[Plugins.QoS]
|
||||
Suffix = ["high","medium","low"]
|
17
go.mod
Normal file
17
go.mod
Normal file
@@ -0,0 +1,17 @@
|
||||
module github.com/langhuihui/monibuca
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
|
||||
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478
|
||||
github.com/funny/utest v0.0.0-20161029064919-43870a374500 // indirect
|
||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect
|
||||
github.com/gobwas/pool v0.2.0 // indirect
|
||||
github.com/gobwas/ws v1.0.2
|
||||
github.com/quangngotan95/go-m3u8 v0.1.0
|
||||
github.com/shirou/gopsutil v2.19.12+incompatible
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 // indirect
|
||||
)
|
22
go.sum
Normal file
22
go.sum
Normal file
@@ -0,0 +1,22 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478 h1:Db9StoJ6RZN3YttC0Pm0I4Y5izITRYch3RMbT59BYN0=
|
||||
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478/go.mod h1:0j1+svBH8ABEIPdUP0AIg4qedsybnXGJBakCEw8cfoo=
|
||||
github.com/funny/utest v0.0.0-20161029064919-43870a374500 h1:Z0r1CZnoIWFB/Uiwh1BU5FYmuFe6L5NPi6XWQEmsTRg=
|
||||
github.com/funny/utest v0.0.0-20161029064919-43870a374500/go.mod h1:mUn39tBov9jKnTWV1RlOYoNzxdBFHiSzXWdY1FoNGGg=
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/quangngotan95/go-m3u8 v0.1.0 h1:8oseBjJn5IKHQKdRZwSNskkua3NLrRtlvXXtoVgBzMk=
|
||||
github.com/quangngotan95/go-m3u8 v0.1.0/go.mod h1:smzfWHlYpBATVNu1GapKLYiCtEo5JxridIgvvudZ+Wc=
|
||||
github.com/shirou/gopsutil v2.19.12+incompatible h1:WRstheAymn1WOPesh+24+bZKFkqrdCR8JOc77v4xV3Q=
|
||||
github.com/shirou/gopsutil v2.19.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
14
main.go
Normal file
14
main.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
_ "github.com/langhuihui/monibuca/plugins"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetOutput(os.Stdout)
|
||||
Run("config.toml")
|
||||
select {}
|
||||
}
|
307
monica/avformat/codec.go
Normal file
307
monica/avformat/codec.go
Normal file
@@ -0,0 +1,307 @@
|
||||
package avformat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
)
|
||||
|
||||
const (
|
||||
ADTS_HEADER_SIZE = 7
|
||||
)
|
||||
|
||||
// ISO/IEC 14496-15 11(16)/page
|
||||
//
|
||||
// Advanced Video Coding
|
||||
//
|
||||
|
||||
type AVCDecoderConfigurationRecord struct {
|
||||
ConfigurationVersion byte // 8 bits Version
|
||||
AVCProfileIndication byte // 8 bits
|
||||
ProfileCompatibility byte // 8 bits
|
||||
AVCLevelIndication byte // 8 bits
|
||||
Reserved1 byte // 6 bits
|
||||
LengthSizeMinusOne byte // 2 bits 非常重要,每个NALU包前面都(lengthSizeMinusOne & 3)+1个字节的NAL包长度描述
|
||||
Reserved2 byte // 3 bits
|
||||
NumOfSequenceParameterSets byte // 5 bits SPS 的个数,计算方法是 numOfSequenceParameterSets & 0x1F
|
||||
NumOfPictureParameterSets byte // 8 bits PPS 的个数
|
||||
|
||||
SequenceParameterSetLength uint16 // 16 byte SPS Length
|
||||
SequenceParameterSetNALUnit []byte // n byte SPS
|
||||
PictureParameterSetLength uint16 // 16 byte PPS Length
|
||||
PictureParameterSetNALUnit []byte // n byte PPS
|
||||
}
|
||||
|
||||
//func (p *AVCDecoderConfigurationRecord) Marshal(b []byte) (n int) {
|
||||
// b[0] = 1
|
||||
// b[1] = p.AVCProfileIndication
|
||||
// b[2] = p.ProfileCompatibility
|
||||
// b[3] = p.AVCLevelIndication
|
||||
// b[4] = p.LengthSizeMinusOne | 0xfc
|
||||
// b[5] = uint8(len(p.SPS)) | 0xe0
|
||||
// n += 6
|
||||
//
|
||||
// for _, sps := range p.SPS {
|
||||
// pio.PutU16BE(b[n:], uint16(len(sps)))
|
||||
// n += 2
|
||||
// copy(b[n:], sps)
|
||||
// n += len(sps)
|
||||
// }
|
||||
//
|
||||
// b[n] = uint8(len(p.PPS))
|
||||
// n++
|
||||
//
|
||||
// for _, pps := range p.PPS {
|
||||
// pio.PutU16BE(b[n:], uint16(len(pps)))
|
||||
// n += 2
|
||||
// copy(b[n:], pps)
|
||||
// n += len(pps)
|
||||
// }
|
||||
//
|
||||
// return
|
||||
//}
|
||||
var ErrDecconfInvalid = errors.New("decode error")
|
||||
|
||||
func (p *AVCDecoderConfigurationRecord) Unmarshal(b []byte) (n int, err error) {
|
||||
if len(b) < 7 {
|
||||
err = errors.New("not enough len")
|
||||
return
|
||||
}
|
||||
|
||||
p.AVCProfileIndication = b[1]
|
||||
p.ProfileCompatibility = b[2]
|
||||
p.AVCLevelIndication = b[3]
|
||||
p.LengthSizeMinusOne = b[4] & 0x03
|
||||
spscount := int(b[5] & 0x1f)
|
||||
n += 6
|
||||
var sps, pps [][]byte
|
||||
for i := 0; i < spscount; i++ {
|
||||
if len(b) < n+2 {
|
||||
err = ErrDecconfInvalid
|
||||
return
|
||||
}
|
||||
spslen := int(util.BigEndian.Uint16(b[n:]))
|
||||
n += 2
|
||||
|
||||
if len(b) < n+spslen {
|
||||
err = ErrDecconfInvalid
|
||||
return
|
||||
}
|
||||
sps = append(sps, b[n:n+spslen])
|
||||
n += spslen
|
||||
}
|
||||
p.SequenceParameterSetLength = uint16(len(sps[0]))
|
||||
p.SequenceParameterSetNALUnit = sps[0]
|
||||
if len(b) < n+1 {
|
||||
err = ErrDecconfInvalid
|
||||
return
|
||||
}
|
||||
ppscount := int(b[n])
|
||||
n++
|
||||
|
||||
for i := 0; i < ppscount; i++ {
|
||||
if len(b) < n+2 {
|
||||
err = ErrDecconfInvalid
|
||||
return
|
||||
}
|
||||
ppslen := int(util.BigEndian.Uint16(b[n:]))
|
||||
n += 2
|
||||
|
||||
if len(b) < n+ppslen {
|
||||
err = ErrDecconfInvalid
|
||||
return
|
||||
}
|
||||
pps = append(pps, b[n:n+ppslen])
|
||||
n += ppslen
|
||||
}
|
||||
p.PictureParameterSetLength = uint16(len(pps[0]))
|
||||
p.PictureParameterSetNALUnit = pps[0]
|
||||
return
|
||||
}
|
||||
|
||||
// ISO/IEC 14496-3 38(52)/page
|
||||
//
|
||||
// Audio
|
||||
//
|
||||
|
||||
type AudioSpecificConfig struct {
|
||||
AudioObjectType byte // 5 bits
|
||||
SamplingFrequencyIndex byte // 4 bits
|
||||
ChannelConfiguration byte // 4 bits
|
||||
GASpecificConfig
|
||||
}
|
||||
|
||||
type GASpecificConfig struct {
|
||||
FrameLengthFlag byte // 1 bit
|
||||
DependsOnCoreCoder byte // 1 bit
|
||||
ExtensionFlag byte // 1 bit
|
||||
}
|
||||
|
||||
//
|
||||
// AudioObjectTypes -> ISO/IEC 14496-3 43(57)/page
|
||||
//
|
||||
// 1 AAC MAIN ISO/IEC 14496-3 subpart 4
|
||||
// 2 AAC LC ISO/IEC 14496-3 subpart 4
|
||||
// 3 AAC SSR ISO/IEC 14496-3 subpart 4
|
||||
// 4 AAC LTP ISO/IEC 14496-3 subpart 4
|
||||
//
|
||||
//
|
||||
|
||||
// ISO/IEC 13838-7 20(25)/page
|
||||
//
|
||||
// Advanced Audio Coding
|
||||
//
|
||||
// AudioDataTransportStream
|
||||
type ADTS struct {
|
||||
ADTSFixedHeader
|
||||
ADTSVariableHeader
|
||||
}
|
||||
|
||||
// 28 bits
|
||||
type ADTSFixedHeader struct {
|
||||
SyncWord uint16 // 12 bits The bit string ‘1111 1111 1111’. See ISO/IEC 11172-3,subclause 2.4.2.3 (Table 8)
|
||||
ID byte // 1 bit MPEG identifier, set to ‘1’. See ISO/IEC 11172-3,subclause 2.4.2.3 (Table 8)
|
||||
Layer byte // 2 bits Indicates which layer is used. Set to ‘00’. See ISO/IEC 11172-3,subclause 2.4.2.3 (Table 8)
|
||||
ProtectionAbsent byte // 1 bit Indicates whether error_check() data is present or not. Same assyntax element ‘protection_bit’ in ISO/IEC 11172-3,subclause 2.4.1 and 2.4.2 (Table 8)
|
||||
Profile byte // 2 bits profile used. See clause 2 (Table 8)
|
||||
SamplingFrequencyIndex byte // 4 bits indicates the sampling frequency used according to the followingtable (Table 8)
|
||||
PrivateBit byte // 1 bit see ISO/IEC 11172-3, subclause 2.4.2.3 (Table 8)
|
||||
ChannelConfiguration byte // 3 bits indicates the channel configuration used. Ifchannel_configuration is greater than 0, the channelconfiguration is given in Table 42, see subclause 8.5.3.1. Ifchannel_configuration equals 0, the channel configuration is notspecified in the header and must be given by aprogram_config_element() following as first syntactic element inthe first raw_data_block() after the header (seesubclause 8.5.3.2), or by the implicit configuration (seesubclause 8.5.3.3) or must be known in the application (Table 8)
|
||||
OriginalCopy byte // 1 bit see ISO/IEC 11172-3, definition of data element copyright
|
||||
Home byte // 1 bit see ISO/IEC 11172-3, definition of data element original/copy
|
||||
}
|
||||
|
||||
// SyncWord, 同步头 总是0xFFF, all bits must be 1,代表着一个ADTS帧的开始
|
||||
// ID, MPEG Version: 0 for MPEG-4, 1 for MPEG-2
|
||||
// Layer, always: '00'
|
||||
// ProtectionAbsent, 表示是否误码校验
|
||||
// Profile, 表示使用哪个级别的AAC,有些芯片只支持AAC LC 。在MPEG-2 AAC中定义了3种.
|
||||
// SamplingFrequencyIndex, 表示使用的采样率下标,通过这个下标在 Sampling Frequencies[ ]数组中查找得知采样率的值
|
||||
// PrivateBit,
|
||||
// ChannelConfiguration, 表示声道数
|
||||
// OriginalCopy,
|
||||
// Home,
|
||||
|
||||
// Profile:
|
||||
//
|
||||
// 0: Main profile
|
||||
// 1: Low Complexity profile(LC)
|
||||
// 2: Scalable Sampling Rate profile(SSR)
|
||||
// 3: Reserved
|
||||
//
|
||||
var SamplingFrequencies = [...]int{96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350}
|
||||
|
||||
// Sampling Frequencies[]:
|
||||
//
|
||||
// 0: 96000 Hz
|
||||
// 1: 88200 Hz
|
||||
// 2: 64000 Hz
|
||||
// 3: 48000 Hz
|
||||
// 4: 44100 Hz
|
||||
// 5: 32000 Hz
|
||||
// 6: 24000 Hz
|
||||
// 7: 22050 Hz
|
||||
// 8: 16000 Hz
|
||||
// 9: 12000 Hz
|
||||
// 10: 11025 Hz
|
||||
// 11: 8000 Hz
|
||||
// 12: 7350 Hz
|
||||
// 13: Reserved
|
||||
// 14: Reserved
|
||||
// 15: frequency is written explictly
|
||||
//
|
||||
|
||||
// ChannelConfiguration:
|
||||
//
|
||||
// 0: Defined in AOT Specifc Config
|
||||
// 1: 1 channel: front-center
|
||||
// 2: 2 channels: front-left, front-right
|
||||
// 3: 3 channels: front-center, front-left, front-right
|
||||
// 4: 4 channels: front-center, front-left, front-right, back-center
|
||||
// 5: 5 channels: front-center, front-left, front-right, back-left, back-right
|
||||
// 6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel
|
||||
// 7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel
|
||||
// 8-15: Reserved
|
||||
//
|
||||
|
||||
// 28 bits
|
||||
type ADTSVariableHeader struct {
|
||||
CopyrightIdentificationBit byte // 1 bit One bit of the 72-bit copyright identification field (seecopyright_id above). The bits of this field are transmitted frame by frame; the first bit is indicated by the copyright_identification_start bit set to ‘1’. The field consists of an 8-bit copyright_identifier, followed by a 64-bit copyright_number.The copyright identifier is given by a Registration Authority as designated by SC29. The copyright_number is a value which identifies uniquely the copyrighted material. See ISO/IEC 13818-3, subclause 2.5.2.13 (Table 9)
|
||||
CopyrightIdentificationStart byte // 1 bit One bit to indicate that the copyright_identification_bit in this audio frame is the first bit of the 72-bit copyright identification. If no copyright identification is transmitted, this bit should be kept '0'.'0' no start of copyright identification in this audio frame '1' start of copyright identification in this audio frame See ISO/IEC 13818-3, subclause 2.5.2.13 (Table 9)
|
||||
AACFrameLength uint16 // 13 bits Length of the frame including headers and error_check in bytes(Table 9)
|
||||
ADTSBufferFullness uint16 // 11 bits state of the bit reservoir in the course of encoding the ADTS frame, up to and including the first raw_data_block() and the optionally following adts_raw_data_block_error_check(). It is transmitted as the number of available bits in the bit reservoir divided by NCC divided by 32 and truncated to an integer value (Table 9). A value of hexadecimal 7FF signals that the bitstream is a variable rate bitstream. In this case, buffer fullness is not applicable
|
||||
NumberOfRawDataBlockInFrame byte // 2 bits Number of raw_data_block()’s that are multiplexed in the adts_frame() is equal to number_of_raw_data_blocks_in_frame + 1. The minimum value is 0 indicating 1 raw_data_block()(Table 9)
|
||||
}
|
||||
|
||||
// CopyrightIdentificationBit,
|
||||
// CopyrightIdentificationStart,
|
||||
// AACFrameLength, 一个ADTS帧的长度包括ADTS头和raw data block.
|
||||
// ADTSBufferFullness, 0x7FF 说明是码率可变的码流.
|
||||
// NumberOfRawDataBlockInFrame, 表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧
|
||||
|
||||
// 所以说number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有一个AAC数据块并不是说没有。(一个AAC原始帧包含一段时间内1024个采样及相关数据)
|
||||
func ADTSToAudioSpecificConfig(data []byte) []byte {
|
||||
profile := ((data[2] & 0xc0) >> 6) + 1
|
||||
sampleRate := (data[2] & 0x3c) >> 2
|
||||
channel := ((data[2] & 0x1) << 2) | ((data[3] & 0xc0) >> 6)
|
||||
config1 := (profile << 3) | ((sampleRate & 0xe) >> 1)
|
||||
config2 := ((sampleRate & 0x1) << 7) | (channel << 3)
|
||||
return []byte{0xAF, 0x00, config1, config2}
|
||||
}
|
||||
func AudioSpecificConfigToADTS(asc AudioSpecificConfig, rawDataLength int) (adts ADTS, adtsByte []byte, err error) {
|
||||
if asc.ChannelConfiguration > 8 || asc.FrameLengthFlag > 13 {
|
||||
err = errors.New("Reserved field.")
|
||||
return
|
||||
}
|
||||
|
||||
// ADTSFixedHeader
|
||||
adts.SyncWord = 0xfff
|
||||
adts.ID = 0
|
||||
adts.Layer = 0
|
||||
adts.ProtectionAbsent = 1
|
||||
|
||||
// SyncWord(12) + ID(1) + Layer(2) + ProtectionAbsent(1)
|
||||
adtsByte = append(adtsByte, 0xff)
|
||||
adtsByte = append(adtsByte, 0xf1)
|
||||
|
||||
if asc.AudioObjectType >= 3 || asc.AudioObjectType == 0 {
|
||||
adts.Profile = 1
|
||||
} else {
|
||||
adts.Profile = asc.AudioObjectType - 1
|
||||
}
|
||||
|
||||
adts.SamplingFrequencyIndex = asc.SamplingFrequencyIndex
|
||||
adts.PrivateBit = 0
|
||||
adts.ChannelConfiguration = asc.ChannelConfiguration
|
||||
adts.OriginalCopy = 0
|
||||
adts.Home = 0
|
||||
|
||||
// Profile(2) + SamplingFrequencyIndex(4) + PrivateBit(1) + ChannelConfiguration(3)(取高1位)
|
||||
byte3 := uint8(adts.Profile<<6) + uint8(adts.SamplingFrequencyIndex<<2) + uint8(adts.PrivateBit<<1) + uint8((adts.ChannelConfiguration&0x7)>>2)
|
||||
adtsByte = append(adtsByte, byte3)
|
||||
|
||||
// ADTSVariableHeader
|
||||
adts.CopyrightIdentificationBit = 0
|
||||
adts.CopyrightIdentificationStart = 0
|
||||
adts.AACFrameLength = 7 + uint16(rawDataLength)
|
||||
adts.ADTSBufferFullness = 0x7ff
|
||||
adts.NumberOfRawDataBlockInFrame = 0
|
||||
|
||||
// ChannelConfiguration(3)(取低2位) + OriginalCopy(1) + Home(1) + CopyrightIdentificationBit(1) + CopyrightIdentificationStart(1) + AACFrameLength(13)(取高2位)
|
||||
byte4 := uint8((adts.ChannelConfiguration&0x3)<<6) + uint8((adts.AACFrameLength&0x1fff)>>11)
|
||||
adtsByte = append(adtsByte, byte4)
|
||||
|
||||
// AACFrameLength(13)
|
||||
// xx xxxxxxxx xxx
|
||||
// 取中间的部分
|
||||
byte5 := uint8(((adts.AACFrameLength & 0x1fff) >> 3) & 0x0ff)
|
||||
adtsByte = append(adtsByte, byte5)
|
||||
|
||||
// AACFrameLength(13)(取低3位) + ADTSBufferFullness(11)(取高5位)
|
||||
byte6 := uint8((adts.AACFrameLength&0x0007)<<5) + 0x1f
|
||||
adtsByte = append(adtsByte, byte6)
|
||||
|
||||
// ADTSBufferFullness(11)(取低6位) + NumberOfRawDataBlockInFrame(2)
|
||||
adtsByte = append(adtsByte, 0xfc)
|
||||
|
||||
return
|
||||
}
|
114
monica/avformat/flv.go
Normal file
114
monica/avformat/flv.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package avformat
|
||||
|
||||
import (
|
||||
"github.com/langhuihui/monibuca/monica/pool"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
// FLV Tag Type
|
||||
FLV_TAG_TYPE_AUDIO = 0x08
|
||||
FLV_TAG_TYPE_VIDEO = 0x09
|
||||
FLV_TAG_TYPE_SCRIPT = 0x12
|
||||
)
|
||||
|
||||
var (
|
||||
// 音频格式. 4 bit
|
||||
SoundFormat = map[byte]string{
|
||||
0: "Linear PCM, platform endian",
|
||||
1: "ADPCM",
|
||||
2: "MP3",
|
||||
3: "Linear PCM, little endian",
|
||||
4: "Nellymoser 16kHz mono",
|
||||
5: "Nellymoser 8kHz mono",
|
||||
6: "Nellymoser",
|
||||
7: "G.711 A-law logarithmic PCM",
|
||||
8: "G.711 mu-law logarithmic PCM",
|
||||
9: "reserved",
|
||||
10: "AAC",
|
||||
11: "Speex",
|
||||
14: "MP3 8Khz",
|
||||
15: "Device-specific sound"}
|
||||
|
||||
// 采样频率. 2 bit
|
||||
SoundRate = map[byte]int{
|
||||
0: 5500,
|
||||
1: 11000,
|
||||
2: 22000,
|
||||
3: 44000}
|
||||
|
||||
// 量化精度. 1 bit
|
||||
SoundSize = map[byte]string{
|
||||
0: "8Bit",
|
||||
1: "16Bit"}
|
||||
|
||||
// 音频类型. 1bit
|
||||
SoundType = map[byte]string{
|
||||
0: "Mono",
|
||||
1: "Stereo"}
|
||||
|
||||
// 视频帧类型. 4bit
|
||||
FrameType = map[byte]string{
|
||||
1: "keyframe (for AVC, a seekable frame)",
|
||||
2: "inter frame (for AVC, a non-seekable frame)",
|
||||
3: "disposable inter frame (H.263 only)",
|
||||
4: "generated keyframe (reserved for server use only)",
|
||||
5: "video info/command frame"}
|
||||
|
||||
// 视频编码类型. 4bit
|
||||
CodecID = map[byte]string{
|
||||
1: "JPEG (currently unused)",
|
||||
2: "Sorenson H.263",
|
||||
3: "Screen video",
|
||||
4: "On2 VP6",
|
||||
5: "On2 VP6 with alpha channel",
|
||||
6: "Screen video version 2",
|
||||
7: "AVC",
|
||||
12: "H265"}
|
||||
)
|
||||
|
||||
var FLVHeader = []byte{0x46, 0x4c, 0x56, 0x01, 0x05, 0, 0, 0, 9, 0, 0, 0, 0}
|
||||
|
||||
func WriteFLVTag(w io.Writer, tag *pool.SendPacket) (err error) {
|
||||
head := pool.GetSlice(11)
|
||||
defer pool.RecycleSlice(head)
|
||||
tail := pool.GetSlice(4)
|
||||
defer pool.RecycleSlice(tail)
|
||||
head[0] = tag.Packet.Type
|
||||
dataSize := uint32(len(tag.Packet.Payload))
|
||||
util.BigEndian.PutUint32(tail, dataSize+11)
|
||||
util.BigEndian.PutUint24(head[1:], dataSize)
|
||||
util.BigEndian.PutUint24(head[4:], tag.Timestamp)
|
||||
util.BigEndian.PutUint32(head[7:], 0)
|
||||
if _, err = w.Write(head); err != nil {
|
||||
return
|
||||
}
|
||||
// Tag Data
|
||||
if _, err = w.Write(tag.Packet.Payload); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = w.Write(tail); err != nil { // PreviousTagSizeN(4)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
func ReadFLVTag(r io.Reader) (tag *pool.AVPacket, err error) {
|
||||
head := pool.GetSlice(11)
|
||||
defer pool.RecycleSlice(head)
|
||||
if _, err = r.Read(head); err != nil {
|
||||
return
|
||||
}
|
||||
av := pool.NewAVPacket(head[0])
|
||||
dataSize := util.BigEndian.Uint24(head[1:])
|
||||
av.Timestamp = util.BigEndian.Uint24(head[4:])
|
||||
body := pool.GetSlice(int(dataSize))
|
||||
defer pool.RecycleSlice(body)
|
||||
if _, err = r.Read(body); err == nil {
|
||||
av.Payload = body
|
||||
t := pool.GetSlice(4)
|
||||
_, err = r.Read(t)
|
||||
pool.RecycleSlice(t)
|
||||
}
|
||||
return
|
||||
}
|
144
monica/avformat/h264.go
Normal file
144
monica/avformat/h264.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package avformat
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Start Code + NAL Unit -> NALU Header + NALU Body
|
||||
// RTP Packet -> NALU Header + NALU Body
|
||||
|
||||
// NALU Body -> Slice Header + Slice data
|
||||
// Slice data -> flags + Macroblock layer1 + Macroblock layer2 + ...
|
||||
// Macroblock layer1 -> mb_type + PCM Data
|
||||
// Macroblock layer2 -> mb_type + Sub_mb_pred or mb_pred + Residual Data
|
||||
// Residual Data ->
|
||||
|
||||
const (
|
||||
// NALU Type
|
||||
NALU_Unspecified = 0
|
||||
NALU_Non_IDR_Picture = 1
|
||||
NALU_Data_Partition_A = 2
|
||||
NALU_Data_Partition_B = 3
|
||||
NALU_Data_Partition_C = 4
|
||||
NALU_IDR_Picture = 5
|
||||
NALU_SEI = 6
|
||||
NALU_SPS = 7
|
||||
NALU_PPS = 8
|
||||
NALU_Access_Unit_Delimiter = 9
|
||||
NALU_Sequence_End = 10
|
||||
NALU_Stream_End = 11
|
||||
NALU_Filler_Data = 12
|
||||
NALU_SPS_Extension = 13
|
||||
NALU_Prefix = 14
|
||||
NALU_SPS_Subset = 15
|
||||
NALU_DPS = 16
|
||||
NALU_Reserved1 = 17
|
||||
NALU_Reserved2 = 18
|
||||
NALU_Not_Auxiliary_Coded = 19
|
||||
NALU_Coded_Slice_Extension = 20
|
||||
NALU_Reserved3 = 21
|
||||
NALU_Reserved4 = 22
|
||||
NALU_Reserved5 = 23
|
||||
NALU_NotReserved = 24
|
||||
// 24 - 31 NALU_NotReserved
|
||||
)
|
||||
|
||||
var (
|
||||
NALU_AUD_BYTE = []byte{0x00, 0x00, 0x00, 0x01, 0x09, 0xF0}
|
||||
NALU_Delimiter1 = []byte{0x00, 0x00, 0x01}
|
||||
NALU_Delimiter2 = []byte{0x00, 0x00, 0x00, 0x01}
|
||||
RTMP_AVC_HEAD = []byte{0x17, 0x00, 0x00, 0x00, 0x00, 0x01, 0x42, 0x00, 0x1E, 0xFF}
|
||||
RTMP_KEYFRAME_HEAD = []byte{0x17, 0x01, 0x00, 0x00, 0x00}
|
||||
RTMP_NORMALFRAME_HEAD = []byte{0x27, 0x01, 0x00, 0x00, 0x00}
|
||||
)
|
||||
var NALU_SEI_BYTE []byte
|
||||
|
||||
// H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)
|
||||
// NAL - Network Abstract Layer
|
||||
// raw byte sequence payload (RBSP) 原始字节序列载荷
|
||||
|
||||
type H264 struct {
|
||||
}
|
||||
|
||||
type NALUnit struct {
|
||||
NALUHeader
|
||||
RBSP
|
||||
}
|
||||
|
||||
type NALUHeader struct {
|
||||
forbidden_zero_bit byte // 1 bit 0
|
||||
nal_ref_idc byte // 2 bits nal_unit_type等于6,9,10,11或12的NAL单元其nal_ref_idc都应等于 0
|
||||
nal_uint_type byte // 5 bits 包含在 NAL 单元中的 RBSP 数据结构的类型
|
||||
}
|
||||
|
||||
type RBSP interface {
|
||||
}
|
||||
|
||||
/*
|
||||
0 Unspecified non-VCL
|
||||
1 Coded slice of a non-IDR picture VCL
|
||||
2 Coded slice data partition A VCL
|
||||
3 Coded slice data partition B VCL
|
||||
4 Coded slice data partition C VCL
|
||||
5 Coded slice of an IDR picture VCL
|
||||
6 Supplemental enhancement information (SEI) non-VCL
|
||||
7 Sequence parameter set non-VCL
|
||||
8 Picture parameter set non-VCL
|
||||
9 Access unit delimiter non-VCL
|
||||
10 End of sequence non-VCL
|
||||
11 End of stream non-VCL
|
||||
12 Filler data non-VCL
|
||||
13 Sequence parameter set extension non-VCL
|
||||
14 Prefix NAL unit non-VCL
|
||||
15 Subset sequence parameter set non-VCL
|
||||
16 Depth parameter set non-VCL
|
||||
17..18 Reserved non-VCL
|
||||
19 Coded slice of an auxiliary coded picture without partitioning non-VCL
|
||||
20 Coded slice extension non-VCL
|
||||
21 Coded slice extension for depth view components non-VCL
|
||||
22..23 Reserved non-VCL
|
||||
24..31 Unspecified non-VCL
|
||||
|
||||
0:未规定
|
||||
1:非IDR图像中不采用数据划分的片段
|
||||
2:非IDR图像中A类数据划分片段
|
||||
3:非IDR图像中B类数据划分片段
|
||||
4:非IDR图像中C类数据划分片段
|
||||
5:IDR图像的片段
|
||||
6:补充增强信息(SEI)
|
||||
7:序列参数集(SPS)
|
||||
8:图像参数集(PPS)
|
||||
9:分割符
|
||||
10:序列结束符
|
||||
11:流结束符
|
||||
12:填充数据
|
||||
13:序列参数集扩展
|
||||
14:带前缀的NAL单元
|
||||
15:子序列参数集
|
||||
16 – 18:保留
|
||||
19:不采用数据划分的辅助编码图像片段
|
||||
20:编码片段扩展
|
||||
21 – 23:保留
|
||||
24 – 31:未规定
|
||||
|
||||
nal_unit_type NAL类型 nal_reference_bit
|
||||
0 未使用 0
|
||||
1 非IDR的片 此片属于参考帧,则不等于0,不属于参考帧,则等与0
|
||||
2 片数据A分区 同上
|
||||
3 片数据B分区 同上
|
||||
4 片数据C分区 同上
|
||||
5 IDR图像的片 5
|
||||
6 补充增强信息单元(SEI) 0
|
||||
7 序列参数集 非0
|
||||
8 图像参数集 非0
|
||||
9 分界符 0
|
||||
10 序列结束 0
|
||||
11 码流结束 0
|
||||
12 填充 0
|
||||
13..23 保留 0
|
||||
24..31 不保留 0
|
||||
*/
|
||||
|
||||
func ReadPPS(w io.Writer) {
|
||||
|
||||
}
|
2421
monica/avformat/mp4.go
Normal file
2421
monica/avformat/mp4.go
Normal file
File diff suppressed because it is too large
Load Diff
577
monica/avformat/mpegts/mpegts.go
Normal file
577
monica/avformat/mpegts/mpegts.go
Normal file
@@ -0,0 +1,577 @@
|
||||
package mpegts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
//"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
|
||||
// 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_H264 = 0x1B
|
||||
STREAM_TYPE_AAC = 0x0F
|
||||
|
||||
// 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 {
|
||||
firstTsPkt *MpegTsPacket // 每一帧的第一个TS包
|
||||
patPkt *MpegTsPacket // 装载PAT的TS包
|
||||
pmtPkt *MpegTsPacket // 装载PMT的TS包
|
||||
pat *MpegTsPAT // PAT表信息
|
||||
pmt *MpegTsPMT // PMT表信息
|
||||
closed bool //是否已经关闭
|
||||
TsPesPktChan chan *MpegTsPesStream // TS + PES Packet Channel,将封装的每一帧ES数据,通过channel来传输
|
||||
}
|
||||
|
||||
func NewMpegTsStream(bufferLength int) (ts *MpegTsStream) {
|
||||
ts = new(MpegTsStream)
|
||||
ts.firstTsPkt = new(MpegTsPacket)
|
||||
ts.patPkt = new(MpegTsPacket)
|
||||
ts.pmtPkt = new(MpegTsPacket)
|
||||
ts.pat = new(MpegTsPAT)
|
||||
ts.pmt = new(MpegTsPMT)
|
||||
ts.TsPesPktChan = make(chan *MpegTsPesStream, bufferLength)
|
||||
return
|
||||
}
|
||||
|
||||
// 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 |
|
||||
if (h&0xff000000)>>24 != 0x47 {
|
||||
err = errors.New("mpegts header sync error!")
|
||||
return
|
||||
}
|
||||
|
||||
// | 1111 1111 | 0000 0000 | 0000 0000 | 0000 0000 |
|
||||
header.SyncByte = byte((h & 0xff000000) >> 24)
|
||||
|
||||
// | 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
|
||||
pat, err := ReadPAT(pr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.pat = &pat
|
||||
s.patPkt = packet
|
||||
}
|
||||
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
|
||||
pmt, err := ReadPMT(pr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// send pmt
|
||||
s.pmt = &pmt
|
||||
s.pmtPkt = packet
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *MpegTsStream) Feed(ts io.Reader) error {
|
||||
var frame int64
|
||||
var tsPktArr []MpegTsPacket
|
||||
for {
|
||||
packet, err := ReadTsPacket(ts)
|
||||
if err == io.EOF {
|
||||
// 文件结尾 把最后面的数据发出去
|
||||
pesPkt, err := TsToPES(tsPktArr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.TsPesPktChan <- &MpegTsPesStream{
|
||||
TsPkt: *s.firstTsPkt,
|
||||
PesPkt: pesPkt,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pr := bytes.NewReader(packet.Payload)
|
||||
err = s.readPAT(&packet, pr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.readPMT(&packet, pr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 在读取PMT中已经将所有的音视频PES的索引信息全部保存了起来
|
||||
// 接着读取所有TS包里面的PID,找出PID==elementaryPID的TS包,就是音视频数据
|
||||
for _, v := range s.pmt.Stream {
|
||||
if v.ElementaryPID == packet.Header.Pid {
|
||||
if packet.Header.PayloadUnitStartIndicator == 1 {
|
||||
if frame != 0 {
|
||||
pesPkt, err := TsToPES(tsPktArr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.TsPesPktChan <- &MpegTsPesStream{
|
||||
TsPkt: *s.firstTsPkt,
|
||||
PesPkt: pesPkt,
|
||||
}
|
||||
|
||||
tsPktArr = nil
|
||||
}
|
||||
s.firstTsPkt = &packet
|
||||
frame++
|
||||
}
|
||||
tsPktArr = append(tsPktArr, packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
520
monica/avformat/mpegts/mpegts.md
Normal file
520
monica/avformat/mpegts/mpegts.md
Normal file
@@ -0,0 +1,520 @@
|
||||
#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)
|
60
monica/avformat/mpegts/mpegts_crc32.go
Normal file
60
monica/avformat/mpegts/mpegts_crc32.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package mpegts
|
||||
|
||||
// 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
|
||||
}
|
229
monica/avformat/mpegts/mpegts_pat.go
Normal file
229
monica/avformat/mpegts/mpegts_pat.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package mpegts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"io"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
753
monica/avformat/mpegts/mpegts_pes.go
Normal file
753
monica/avformat/mpegts/mpegts_pes.go
Normal file
@@ -0,0 +1,753 @@
|
||||
package mpegts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/langhuihui/monibuca/monica/avformat"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// 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 []byte
|
||||
}
|
||||
|
||||
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(ioutil.Discard, lrHeader, int64(16)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// packFieldLength(8)
|
||||
if header.PackHeaderFieldFlag != 0 {
|
||||
if _, err = io.CopyN(ioutil.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(ioutil.Discard, lrHeader, int64(2)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 01 + p_STD_bufferScale(1) + p_STD_bufferSize(13)
|
||||
if header.PSTDBufferFlag != 0 {
|
||||
if _, err = io.CopyN(ioutil.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(ioutil.Discard, lrHeader, int64(2)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 把剩下的头的数据消耗掉
|
||||
if lrHeader.N > 0 {
|
||||
if _, err = io.CopyN(ioutil.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
|
||||
}
|
||||
|
||||
func WritePESPacket(w io.Writer, frame *MpegtsPESFrame, packet MpegTsPESPacket) (err error) {
|
||||
var tsPkts []byte
|
||||
if tsPkts, err = PESToTs(frame, packet); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// bw.Bytes == PES Packet
|
||||
if _, err = w.Write(tsPkts); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func IowWritePESPacket(w io.Writer, tsHeader MpegTsHeader, packet MpegTsPESPacket) (err error) {
|
||||
if packet.Header.PacketStartCodePrefix != 0x000001 {
|
||||
return errors.New("packetStartCodePrefix != 0x000001")
|
||||
}
|
||||
|
||||
bw := &bytes.Buffer{}
|
||||
|
||||
// TODO:如果头长度大于65536,字段会为0,是否要改?
|
||||
_, err = WritePESHeader(bw, packet.Header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
PESPacket := &util.IOVec{}
|
||||
PESPacket.Append(bw.Bytes()) // header
|
||||
PESPacket.Append(packet.Payload) // packet
|
||||
|
||||
// 用IOVecWriter来写PES包,IOVecWriter实现了Write方法.
|
||||
// 因为通常在将一帧PES封装成TS包(188字节)的时候,一般情况下一帧PES字节数会大于188,并且分多次封装.
|
||||
// 例如这一帧PES字节数为189,那么在封装第二个TS包的时候就只会封装1字节,会导致多次写操作,降低性能.
|
||||
// 因此将所有的字节数,都写进缓冲中去,然后用系统调用syscall来写入.
|
||||
iow := util.NewIOVecWriter(w)
|
||||
|
||||
var isKeyFrame bool
|
||||
var headerLength int
|
||||
|
||||
isKeyFrame = CheckPESPacketIsKeyFrame(packet)
|
||||
|
||||
// 写一帧PES
|
||||
// 如果是I帧,会有pcr,所以会有调整字段AF.
|
||||
// 如果当前包字节不满188字节,会需要填充0xff,所以会有调整字段AF.
|
||||
for i := 0; PESPacket.Length > 0; i++ {
|
||||
|
||||
header := MpegTsHeader{
|
||||
SyncByte: 0x47,
|
||||
Pid: tsHeader.Pid,
|
||||
AdaptionFieldControl: 1,
|
||||
ContinuityCounter: byte(i % 15),
|
||||
}
|
||||
|
||||
// 每一帧开头
|
||||
if i == 0 {
|
||||
header.PayloadUnitStartIndicator = 1
|
||||
}
|
||||
|
||||
// I帧
|
||||
if isKeyFrame {
|
||||
header.AdaptionFieldControl = 0x03
|
||||
header.AdaptationFieldLength = 7
|
||||
header.PCRFlag = 1
|
||||
header.RandomAccessIndicator = tsHeader.RandomAccessIndicator
|
||||
header.ProgramClockReferenceBase = tsHeader.ProgramClockReferenceBase
|
||||
header.ProgramClockReferenceExtension = tsHeader.ProgramClockReferenceExtension
|
||||
|
||||
isKeyFrame = false
|
||||
}
|
||||
|
||||
// 这个包大小,会在每一次PESPacket.WriteTo中慢慢减少.
|
||||
packetLength := PESPacket.Length
|
||||
|
||||
// 包不满188字节
|
||||
if packetLength < TS_PACKET_SIZE-4 {
|
||||
|
||||
if header.AdaptionFieldControl >= 2 {
|
||||
header.AdaptationFieldLength = uint8(TS_PACKET_SIZE - 4 - 1 - packetLength - 7)
|
||||
} else {
|
||||
header.AdaptionFieldControl = 0x03
|
||||
header.AdaptationFieldLength = uint8(TS_PACKET_SIZE - 4 - 1 - packetLength)
|
||||
}
|
||||
|
||||
headerLength, err = WriteTsHeader(iow, header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stuffingLength := int(header.AdaptationFieldLength - 1)
|
||||
if _, err = iow.Write(util.GetFillBytes(0xff, stuffingLength)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
headerLength += stuffingLength
|
||||
|
||||
} else {
|
||||
headerLength, err = WriteTsHeader(iow, header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if headerLength, err = writeTsHeader(iow, header, packetLength); err != nil {
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
payloadLength := 188 - headerLength
|
||||
|
||||
// 写PES负载
|
||||
if _, err = PESPacket.WriteTo(iow, payloadLength); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
iow.Flush()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func CheckPESPacketIsKeyFrame(packet MpegTsPESPacket) bool {
|
||||
|
||||
nalus := bytes.SplitN(packet.Payload, avformat.NALU_Delimiter1, -1)
|
||||
|
||||
for _, v := range nalus {
|
||||
if v[0]&0x1f == avformat.NALU_IDR_Picture {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func TsToPES(tsPkts []MpegTsPacket) (pesPkt MpegTsPESPacket, err error) {
|
||||
var index int
|
||||
|
||||
for i := 0; i < len(tsPkts); i++ {
|
||||
if tsPkts[i].Header.SyncByte != 0x47 {
|
||||
err = errors.New("mpegts header sync error!")
|
||||
return
|
||||
}
|
||||
|
||||
if tsPkts[i].Header.PayloadUnitStartIndicator == 1 {
|
||||
index++
|
||||
|
||||
// 一个PES包里面只可能包含一个PayloadUnitStartIndicator=1的TS包.
|
||||
if index >= 2 {
|
||||
err = errors.New("TsToPES error PayloadUnitStartIndicator >= 2")
|
||||
return
|
||||
}
|
||||
|
||||
r := bytes.NewReader(tsPkts[i].Payload)
|
||||
lr := &io.LimitedReader{R: r, N: int64(len(tsPkts[i].Payload))}
|
||||
|
||||
// TS Packet PES Header Start Index
|
||||
hBegin := lr.N
|
||||
|
||||
// PES Header
|
||||
pesPkt.Header, err = ReadPESHeader(lr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TS Packet PES Header End Index
|
||||
hEnd := lr.N
|
||||
|
||||
pesHeaderLength := hBegin - hEnd
|
||||
|
||||
if pesHeaderLength > 0 && pesHeaderLength <= hBegin {
|
||||
pesPkt.Payload = append(pesPkt.Payload, tsPkts[i].Payload[pesHeaderLength:]...)
|
||||
}
|
||||
}
|
||||
|
||||
if tsPkts[i].Header.PayloadUnitStartIndicator == 0 {
|
||||
// MpegTsPacket Header 已经包含了自适应字段在里面,所以MpegTsPacket Payload直接就是PES Pyaload
|
||||
pesPkt.Payload = append(pesPkt.Payload, tsPkts[i].Payload...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func PESToTs(frame *MpegtsPESFrame, packet MpegTsPESPacket) (tsPkts []byte, err error) {
|
||||
if packet.Header.PacketStartCodePrefix != 0x000001 {
|
||||
err = errors.New("packetStartCodePrefix != 0x000001")
|
||||
return
|
||||
}
|
||||
|
||||
bwPESPkt := &bytes.Buffer{}
|
||||
_, err = WritePESHeader(bwPESPkt, packet.Header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = bwPESPkt.Write(packet.Payload); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var tsHeaderLength int
|
||||
for i := 0; bwPESPkt.Len() > 0; i++ {
|
||||
bwTsHeader := &bytes.Buffer{}
|
||||
|
||||
tsHeader := 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
|
||||
}
|
||||
}
|
||||
|
||||
pesPktLength := bwPESPkt.Len()
|
||||
|
||||
// 每一帧的结尾,当不满足188个字节的时候,包含调整字段
|
||||
if pesPktLength < TS_PACKET_SIZE-4 {
|
||||
var tsStuffingLength uint8
|
||||
|
||||
tsHeader.AdaptionFieldControl = 0x03
|
||||
tsHeader.AdaptationFieldLength = uint8(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 = WriteTsHeader(bwTsHeader, tsHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if tsStuffingLength > 0 {
|
||||
if _, err = bwTsHeader.Write(util.GetFillBytes(0xff, int(tsStuffingLength))); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tsHeaderLength += int(tsStuffingLength)
|
||||
} else {
|
||||
tsHeaderLength, err = WriteTsHeader(bwTsHeader, tsHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tsPayloadLength := TS_PACKET_SIZE - tsHeaderLength
|
||||
|
||||
//fmt.Println("tsPayloadLength :", tsPayloadLength)
|
||||
|
||||
// 这里不断的减少PES包
|
||||
tsHeaderByte := bwTsHeader.Bytes()
|
||||
tsPayloadByte := bwPESPkt.Next(tsPayloadLength)
|
||||
|
||||
// tmp := tsHeaderByte[3] << 2
|
||||
// tmp = tmp >> 6
|
||||
// if tmp == 2 {
|
||||
// fmt.Println("fuck you mother.")
|
||||
// }
|
||||
|
||||
tsPktByte := append(tsHeaderByte, tsPayloadByte...)
|
||||
|
||||
if len(tsPktByte) != TS_PACKET_SIZE {
|
||||
err = errors.New(fmt.Sprintf("%s, packet size=%d", "TS_PACKET_SIZE != 188,", len(tsPktByte)))
|
||||
return
|
||||
}
|
||||
|
||||
tsPkts = append(tsPkts, tsPktByte...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
383
monica/avformat/mpegts/mpegts_pmt.go
Normal file
383
monica/avformat/mpegts/mpegts_pmt.go
Normal file
@@ -0,0 +1,383 @@
|
||||
package mpegts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ios13818-1-CN.pdf 46(60)-153(167)/page
|
||||
//
|
||||
// PMT
|
||||
//
|
||||
|
||||
var DefaultPMTPacket = []byte{
|
||||
// TS Header
|
||||
0x47, 0x41, 0x00, 0x10,
|
||||
|
||||
// Pointer Field
|
||||
0x00,
|
||||
|
||||
// PSI
|
||||
0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00,
|
||||
|
||||
// PMT
|
||||
0xe1, 0x01,
|
||||
0xf0, 0x00,
|
||||
|
||||
// H264
|
||||
0x1b, 0xe1, 0x01, 0xf0, 0x00,
|
||||
|
||||
// AAC
|
||||
0x0f, 0xe1, 0x02, 0xf0, 0x00,
|
||||
|
||||
//0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
// CRC for not audio
|
||||
//0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
// CRC for AAC
|
||||
0x9e, 0x28, 0xc6, 0xdd,
|
||||
|
||||
// CRC for MP3
|
||||
// 0x4e, 0x59, 0x3d, 0x1e,
|
||||
|
||||
// Stuffing 157 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,
|
||||
}
|
||||
|
||||
// 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 WriteDefaultPMTPacket(w io.Writer) (err error) {
|
||||
_, err = w.Write(DefaultPMTPacket)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
231
monica/avformat/mpegts/mpegts_psi.go
Normal file
231
monica/avformat/mpegts/mpegts_psi.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package mpegts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
//
|
||||
// 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(ioutil.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
|
||||
}
|
||||
|
||||
cw := &util.Crc32Writer{W: w, Crc32: 0xffffffff}
|
||||
|
||||
// table id(8)
|
||||
if err = util.WriteUint8ToByte(cw, tableId); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// sectionSyntaxIndicator(1) + zero(1) + reserved1(2) + sectionLength(12)
|
||||
// sectionLength 前两个字节固定为00
|
||||
// 1 0 11 sectionLength
|
||||
if err = util.WriteUint16ToByte(cw, sectionSyntaxIndicatorAndSectionLength, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// PAT TransportStreamID(16) or PMT ProgramNumber(16)
|
||||
if err = util.WriteUint16ToByte(cw, transportStreamIdOrProgramNumber, true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// reserved2(2) + versionNumber(5) + currentNextIndicator(1)
|
||||
// 0x3 << 6 -> 1100 0000
|
||||
// 0x3 << 6 | 1 -> 1100 0001
|
||||
if err = util.WriteUint8ToByte(cw, versionNumberAndCurrentNextIndicator); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// sectionNumber(8)
|
||||
if err = util.WriteUint8ToByte(cw, sectionNumber); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// lastSectionNumber(8)
|
||||
if err = util.WriteUint8ToByte(cw, lastSectionNumber); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// data
|
||||
if _, err = cw.Write(data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// crc32
|
||||
crc32 := util.BigLittleSwap(uint(cw.Crc32))
|
||||
if err = util.WriteUint32ToByte(cw, uint32(crc32), true); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
214
monica/avformat/sps.go
Normal file
214
monica/avformat/sps.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package avformat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/langhuihui/monibuca/monica/util/bits"
|
||||
)
|
||||
|
||||
type SPSInfo struct {
|
||||
ProfileIdc uint
|
||||
LevelIdc uint
|
||||
|
||||
MbWidth uint
|
||||
MbHeight uint
|
||||
|
||||
CropLeft uint
|
||||
CropRight uint
|
||||
CropTop uint
|
||||
CropBottom uint
|
||||
|
||||
Width uint
|
||||
Height uint
|
||||
}
|
||||
|
||||
func ParseSPS(data []byte) (self SPSInfo, err error) {
|
||||
r := &bits.GolombBitReader{R: bytes.NewReader(data)}
|
||||
|
||||
if _, err = r.ReadBits(8); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if self.ProfileIdc, err = r.ReadBits(8); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// constraint_set0_flag-constraint_set6_flag,reserved_zero_2bits
|
||||
if _, err = r.ReadBits(8); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// level_idc
|
||||
if self.LevelIdc, err = r.ReadBits(8); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// seq_parameter_set_id
|
||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if self.ProfileIdc == 100 || self.ProfileIdc == 110 ||
|
||||
self.ProfileIdc == 122 || self.ProfileIdc == 244 ||
|
||||
self.ProfileIdc == 44 || self.ProfileIdc == 83 ||
|
||||
self.ProfileIdc == 86 || self.ProfileIdc == 118 {
|
||||
|
||||
var chroma_format_idc uint
|
||||
if chroma_format_idc, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if chroma_format_idc == 3 {
|
||||
// residual_colour_transform_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// bit_depth_luma_minus8
|
||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
// bit_depth_chroma_minus8
|
||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
// qpprime_y_zero_transform_bypass_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var seq_scaling_matrix_present_flag uint
|
||||
if seq_scaling_matrix_present_flag, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if seq_scaling_matrix_present_flag != 0 {
|
||||
for i := 0; i < 8; i++ {
|
||||
var seq_scaling_list_present_flag uint
|
||||
if seq_scaling_list_present_flag, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
if seq_scaling_list_present_flag != 0 {
|
||||
var sizeOfScalingList uint
|
||||
if i < 6 {
|
||||
sizeOfScalingList = 16
|
||||
} else {
|
||||
sizeOfScalingList = 64
|
||||
}
|
||||
lastScale := uint(8)
|
||||
nextScale := uint(8)
|
||||
for j := uint(0); j < sizeOfScalingList; j++ {
|
||||
if nextScale != 0 {
|
||||
var delta_scale uint
|
||||
if delta_scale, err = r.ReadSE(); err != nil {
|
||||
return
|
||||
}
|
||||
nextScale = (lastScale + delta_scale + 256) % 256
|
||||
}
|
||||
if nextScale != 0 {
|
||||
lastScale = nextScale
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log2_max_frame_num_minus4
|
||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var pic_order_cnt_type uint
|
||||
if pic_order_cnt_type, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
if pic_order_cnt_type == 0 {
|
||||
// log2_max_pic_order_cnt_lsb_minus4
|
||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
} else if pic_order_cnt_type == 1 {
|
||||
// delta_pic_order_always_zero_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
// offset_for_non_ref_pic
|
||||
if _, err = r.ReadSE(); err != nil {
|
||||
return
|
||||
}
|
||||
// offset_for_top_to_bottom_field
|
||||
if _, err = r.ReadSE(); err != nil {
|
||||
return
|
||||
}
|
||||
var num_ref_frames_in_pic_order_cnt_cycle uint
|
||||
if num_ref_frames_in_pic_order_cnt_cycle, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
for i := uint(0); i < num_ref_frames_in_pic_order_cnt_cycle; i++ {
|
||||
if _, err = r.ReadSE(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// max_num_ref_frames
|
||||
if _, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// gaps_in_frame_num_value_allowed_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if self.MbWidth, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
self.MbWidth++
|
||||
|
||||
if self.MbHeight, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
self.MbHeight++
|
||||
|
||||
var frame_mbs_only_flag uint
|
||||
if frame_mbs_only_flag, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
if frame_mbs_only_flag == 0 {
|
||||
// mb_adaptive_frame_field_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// direct_8x8_inference_flag
|
||||
if _, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var frame_cropping_flag uint
|
||||
if frame_cropping_flag, err = r.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
if frame_cropping_flag != 0 {
|
||||
if self.CropLeft, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
if self.CropRight, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
if self.CropTop, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
if self.CropBottom, err = r.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.Width = (self.MbWidth * 16) - self.CropLeft*2 - self.CropRight*2
|
||||
self.Height = ((2 - frame_mbs_only_flag) * self.MbHeight * 16) - self.CropTop*2 - self.CropBottom*2
|
||||
|
||||
return
|
||||
}
|
34
monica/config.go
Normal file
34
monica/config.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package monica
|
||||
|
||||
import "log"
|
||||
|
||||
const (
|
||||
PLUGIN_SUBSCRIBER = 1
|
||||
PLUGIN_PUBLISHER = 1 << 1
|
||||
PLUGIN_HOOK = 1 << 2
|
||||
)
|
||||
|
||||
var (
|
||||
cg = &Config{Plugins: make(map[string]interface{})}
|
||||
plugins = make(map[string]*PluginConfig)
|
||||
)
|
||||
|
||||
type PluginConfig struct {
|
||||
Name string //插件名称
|
||||
Type byte //类型
|
||||
Config interface{} //插件配置
|
||||
Run func()
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Plugins map[string]interface{}
|
||||
}
|
||||
|
||||
func InstallPlugin(opt *PluginConfig) {
|
||||
log.Printf("install plugin %s", opt.Name)
|
||||
plugins[opt.Name] = opt
|
||||
}
|
||||
|
||||
type ListenerConfig struct {
|
||||
ListenAddr string
|
||||
}
|
56
monica/hook.go
Normal file
56
monica/hook.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package monica
|
||||
|
||||
var AuthHooks = make(AuthHook, 0)
|
||||
|
||||
type AuthHook []func(string) error
|
||||
|
||||
func (h AuthHook) AddHook(hook func(string) error) {
|
||||
AuthHooks = append(h, hook)
|
||||
}
|
||||
func (h AuthHook) Trigger(sign string) error {
|
||||
for _, h := range h {
|
||||
if err := h(sign); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var OnPublishHooks = make(OnPublishHook, 0)
|
||||
|
||||
type OnPublishHook []func(r *Room)
|
||||
|
||||
func (h OnPublishHook) AddHook(hook func(r *Room)) {
|
||||
OnPublishHooks = append(h, hook)
|
||||
}
|
||||
func (h OnPublishHook) Trigger(r *Room) {
|
||||
for _, h := range h {
|
||||
h(r)
|
||||
}
|
||||
}
|
||||
|
||||
var OnSubscribeHooks = make(OnSubscribeHook, 0)
|
||||
|
||||
type OnSubscribeHook []func(s *OutputStream)
|
||||
|
||||
func (h OnSubscribeHook) AddHook(hook func(s *OutputStream)) {
|
||||
OnSubscribeHooks = append(h, hook)
|
||||
}
|
||||
func (h OnSubscribeHook) Trigger(s *OutputStream) {
|
||||
for _, h := range h {
|
||||
h(s)
|
||||
}
|
||||
}
|
||||
|
||||
var OnDropHooks = make(OnDropHook, 0)
|
||||
|
||||
type OnDropHook []func(s *OutputStream)
|
||||
|
||||
func (h OnDropHook) AddHook(hook func(s *OutputStream)) {
|
||||
OnDropHooks = append(h, hook)
|
||||
}
|
||||
func (h OnDropHook) Trigger(s *OutputStream) {
|
||||
for _, h := range h {
|
||||
h(s)
|
||||
}
|
||||
}
|
27
monica/index.go
Normal file
27
monica/index.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package monica
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/BurntSushi/toml"
|
||||
"log"
|
||||
)
|
||||
|
||||
func Run(configFile string) (err error) {
|
||||
if _, err = toml.DecodeFile(configFile, cg); err == nil {
|
||||
for name, config := range plugins {
|
||||
if cfg, ok := cg.Plugins[name]; ok {
|
||||
b, _ := json.Marshal(cfg)
|
||||
if err = json.Unmarshal(b, config.Config); err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
} else if config.Config != nil {
|
||||
continue
|
||||
}
|
||||
if config.Run != nil {
|
||||
go config.Run()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
32
monica/logger.go
Normal file
32
monica/logger.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package monica
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
type LogWriter struct {
|
||||
io.Writer
|
||||
origin io.Writer
|
||||
}
|
||||
|
||||
func (w *LogWriter) Write(data []byte) (n int, err error) {
|
||||
if n, err = w.Writer.Write(data); err != nil {
|
||||
go log.SetOutput(w.origin)
|
||||
}
|
||||
return w.origin.Write(data)
|
||||
}
|
||||
|
||||
func AddWriter(wn io.Writer) {
|
||||
log.SetOutput(&LogWriter{
|
||||
Writer: wn,
|
||||
origin: log.Writer(),
|
||||
})
|
||||
}
|
||||
|
||||
func MayBeError(info error) (hasError bool) {
|
||||
if hasError = info != nil; hasError {
|
||||
log.Print(info)
|
||||
}
|
||||
return
|
||||
}
|
73
monica/pool/avpacket.go
Normal file
73
monica/pool/avpacket.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
AVPacketPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(AVPacket)
|
||||
},
|
||||
}
|
||||
SendPacketPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(SendPacket)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Video or Audio
|
||||
type AVPacket struct {
|
||||
Timestamp uint32
|
||||
Type byte //8 audio,9 video
|
||||
IsAACSequence bool
|
||||
IsADTS bool
|
||||
// Video
|
||||
VideoFrameType byte //4bit
|
||||
IsAVCSequence bool
|
||||
Payload []byte
|
||||
RefCount int //Payload的引用次数
|
||||
}
|
||||
|
||||
func (av *AVPacket) IsKeyFrame() bool {
|
||||
return av.VideoFrameType == 1 || av.VideoFrameType == 4
|
||||
}
|
||||
|
||||
func (av *AVPacket) Recycle() {
|
||||
if av.RefCount == 0 {
|
||||
return
|
||||
} else if av.RefCount == 1 {
|
||||
av.RefCount = 0
|
||||
RecycleSlice(av.Payload)
|
||||
AVPacketPool.Put(av)
|
||||
} else {
|
||||
av.RefCount--
|
||||
}
|
||||
}
|
||||
func NewAVPacket(avType byte) (p *AVPacket) {
|
||||
p = AVPacketPool.Get().(*AVPacket)
|
||||
p.Type = avType
|
||||
p.IsAVCSequence = false
|
||||
p.VideoFrameType = 0
|
||||
p.Timestamp = 0
|
||||
p.IsAACSequence = false
|
||||
p.IsADTS = false
|
||||
return
|
||||
}
|
||||
|
||||
type SendPacket struct {
|
||||
Timestamp uint32
|
||||
Packet *AVPacket
|
||||
}
|
||||
|
||||
func (packet *SendPacket) Recycle() {
|
||||
packet.Packet.Recycle()
|
||||
SendPacketPool.Put(packet)
|
||||
}
|
||||
func NewSendPacket(p *AVPacket, timestamp uint32) (result *SendPacket) {
|
||||
result = SendPacketPool.Get().(*SendPacket)
|
||||
result.Packet = p
|
||||
result.Timestamp = timestamp
|
||||
return
|
||||
}
|
21
monica/pool/slice_pool.go
Normal file
21
monica/pool/slice_pool.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"github.com/funny/slab"
|
||||
)
|
||||
|
||||
var (
|
||||
slicePool = slab.NewChanPool(
|
||||
16, // The smallest chunk size is 16B.
|
||||
64*1024, // The largest chunk size is 64KB.
|
||||
2, // Power of 2 growth in chunk size.
|
||||
1024*1024, // Each slab will be 1MB in size.
|
||||
)
|
||||
)
|
||||
|
||||
func RecycleSlice(slice []byte) {
|
||||
slicePool.Free(slice)
|
||||
}
|
||||
func GetSlice(s int) []byte {
|
||||
return slicePool.Alloc(s)
|
||||
}
|
38
monica/publisher.go
Normal file
38
monica/publisher.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package monica
|
||||
|
||||
import (
|
||||
"log"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Publisher interface {
|
||||
OnClosed()
|
||||
}
|
||||
|
||||
type InputStream struct {
|
||||
*Room
|
||||
}
|
||||
|
||||
func (p *InputStream) Close() {
|
||||
if p.Running() {
|
||||
p.Cancel()
|
||||
}
|
||||
}
|
||||
func (p *InputStream) Running() bool {
|
||||
return p.Room != nil && p.Err() == nil
|
||||
}
|
||||
func (p *InputStream) OnClosed() {
|
||||
}
|
||||
func (p *InputStream) Publish(streamPath string, publisher Publisher) bool {
|
||||
p.Room = AllRoom.Get(streamPath)
|
||||
if p.Publisher != nil {
|
||||
return false
|
||||
}
|
||||
p.Publisher = publisher
|
||||
p.Type = reflect.ValueOf(publisher).Elem().Type().Name()
|
||||
log.Printf("publish set :%s", p.Type)
|
||||
p.StartTime = time.Now()
|
||||
OnPublishHooks.Trigger(p.Room)
|
||||
return true
|
||||
}
|
233
monica/room.go
Normal file
233
monica/room.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package monica
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/langhuihui/monibuca/monica/avformat"
|
||||
"github.com/langhuihui/monibuca/monica/pool"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
AllRoom = Collection{}
|
||||
roomCtxBg = context.Background()
|
||||
)
|
||||
|
||||
type Collection struct {
|
||||
sync.Map
|
||||
}
|
||||
|
||||
func (c *Collection) Get(name string) (result *Room) {
|
||||
item, loaded := AllRoom.LoadOrStore(name, &Room{
|
||||
Subscribers: make(map[string]*OutputStream),
|
||||
Control: make(chan interface{}),
|
||||
VideoChan: make(chan *pool.AVPacket, 1),
|
||||
AudioChan: make(chan *pool.AVPacket, 1),
|
||||
})
|
||||
result = item.(*Room)
|
||||
if !loaded {
|
||||
result.StreamName = name
|
||||
result.Context, result.Cancel = context.WithCancel(roomCtxBg)
|
||||
go result.Run()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Room struct {
|
||||
context.Context
|
||||
Publisher
|
||||
RoomInfo
|
||||
Control chan interface{}
|
||||
Cancel context.CancelFunc
|
||||
Subscribers map[string]*OutputStream // 订阅者
|
||||
VideoTag *pool.AVPacket // 每个视频包都是这样的结构,区别在于Payload的大小.FMS在发送AVC sequence header,需要加上 VideoTags,这个tag 1个字节(8bits)的数据
|
||||
AudioTag *pool.AVPacket // 每个音频包都是这样的结构,区别在于Payload的大小.FMS在发送AAC sequence header,需要加上 AudioTags,这个tag 1个字节(8bits)的数据
|
||||
FirstScreen []*pool.AVPacket
|
||||
AudioChan chan *pool.AVPacket
|
||||
VideoChan chan *pool.AVPacket
|
||||
UseTimestamp bool //是否采用数据包中的时间戳
|
||||
}
|
||||
|
||||
type RoomInfo struct {
|
||||
StreamName string
|
||||
StartTime time.Time
|
||||
SubscriberInfo []*SubscriberInfo
|
||||
Type string
|
||||
VideoInfo struct {
|
||||
PacketCount int
|
||||
CodecID byte
|
||||
avformat.SPSInfo
|
||||
}
|
||||
AudioInfo struct {
|
||||
PacketCount int
|
||||
SoundFormat byte //4bit
|
||||
SoundRate int //2bit
|
||||
SoundSize byte //1bit
|
||||
SoundType byte //1bit
|
||||
}
|
||||
}
|
||||
type UnSubscribeCmd struct {
|
||||
*OutputStream
|
||||
}
|
||||
type SubscribeCmd struct {
|
||||
*OutputStream
|
||||
}
|
||||
type ChangeRoomCmd struct {
|
||||
*OutputStream
|
||||
NewRoom *Room
|
||||
}
|
||||
|
||||
func (r *Room) onClosed() {
|
||||
log.Printf("room destoryed :%s", r.StreamName)
|
||||
AllRoom.Delete(r.StreamName)
|
||||
if r.Publisher != nil {
|
||||
r.OnClosed()
|
||||
}
|
||||
}
|
||||
func (r *Room) Subscribe(s *OutputStream) {
|
||||
s.Room = r
|
||||
if r.Err() == nil {
|
||||
s.SubscribeTime = time.Now()
|
||||
log.Printf("subscribe :%s %s,to room %s", s.Type, s.ID, r.StreamName)
|
||||
s.packetQueue = make(chan *pool.SendPacket, 1024)
|
||||
s.Context, s.Cancel = context.WithCancel(r)
|
||||
s.Control <- &SubscribeCmd{s}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Room) UnSubscribe(s *OutputStream) {
|
||||
if r.Err() == nil {
|
||||
r.Control <- &UnSubscribeCmd{s}
|
||||
}
|
||||
}
|
||||
func (r *Room) Run() {
|
||||
log.Printf("room create :%s", r.StreamName)
|
||||
defer r.onClosed()
|
||||
update := time.NewTicker(time.Second)
|
||||
defer update.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-r.Done():
|
||||
return
|
||||
case <-update.C:
|
||||
r.SubscriberInfo = make([]*SubscriberInfo, len(r.Subscribers))
|
||||
i := 0
|
||||
for _, v := range r.Subscribers {
|
||||
r.SubscriberInfo[i] = &v.SubscriberInfo
|
||||
i++
|
||||
}
|
||||
case s := <-r.Control:
|
||||
switch v := s.(type) {
|
||||
case *UnSubscribeCmd:
|
||||
delete(r.Subscribers, v.ID)
|
||||
log.Printf("%s subscriber %s removed remains:%d", r.StreamName, v.ID, len(r.Subscribers))
|
||||
if len(r.Subscribers) == 0 && r.Publisher == nil {
|
||||
r.Cancel()
|
||||
}
|
||||
case *SubscribeCmd:
|
||||
if _, ok := r.Subscribers[v.ID]; !ok {
|
||||
r.Subscribers[v.ID] = v.OutputStream
|
||||
log.Printf("%s subscriber %s added remains:%d", r.StreamName, v.ID, len(r.Subscribers))
|
||||
OnSubscribeHooks.Trigger(v.OutputStream)
|
||||
}
|
||||
case *ChangeRoomCmd:
|
||||
if _, ok := v.NewRoom.Subscribers[v.ID]; !ok {
|
||||
delete(r.Subscribers, v.ID)
|
||||
v.NewRoom.Subscribe(v.OutputStream)
|
||||
if len(r.Subscribers) == 0 && r.Publisher == nil {
|
||||
r.Cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
case audio := <-r.AudioChan:
|
||||
for _, v := range r.Subscribers {
|
||||
v.sendAudio(audio)
|
||||
}
|
||||
case video := <-r.VideoChan:
|
||||
for _, v := range r.Subscribers {
|
||||
v.sendVideo(video)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func (r *Room) PushAudio(audio *pool.AVPacket) {
|
||||
if audio.Payload[0] == 0xFF && (audio.Payload[1]&0xF0) == 0xF0 {
|
||||
audio.IsADTS = true
|
||||
|
||||
} else if r.AudioTag == nil {
|
||||
audio.IsAACSequence = true
|
||||
r.AudioTag = audio
|
||||
tmp := audio.Payload[0] // 第一个字节保存着音频的相关信息
|
||||
if r.AudioInfo.SoundFormat = tmp >> 4; r.AudioInfo.SoundFormat == 10 { //真的是AAC的话,后面有一个字节的详细信息
|
||||
//0 = AAC sequence header,1 = AAC raw。
|
||||
if aacPacketType := audio.Payload[1]; aacPacketType == 0 {
|
||||
config1 := audio.Payload[2]
|
||||
config2 := audio.Payload[3]
|
||||
//audioObjectType = (config1 & 0xF8) >> 3
|
||||
// 1 AAC MAIN ISO/IEC 14496-3 subpart 4
|
||||
// 2 AAC LC ISO/IEC 14496-3 subpart 4
|
||||
// 3 AAC SSR ISO/IEC 14496-3 subpart 4
|
||||
// 4 AAC LTP ISO/IEC 14496-3 subpart 4
|
||||
r.AudioInfo.SoundRate = avformat.SamplingFrequencies[((config1&0x7)<<1)|(0x90>>7)]
|
||||
r.AudioInfo.SoundType = (config2 >> 3) & 0x0F //声道
|
||||
//frameLengthFlag = (config2 >> 2) & 0x01
|
||||
//dependsOnCoreCoder = (config2 >> 1) & 0x01
|
||||
//extensionFlag = config2 & 0x01
|
||||
}
|
||||
} else {
|
||||
r.AudioInfo.SoundRate = avformat.SoundRate[(tmp&0x0c)>>2] // 采样率 0 = 5.5 kHz or 1 = 11 kHz or 2 = 22 kHz or 3 = 44 kHz
|
||||
r.AudioInfo.SoundSize = (tmp & 0x02) >> 1 // 采样精度 0 = 8-bit samples or 1 = 16-bit samples
|
||||
r.AudioInfo.SoundType = tmp & 0x01 // 0 单声道,1立体声
|
||||
}
|
||||
return
|
||||
}
|
||||
audio.RefCount = len(r.Subscribers)
|
||||
if !r.UseTimestamp {
|
||||
audio.Timestamp = uint32(time.Since(r.StartTime) / time.Millisecond)
|
||||
}
|
||||
r.AudioInfo.PacketCount++
|
||||
r.AudioChan <- audio
|
||||
}
|
||||
func (r *Room) setH264Info(video *pool.AVPacket) {
|
||||
r.VideoTag = video
|
||||
info := avformat.AVCDecoderConfigurationRecord{}
|
||||
//0:codec,1:IsAVCSequence,2~4:compositionTime
|
||||
if _, err := info.Unmarshal(video.Payload[5:]); err == nil {
|
||||
r.VideoInfo.SPSInfo, err = avformat.ParseSPS(info.SequenceParameterSetNALUnit)
|
||||
}
|
||||
}
|
||||
func (r *Room) PushVideo(video *pool.AVPacket) {
|
||||
video.VideoFrameType = video.Payload[0] >> 4 // 帧类型 4Bit, H264一般为1或者2
|
||||
r.VideoInfo.CodecID = video.Payload[0] & 0x0f // 编码类型ID 4Bit, JPEG, H263, AVC...
|
||||
video.IsAVCSequence = video.VideoFrameType == 1 && video.Payload[1] == 0
|
||||
if r.VideoTag == nil {
|
||||
if video.IsAVCSequence {
|
||||
r.setH264Info(video)
|
||||
} else {
|
||||
log.Println("no AVCSequence")
|
||||
}
|
||||
} else {
|
||||
//更换AVCSequence
|
||||
if video.IsAVCSequence {
|
||||
r.setH264Info(video)
|
||||
}
|
||||
if r.FirstScreen != nil {
|
||||
if video.IsKeyFrame() {
|
||||
for _, cache := range r.FirstScreen { //清空队列
|
||||
cache.Recycle()
|
||||
}
|
||||
r.FirstScreen = r.FirstScreen[:0]
|
||||
}
|
||||
r.FirstScreen = append(r.FirstScreen, video)
|
||||
video.RefCount = len(r.Subscribers) + 1
|
||||
} else {
|
||||
video.RefCount = len(r.Subscribers)
|
||||
}
|
||||
if !r.UseTimestamp {
|
||||
video.Timestamp = uint32(time.Since(r.StartTime) / time.Millisecond)
|
||||
}
|
||||
r.VideoInfo.PacketCount++
|
||||
r.VideoChan <- video
|
||||
}
|
||||
}
|
135
monica/subscriber.go
Normal file
135
monica/subscriber.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package monica
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/langhuihui/monibuca/monica/pool"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Subscriber interface {
|
||||
Send(*pool.SendPacket) error
|
||||
}
|
||||
|
||||
type SubscriberInfo struct {
|
||||
ID string
|
||||
TotalDrop int //总丢帧
|
||||
TotalPacket int
|
||||
Type string
|
||||
BufferLength int
|
||||
SubscribeTime time.Time
|
||||
}
|
||||
type OutputStream struct {
|
||||
context.Context
|
||||
*Room
|
||||
SubscriberInfo
|
||||
SendHandler func(*pool.SendPacket) error
|
||||
Cancel context.CancelFunc
|
||||
Sign string
|
||||
VTSent bool
|
||||
ATSent bool
|
||||
VSentTime uint32
|
||||
ASentTime uint32
|
||||
packetQueue chan *pool.SendPacket
|
||||
dropCount int
|
||||
OffsetTime uint32
|
||||
firstScreenIndex int
|
||||
}
|
||||
|
||||
func (s *OutputStream) IsClosed() bool {
|
||||
return s.Context != nil && s.Err() != nil
|
||||
}
|
||||
|
||||
func (s *OutputStream) Close() {
|
||||
if s.Cancel != nil {
|
||||
s.Cancel()
|
||||
}
|
||||
}
|
||||
func (s *OutputStream) Play(streamPath string) (err error) {
|
||||
AllRoom.Get(streamPath).Subscribe(s)
|
||||
defer s.UnSubscribe(s)
|
||||
for {
|
||||
select {
|
||||
case <-s.Done():
|
||||
return s.Err()
|
||||
case p := <-s.packetQueue:
|
||||
if err = s.SendHandler(p); err != nil {
|
||||
s.Cancel() //此处为了使得IsClosed 返回true
|
||||
return
|
||||
}
|
||||
p.Recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
func (s *OutputStream) sendPacket(packet *pool.AVPacket, timestamp uint32) {
|
||||
if !packet.IsAVCSequence && timestamp == 0 {
|
||||
timestamp = 1 //防止为0
|
||||
}
|
||||
s.TotalPacket++
|
||||
s.BufferLength = len(s.packetQueue)
|
||||
if s.dropCount > 0 {
|
||||
if packet.IsKeyFrame() {
|
||||
fmt.Printf("%s drop packet:%d\n", s.ID, s.dropCount)
|
||||
s.dropCount = 0 //退出丢包
|
||||
} else {
|
||||
s.dropCount++
|
||||
s.TotalDrop++
|
||||
return
|
||||
}
|
||||
}
|
||||
if s.BufferLength == cap(s.packetQueue) {
|
||||
s.dropCount++
|
||||
s.TotalDrop++
|
||||
packet.Recycle()
|
||||
} else if !s.IsClosed() {
|
||||
s.packetQueue <- pool.NewSendPacket(packet, timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *OutputStream) sendVideo(video *pool.AVPacket) error {
|
||||
isKF := video.IsKeyFrame()
|
||||
if s.VTSent {
|
||||
if s.FirstScreen == nil || s.firstScreenIndex == -1 {
|
||||
s.sendPacket(video, video.Timestamp-s.VSentTime+s.OffsetTime)
|
||||
} else if !isKF && s.firstScreenIndex < len(s.FirstScreen) {
|
||||
firstScreen := s.FirstScreen[s.firstScreenIndex]
|
||||
firstScreen.RefCount++
|
||||
s.VSentTime = firstScreen.Timestamp - s.FirstScreen[0].Timestamp
|
||||
s.sendPacket(firstScreen, s.VSentTime)
|
||||
video.Recycle() //回收当前数据
|
||||
s.firstScreenIndex++
|
||||
} else {
|
||||
s.firstScreenIndex = -1 //收到关键帧或者首屏缓冲已播完后退出首屏渲染模式
|
||||
s.OffsetTime += s.VSentTime
|
||||
s.VSentTime = video.Timestamp
|
||||
s.sendPacket(video, s.OffsetTime)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
//非首屏渲染模式跳过开头的非关键帧
|
||||
if !isKF {
|
||||
if s.FirstScreen == nil {
|
||||
return nil
|
||||
}
|
||||
} else if s.FirstScreen != nil {
|
||||
s.firstScreenIndex = -1 //跳过首屏渲染
|
||||
}
|
||||
s.VTSent = true
|
||||
s.sendPacket(s.VideoTag, 0)
|
||||
s.VSentTime = video.Timestamp
|
||||
return s.sendVideo(video)
|
||||
}
|
||||
func (s *OutputStream) sendAudio(audio *pool.AVPacket) error {
|
||||
if s.ATSent {
|
||||
if s.FirstScreen != nil && s.firstScreenIndex == -1 {
|
||||
audio.Recycle()
|
||||
return nil
|
||||
}
|
||||
s.sendPacket(audio, audio.Timestamp-s.ASentTime)
|
||||
return nil
|
||||
}
|
||||
s.ATSent = true
|
||||
s.sendPacket(s.AudioTag, 0)
|
||||
s.ASentTime = audio.Timestamp
|
||||
return s.sendAudio(audio)
|
||||
}
|
269
monica/util/big_little_endian.go
Normal file
269
monica/util/big_little_endian.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package util
|
||||
|
||||
//
|
||||
// 注意:RTMP模式下都是大端模式
|
||||
//
|
||||
|
||||
var LittleEndian littleEndian
|
||||
|
||||
// BigEndian is the big-endian implementation of ByteOrder.
|
||||
var BigEndian bigEndian
|
||||
|
||||
// 低位字节排放在内存的低地址端,高位字节排放在内存的高地址端.
|
||||
type littleEndian struct{}
|
||||
|
||||
// b == 0x1234, b[0] == 0x12, b[1] == 0x34
|
||||
// b[0]低字节 b[1]高字节
|
||||
// 内存地址 低 -> 高
|
||||
// 0x34 0x12
|
||||
|
||||
// byte(v)低字节 b[0]内存低地址
|
||||
// byte(v>>8)高字节 b[1]内存高地址
|
||||
|
||||
// b == 2222 2222 1111 1111
|
||||
// b >> 8 -> 0000 0000 2222 2222
|
||||
// b << 8 -> 1111 1111 0000 0000
|
||||
|
||||
func (littleEndian) Uint16(b []byte) uint16 { return uint16(b[0]) | uint16(b[1])<<8 }
|
||||
func (littleEndian) Uint24(b []byte) uint32 { return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 }
|
||||
func (littleEndian) Uint32(b []byte) uint32 {
|
||||
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||
}
|
||||
func (littleEndian) Uint40(b []byte) uint64 {
|
||||
return uint64(b[0]) | uint64(b[1])<<8 |
|
||||
uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32
|
||||
}
|
||||
func (littleEndian) Uint48(b []byte) uint64 {
|
||||
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 |
|
||||
uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40
|
||||
}
|
||||
func (littleEndian) Uint64(b []byte) uint64 {
|
||||
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
|
||||
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
|
||||
}
|
||||
|
||||
//
|
||||
// Put
|
||||
//
|
||||
|
||||
func (littleEndian) PutUint16(b []byte, v uint16) {
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
}
|
||||
func (littleEndian) PutUint24(b []byte, v uint32) {
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
}
|
||||
func (littleEndian) PutUint32(b []byte, v uint32) {
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 24)
|
||||
}
|
||||
func (littleEndian) PutUint64(b []byte, v uint64) {
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 24)
|
||||
b[4] = byte(v >> 32)
|
||||
b[5] = byte(v >> 40)
|
||||
b[6] = byte(v >> 48)
|
||||
b[7] = byte(v >> 56)
|
||||
}
|
||||
|
||||
//
|
||||
// To
|
||||
//
|
||||
|
||||
func (littleEndian) ToUint16(v uint16) []byte {
|
||||
b := make([]byte, 2)
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
return b
|
||||
}
|
||||
func (littleEndian) ToUint24(v uint32) []byte {
|
||||
b := make([]byte, 3)
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
return b
|
||||
}
|
||||
func (littleEndian) ToUint32(v uint32) []byte {
|
||||
b := make([]byte, 4)
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 24)
|
||||
return b
|
||||
}
|
||||
func (littleEndian) ToUint40(v uint64) []byte {
|
||||
b := make([]byte, 5)
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 24)
|
||||
b[4] = byte(v >> 32)
|
||||
return b
|
||||
}
|
||||
func (littleEndian) ToUint48(v uint64) []byte {
|
||||
b := make([]byte, 6)
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 24)
|
||||
b[4] = byte(v >> 32)
|
||||
b[5] = byte(v >> 40)
|
||||
return b
|
||||
}
|
||||
func (littleEndian) ToUint64(v uint64) []byte {
|
||||
b := make([]byte, 8)
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 24)
|
||||
b[4] = byte(v >> 32)
|
||||
b[5] = byte(v >> 40)
|
||||
b[6] = byte(v >> 48)
|
||||
b[7] = byte(v >> 56)
|
||||
return b
|
||||
}
|
||||
|
||||
// 高位字节排放在内存的低地址端,低位字节排放在内存的高地址端
|
||||
type bigEndian struct{}
|
||||
|
||||
// b == 0x1234, b[0] == 0x12, b[1] == 0x34
|
||||
// 内存地址 低 -> 高
|
||||
// 0x12 0x34
|
||||
func (bigEndian) Uint16(b []byte) uint16 { return uint16(b[1]) | uint16(b[0])<<8 }
|
||||
func (bigEndian) Uint24(b []byte) uint32 { return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16 }
|
||||
func (bigEndian) Uint32(b []byte) uint32 {
|
||||
return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
|
||||
}
|
||||
func (bigEndian) Uint40(b []byte) uint64 {
|
||||
return uint64(b[4]) | uint64(b[3])<<8 |
|
||||
uint64(b[2])<<16 | uint64(b[1])<<24 | uint64(b[0])<<32
|
||||
}
|
||||
func (bigEndian) Uint48(b []byte) uint64 {
|
||||
return uint64(b[5]) | uint64(b[4])<<8 | uint64(b[3])<<16 |
|
||||
uint64(b[2])<<24 | uint64(b[1])<<32 | uint64(b[0])<<40
|
||||
}
|
||||
func (bigEndian) Uint64(b []byte) uint64 {
|
||||
return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
|
||||
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
|
||||
}
|
||||
|
||||
//
|
||||
// Put
|
||||
//
|
||||
|
||||
func (bigEndian) PutUint16(b []byte, v uint16) {
|
||||
b[0] = byte(v >> 8)
|
||||
b[1] = byte(v)
|
||||
}
|
||||
func (bigEndian) PutUint24(b []byte, v uint32) {
|
||||
b[0] = byte(v >> 16)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v)
|
||||
}
|
||||
func (bigEndian) PutUint32(b []byte, v uint32) {
|
||||
b[0] = byte(v >> 24)
|
||||
b[1] = byte(v >> 16)
|
||||
b[2] = byte(v >> 8)
|
||||
b[3] = byte(v)
|
||||
}
|
||||
func (bigEndian) PutUint64(b []byte, v uint64) {
|
||||
b[0] = byte(v >> 56)
|
||||
b[1] = byte(v >> 48)
|
||||
b[2] = byte(v >> 40)
|
||||
b[3] = byte(v >> 32)
|
||||
b[4] = byte(v >> 24)
|
||||
b[5] = byte(v >> 16)
|
||||
b[6] = byte(v >> 8)
|
||||
b[7] = byte(v)
|
||||
}
|
||||
|
||||
//
|
||||
// To
|
||||
//
|
||||
|
||||
func (bigEndian) ToUint16(v uint16) []byte {
|
||||
b := make([]byte, 2)
|
||||
b[0] = byte(v >> 8)
|
||||
b[1] = byte(v)
|
||||
return b
|
||||
}
|
||||
func (bigEndian) ToUint24(v uint32) []byte {
|
||||
b := make([]byte, 3)
|
||||
b[0] = byte(v >> 16)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v)
|
||||
return b
|
||||
}
|
||||
func (bigEndian) ToUint32(v uint32) []byte {
|
||||
b := make([]byte, 4)
|
||||
b[0] = byte(v >> 24)
|
||||
b[1] = byte(v >> 16)
|
||||
b[2] = byte(v >> 8)
|
||||
b[3] = byte(v)
|
||||
return b
|
||||
}
|
||||
func (bigEndian) ToUint40(v uint64) []byte {
|
||||
b := make([]byte, 5)
|
||||
b[0] = byte(v >> 32)
|
||||
b[1] = byte(v >> 24)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 8)
|
||||
b[4] = byte(v)
|
||||
return b
|
||||
}
|
||||
func (bigEndian) ToUint48(v uint64) []byte {
|
||||
b := make([]byte, 6)
|
||||
b[0] = byte(v >> 40)
|
||||
b[1] = byte(v >> 32)
|
||||
b[2] = byte(v >> 24)
|
||||
b[3] = byte(v >> 16)
|
||||
b[4] = byte(v >> 8)
|
||||
b[5] = byte(v)
|
||||
return b
|
||||
}
|
||||
func (bigEndian) ToUint64(v uint64) []byte {
|
||||
b := make([]byte, 8)
|
||||
b[0] = byte(v >> 56)
|
||||
b[1] = byte(v >> 48)
|
||||
b[2] = byte(v >> 40)
|
||||
b[3] = byte(v >> 32)
|
||||
b[4] = byte(v >> 24)
|
||||
b[5] = byte(v >> 16)
|
||||
b[6] = byte(v >> 8)
|
||||
b[7] = byte(v)
|
||||
return b
|
||||
}
|
||||
|
||||
//哥伦布解码
|
||||
func GetUev(buff []byte, start int) (value int, pos int) {
|
||||
l := len(buff)
|
||||
var nZeroNum uint = 0
|
||||
for start < l*8 {
|
||||
if (buff[start/8] & (0x80 >> uint(start%8))) > 0 {
|
||||
break
|
||||
}
|
||||
nZeroNum += 1
|
||||
start += 1
|
||||
}
|
||||
dwRet := 0
|
||||
start += 1
|
||||
var i uint
|
||||
for i = 0; i < nZeroNum; i++ {
|
||||
dwRet <<= 1
|
||||
if (buff[start/8] & (0x80 >> uint(start%8))) > 0 {
|
||||
dwRet += 1
|
||||
}
|
||||
start += 1
|
||||
}
|
||||
return (1 << nZeroNum) - 1 + dwRet, start
|
||||
}
|
||||
|
||||
func BigLittleSwap(v uint) uint {
|
||||
return (v >> 24) | ((v>>16)&0xff)<<8 | ((v>>8)&0xff)<<16 | (v&0xff)<<24
|
||||
}
|
118
monica/util/bits/bits.go
Normal file
118
monica/util/bits/bits.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package bits
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
R io.Reader
|
||||
n int
|
||||
bits uint64
|
||||
}
|
||||
|
||||
func (self *Reader) ReadBits64(n int) (bits uint64, err error) {
|
||||
if self.n < n {
|
||||
var b [8]byte
|
||||
var got int
|
||||
want := (n - self.n + 7) / 8
|
||||
if got, err = self.R.Read(b[:want]); err != nil {
|
||||
return
|
||||
}
|
||||
if got < want {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
for i := 0; i < got; i++ {
|
||||
self.bits <<= 8
|
||||
self.bits |= uint64(b[i])
|
||||
}
|
||||
self.n += got * 8
|
||||
}
|
||||
bits = self.bits >> uint(self.n-n)
|
||||
self.bits ^= bits << uint(self.n-n)
|
||||
self.n -= n
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Reader) ReadBits(n int) (bits uint, err error) {
|
||||
var bits64 uint64
|
||||
if bits64, err = self.ReadBits64(n); err != nil {
|
||||
return
|
||||
}
|
||||
bits = uint(bits64)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Reader) Read(p []byte) (n int, err error) {
|
||||
for n < len(p) {
|
||||
want := 8
|
||||
if len(p)-n < want {
|
||||
want = len(p) - n
|
||||
}
|
||||
var bits uint64
|
||||
if bits, err = self.ReadBits64(want * 8); err != nil {
|
||||
break
|
||||
}
|
||||
for i := 0; i < want; i++ {
|
||||
p[n+i] = byte(bits >> uint((want-i-1)*8))
|
||||
}
|
||||
n += want
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
W io.Writer
|
||||
n int
|
||||
bits uint64
|
||||
}
|
||||
|
||||
func (self *Writer) WriteBits64(bits uint64, n int) (err error) {
|
||||
if self.n+n > 64 {
|
||||
move := uint(64 - self.n)
|
||||
mask := bits >> move
|
||||
self.bits = (self.bits << move) | mask
|
||||
self.n = 64
|
||||
if err = self.FlushBits(); err != nil {
|
||||
return
|
||||
}
|
||||
n -= int(move)
|
||||
bits ^= (mask << move)
|
||||
}
|
||||
self.bits = (self.bits << uint(n)) | bits
|
||||
self.n += n
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Writer) WriteBits(bits uint, n int) (err error) {
|
||||
return self.WriteBits64(uint64(bits), n)
|
||||
}
|
||||
|
||||
func (self *Writer) Write(p []byte) (n int, err error) {
|
||||
for n < len(p) {
|
||||
if err = self.WriteBits64(uint64(p[n]), 8); err != nil {
|
||||
return
|
||||
}
|
||||
n++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Writer) FlushBits() (err error) {
|
||||
if self.n > 0 {
|
||||
var b [8]byte
|
||||
bits := self.bits
|
||||
if self.n%8 != 0 {
|
||||
bits <<= uint(8 - (self.n % 8))
|
||||
}
|
||||
want := (self.n + 7) / 8
|
||||
for i := 0; i < want; i++ {
|
||||
b[i] = byte(bits >> uint((want-i-1)*8))
|
||||
}
|
||||
if _, err = self.W.Write(b[:want]); err != nil {
|
||||
return
|
||||
}
|
||||
self.n = 0
|
||||
}
|
||||
return
|
||||
}
|
51
monica/util/bits/bits_test.go
Normal file
51
monica/util/bits/bits_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package bits
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBits(t *testing.T) {
|
||||
rdata := []byte{0xf3, 0xb3, 0x45, 0x60}
|
||||
rbuf := bytes.NewReader(rdata[:])
|
||||
r := &Reader{R: rbuf}
|
||||
var u32 uint
|
||||
if u32, _ = r.ReadBits(4); u32 != 0xf {
|
||||
t.FailNow()
|
||||
}
|
||||
if u32, _ = r.ReadBits(4); u32 != 0x3 {
|
||||
t.FailNow()
|
||||
}
|
||||
if u32, _ = r.ReadBits(2); u32 != 0x2 {
|
||||
t.FailNow()
|
||||
}
|
||||
if u32, _ = r.ReadBits(2); u32 != 0x3 {
|
||||
t.FailNow()
|
||||
}
|
||||
b := make([]byte, 2)
|
||||
if r.Read(b); b[0] != 0x34 || b[1] != 0x56 {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
wbuf := &bytes.Buffer{}
|
||||
w := &Writer{W: wbuf}
|
||||
w.WriteBits(0xf, 4)
|
||||
w.WriteBits(0x3, 4)
|
||||
w.WriteBits(0x2, 2)
|
||||
w.WriteBits(0x3, 2)
|
||||
n, _ := w.Write([]byte{0x34, 0x56})
|
||||
if n != 2 {
|
||||
t.FailNow()
|
||||
}
|
||||
w.FlushBits()
|
||||
wdata := wbuf.Bytes()
|
||||
if wdata[0] != 0xf3 || wdata[1] != 0xb3 || wdata[2] != 0x45 || wdata[3] != 0x60 {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
b = make([]byte, 8)
|
||||
PutUInt64BE(b, 0x11223344, 32)
|
||||
if b[0] != 0x11 || b[1] != 0x22 || b[2] != 0x33 || b[3] != 0x44 {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
22
monica/util/bits/bufio/bufio.go
Normal file
22
monica/util/bits/bufio/bufio.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package bufio
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
buf [][]byte
|
||||
R io.ReadSeeker
|
||||
}
|
||||
|
||||
func NewReaderSize(r io.ReadSeeker, size int) *Reader {
|
||||
buf := make([]byte, size*2)
|
||||
return &Reader{
|
||||
R: r,
|
||||
buf: [][]byte{buf[0:size], buf[size:]},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Reader) ReadAt(b []byte, off int64) (n int, err error) {
|
||||
return
|
||||
}
|
65
monica/util/bits/golomb_reader.go
Normal file
65
monica/util/bits/golomb_reader.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package bits
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type GolombBitReader struct {
|
||||
R io.Reader
|
||||
buf [1]byte
|
||||
left byte
|
||||
}
|
||||
|
||||
func (self *GolombBitReader) ReadBit() (res uint, err error) {
|
||||
if self.left == 0 {
|
||||
if _, err = self.R.Read(self.buf[:]); err != nil {
|
||||
return
|
||||
}
|
||||
self.left = 8
|
||||
}
|
||||
self.left--
|
||||
res = uint(self.buf[0]>>self.left) & 1
|
||||
return
|
||||
}
|
||||
|
||||
func (self *GolombBitReader) ReadBits(n int) (res uint, err error) {
|
||||
for i := 0; i < n; i++ {
|
||||
var bit uint
|
||||
if bit, err = self.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
res |= bit << uint(n-i-1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *GolombBitReader) ReadExponentialGolombCode() (res uint, err error) {
|
||||
i := 0
|
||||
for {
|
||||
var bit uint
|
||||
if bit, err = self.ReadBit(); err != nil {
|
||||
return
|
||||
}
|
||||
if !(bit == 0 && i < 32) {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if res, err = self.ReadBits(i); err != nil {
|
||||
return
|
||||
}
|
||||
res += (1 << uint(i)) - 1
|
||||
return
|
||||
}
|
||||
|
||||
func (self *GolombBitReader) ReadSE() (res uint, err error) {
|
||||
if res, err = self.ReadExponentialGolombCode(); err != nil {
|
||||
return
|
||||
}
|
||||
if res&0x01 != 0 {
|
||||
res = (res + 1) / 2
|
||||
} else {
|
||||
res = -res / 2
|
||||
}
|
||||
return
|
||||
}
|
3
monica/util/bits/pio/pio.go
Normal file
3
monica/util/bits/pio/pio.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package pio
|
||||
|
||||
var RecommendBufioSize = 1024 * 64
|
121
monica/util/bits/pio/reader.go
Normal file
121
monica/util/bits/pio/reader.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package pio
|
||||
|
||||
func U8(b []byte) (i uint8) {
|
||||
return b[0]
|
||||
}
|
||||
|
||||
func U16BE(b []byte) (i uint16) {
|
||||
i = uint16(b[0])
|
||||
i <<= 8
|
||||
i |= uint16(b[1])
|
||||
return
|
||||
}
|
||||
|
||||
func I16BE(b []byte) (i int16) {
|
||||
i = int16(b[0])
|
||||
i <<= 8
|
||||
i |= int16(b[1])
|
||||
return
|
||||
}
|
||||
|
||||
func I24BE(b []byte) (i int32) {
|
||||
i = int32(int8(b[0]))
|
||||
i <<= 8
|
||||
i |= int32(b[1])
|
||||
i <<= 8
|
||||
i |= int32(b[2])
|
||||
return
|
||||
}
|
||||
|
||||
func U24BE(b []byte) (i uint32) {
|
||||
i = uint32(b[0])
|
||||
i <<= 8
|
||||
i |= uint32(b[1])
|
||||
i <<= 8
|
||||
i |= uint32(b[2])
|
||||
return
|
||||
}
|
||||
|
||||
func I32BE(b []byte) (i int32) {
|
||||
i = int32(int8(b[0]))
|
||||
i <<= 8
|
||||
i |= int32(b[1])
|
||||
i <<= 8
|
||||
i |= int32(b[2])
|
||||
i <<= 8
|
||||
i |= int32(b[3])
|
||||
return
|
||||
}
|
||||
|
||||
func U32LE(b []byte) (i uint32) {
|
||||
i = uint32(b[3])
|
||||
i <<= 8
|
||||
i |= uint32(b[2])
|
||||
i <<= 8
|
||||
i |= uint32(b[1])
|
||||
i <<= 8
|
||||
i |= uint32(b[0])
|
||||
return
|
||||
}
|
||||
|
||||
func U32BE(b []byte) (i uint32) {
|
||||
i = uint32(b[0])
|
||||
i <<= 8
|
||||
i |= uint32(b[1])
|
||||
i <<= 8
|
||||
i |= uint32(b[2])
|
||||
i <<= 8
|
||||
i |= uint32(b[3])
|
||||
return
|
||||
}
|
||||
|
||||
func U40BE(b []byte) (i uint64) {
|
||||
i = uint64(b[0])
|
||||
i <<= 8
|
||||
i |= uint64(b[1])
|
||||
i <<= 8
|
||||
i |= uint64(b[2])
|
||||
i <<= 8
|
||||
i |= uint64(b[3])
|
||||
i <<= 8
|
||||
i |= uint64(b[4])
|
||||
return
|
||||
}
|
||||
|
||||
func U64BE(b []byte) (i uint64) {
|
||||
i = uint64(b[0])
|
||||
i <<= 8
|
||||
i |= uint64(b[1])
|
||||
i <<= 8
|
||||
i |= uint64(b[2])
|
||||
i <<= 8
|
||||
i |= uint64(b[3])
|
||||
i <<= 8
|
||||
i |= uint64(b[4])
|
||||
i <<= 8
|
||||
i |= uint64(b[5])
|
||||
i <<= 8
|
||||
i |= uint64(b[6])
|
||||
i <<= 8
|
||||
i |= uint64(b[7])
|
||||
return
|
||||
}
|
||||
|
||||
func I64BE(b []byte) (i int64) {
|
||||
i = int64(int8(b[0]))
|
||||
i <<= 8
|
||||
i |= int64(b[1])
|
||||
i <<= 8
|
||||
i |= int64(b[2])
|
||||
i <<= 8
|
||||
i |= int64(b[3])
|
||||
i <<= 8
|
||||
i |= int64(b[4])
|
||||
i <<= 8
|
||||
i |= int64(b[5])
|
||||
i <<= 8
|
||||
i |= int64(b[6])
|
||||
i <<= 8
|
||||
i |= int64(b[7])
|
||||
return
|
||||
}
|
68
monica/util/bits/pio/vec.go
Normal file
68
monica/util/bits/pio/vec.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package pio
|
||||
|
||||
func VecLen(vec [][]byte) (n int) {
|
||||
for _, b := range vec {
|
||||
n += len(b)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func VecSliceTo(in [][]byte, out [][]byte, s int, e int) (n int) {
|
||||
if s < 0 {
|
||||
s = 0
|
||||
}
|
||||
|
||||
if e >= 0 && e < s {
|
||||
panic("pio: VecSlice start > end")
|
||||
}
|
||||
|
||||
i := 0
|
||||
off := 0
|
||||
for s > 0 && i < len(in) {
|
||||
left := len(in[i])
|
||||
read := s
|
||||
if left < read {
|
||||
read = left
|
||||
}
|
||||
left -= read
|
||||
off += read
|
||||
s -= read
|
||||
e -= read
|
||||
if left == 0 {
|
||||
i++
|
||||
off = 0
|
||||
}
|
||||
}
|
||||
if s > 0 {
|
||||
panic("pio: VecSlice start out of range")
|
||||
}
|
||||
|
||||
for e != 0 && i < len(in) {
|
||||
left := len(in[i]) - off
|
||||
read := left
|
||||
if e > 0 && e < read {
|
||||
read = e
|
||||
}
|
||||
out[n] = in[i][off : off+read]
|
||||
n++
|
||||
left -= read
|
||||
e -= read
|
||||
off += read
|
||||
if left == 0 {
|
||||
i++
|
||||
off = 0
|
||||
}
|
||||
}
|
||||
if e > 0 {
|
||||
panic("pio: VecSlice end out of range")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func VecSlice(in [][]byte, s int, e int) (out [][]byte) {
|
||||
out = make([][]byte, len(in))
|
||||
n := VecSliceTo(in, out, s, e)
|
||||
out = out[:n]
|
||||
return
|
||||
}
|
21
monica/util/bits/pio/vec_test.go
Normal file
21
monica/util/bits/pio/vec_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package pio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func ExampleVec() {
|
||||
vec := [][]byte{[]byte{1, 2, 3}, []byte{4, 5, 6, 7, 8, 9}, []byte{10, 11, 12, 13}}
|
||||
println(VecLen(vec))
|
||||
|
||||
vec = VecSlice(vec, 1, -1)
|
||||
fmt.Println(vec)
|
||||
|
||||
vec = VecSlice(vec, 2, -1)
|
||||
fmt.Println(vec)
|
||||
|
||||
vec = VecSlice(vec, 8, 8)
|
||||
fmt.Println(vec)
|
||||
|
||||
// Output:
|
||||
}
|
87
monica/util/bits/pio/writer.go
Normal file
87
monica/util/bits/pio/writer.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package pio
|
||||
|
||||
func PutU8(b []byte, v uint8) {
|
||||
b[0] = v
|
||||
}
|
||||
|
||||
func PutI16BE(b []byte, v int16) {
|
||||
b[0] = byte(v >> 8)
|
||||
b[1] = byte(v)
|
||||
}
|
||||
|
||||
func PutU16BE(b []byte, v uint16) {
|
||||
b[0] = byte(v >> 8)
|
||||
b[1] = byte(v)
|
||||
}
|
||||
|
||||
func PutI24BE(b []byte, v int32) {
|
||||
b[0] = byte(v >> 16)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v)
|
||||
}
|
||||
|
||||
func PutU24BE(b []byte, v uint32) {
|
||||
b[0] = byte(v >> 16)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v)
|
||||
}
|
||||
|
||||
func PutI32BE(b []byte, v int32) {
|
||||
b[0] = byte(v >> 24)
|
||||
b[1] = byte(v >> 16)
|
||||
b[2] = byte(v >> 8)
|
||||
b[3] = byte(v)
|
||||
}
|
||||
|
||||
func PutU32BE(b []byte, v uint32) {
|
||||
b[0] = byte(v >> 24)
|
||||
b[1] = byte(v >> 16)
|
||||
b[2] = byte(v >> 8)
|
||||
b[3] = byte(v)
|
||||
}
|
||||
|
||||
func PutU32LE(b []byte, v uint32) {
|
||||
b[3] = byte(v >> 24)
|
||||
b[2] = byte(v >> 16)
|
||||
b[1] = byte(v >> 8)
|
||||
b[0] = byte(v)
|
||||
}
|
||||
|
||||
func PutU40BE(b []byte, v uint64) {
|
||||
b[0] = byte(v >> 32)
|
||||
b[1] = byte(v >> 24)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 8)
|
||||
b[4] = byte(v)
|
||||
}
|
||||
|
||||
func PutU48BE(b []byte, v uint64) {
|
||||
b[0] = byte(v >> 40)
|
||||
b[1] = byte(v >> 32)
|
||||
b[2] = byte(v >> 24)
|
||||
b[3] = byte(v >> 16)
|
||||
b[4] = byte(v >> 8)
|
||||
b[5] = byte(v)
|
||||
}
|
||||
|
||||
func PutU64BE(b []byte, v uint64) {
|
||||
b[0] = byte(v >> 56)
|
||||
b[1] = byte(v >> 48)
|
||||
b[2] = byte(v >> 40)
|
||||
b[3] = byte(v >> 32)
|
||||
b[4] = byte(v >> 24)
|
||||
b[5] = byte(v >> 16)
|
||||
b[6] = byte(v >> 8)
|
||||
b[7] = byte(v)
|
||||
}
|
||||
|
||||
func PutI64BE(b []byte, v int64) {
|
||||
b[0] = byte(v >> 56)
|
||||
b[1] = byte(v >> 48)
|
||||
b[2] = byte(v >> 40)
|
||||
b[3] = byte(v >> 32)
|
||||
b[4] = byte(v >> 24)
|
||||
b[5] = byte(v >> 16)
|
||||
b[6] = byte(v >> 8)
|
||||
b[7] = byte(v)
|
||||
}
|
384
monica/util/convert.go
Normal file
384
monica/util/convert.go
Normal file
@@ -0,0 +1,384 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
/*
|
||||
func ReadByteToUintX(r io.Reader, l int) (data uint64, err error) {
|
||||
if l%8 != 0 || l > 64 {
|
||||
return 0, errors.New("disable convert")
|
||||
}
|
||||
|
||||
bb := make([]byte, l)
|
||||
if _, err := io.ReadFull(r, bb); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
switch l / 8 {
|
||||
case 1:
|
||||
{
|
||||
return uint8(bb[0]), nil
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
return BigEndian.Uint16(bb), nil
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
return BigEndian.Uint24(bb), nil
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
return BigEndian.Uint32(bb), nil
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
//return BigEndian.Uint40(bb), nil
|
||||
return 0, errors.New("disable convert")
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
return BigEndian.Uint48(bb), nil
|
||||
}
|
||||
case 7:
|
||||
{
|
||||
//return BigEndian.Uint56(bb), nil
|
||||
return 0, errors.New("disable convert")
|
||||
}
|
||||
case 8:
|
||||
{
|
||||
return BigEndian.Uint64(bb), nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, errors.New("convert not exist")
|
||||
}
|
||||
*/
|
||||
|
||||
// // 千万注意大小端,RTMP是大端
|
||||
func ByteToUint32N(data []byte) (ret uint32, err error) {
|
||||
if len(data) > 4 {
|
||||
return 0, errors.New("ByteToUint32N error!")
|
||||
}
|
||||
|
||||
for i := 0; i < len(data); i++ {
|
||||
ret <<= 8
|
||||
ret |= uint32(data[i])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// // 千万注意大小端,RTMP是大端
|
||||
func ByteToUint64N(data []byte) (ret uint64, err error) {
|
||||
if len(data) > 8 {
|
||||
return 0, errors.New("ByteToUint64N error!")
|
||||
}
|
||||
|
||||
for i := 0; i < len(data); i++ {
|
||||
ret <<= 8
|
||||
ret |= uint64(data[i])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 千万注意大小端,RTMP是大端
|
||||
func ByteToUint32(data []byte, bigEndian bool) (ret uint32, err error) {
|
||||
if bigEndian {
|
||||
return BigEndian.Uint32(data), nil
|
||||
} else {
|
||||
return LittleEndian.Uint32(data), nil
|
||||
}
|
||||
}
|
||||
|
||||
func Uint32ToByte(data uint32, bigEndian bool) (ret []byte, err error) {
|
||||
if bigEndian {
|
||||
return BigEndian.ToUint32(data), nil
|
||||
} else {
|
||||
return LittleEndian.ToUint32(data), nil
|
||||
}
|
||||
}
|
||||
|
||||
func ReadByteToUint8(r io.Reader) (data uint8, err error) {
|
||||
bb := make([]byte, 1)
|
||||
if _, err := io.ReadFull(r, bb); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint8(bb[0]), nil
|
||||
}
|
||||
|
||||
func ReadByteToUint16(r io.Reader, bigEndian bool) (data uint16, err error) {
|
||||
bb := make([]byte, 2)
|
||||
if _, err := io.ReadFull(r, bb); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if bigEndian {
|
||||
return BigEndian.Uint16(bb), nil
|
||||
} else {
|
||||
return LittleEndian.Uint16(bb), nil
|
||||
}
|
||||
}
|
||||
|
||||
func ReadByteToUint24(r io.Reader, bigEndian bool) (data uint32, err error) {
|
||||
bb := make([]byte, 3)
|
||||
if _, err := io.ReadFull(r, bb); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if bigEndian {
|
||||
return BigEndian.Uint24(bb), nil
|
||||
} else {
|
||||
return LittleEndian.Uint24(bb), nil
|
||||
}
|
||||
}
|
||||
|
||||
func ReadByteToUint32(r io.Reader, bigEndian bool) (data uint32, err error) {
|
||||
bb := make([]byte, 4)
|
||||
if _, err := io.ReadFull(r, bb); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if bigEndian {
|
||||
return BigEndian.Uint32(bb), nil
|
||||
} else {
|
||||
return LittleEndian.Uint32(bb), nil
|
||||
}
|
||||
}
|
||||
|
||||
func ReadByteToUint40(r io.Reader, bigEndian bool) (data uint64, err error) {
|
||||
bb := make([]byte, 5)
|
||||
if _, err := io.ReadFull(r, bb); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if bigEndian {
|
||||
return BigEndian.Uint40(bb), nil
|
||||
} else {
|
||||
return LittleEndian.Uint40(bb), nil
|
||||
}
|
||||
}
|
||||
|
||||
func ReadByteToUint48(r io.Reader, bigEndian bool) (data uint64, err error) {
|
||||
bb := make([]byte, 6)
|
||||
if _, err := io.ReadFull(r, bb); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if bigEndian {
|
||||
return BigEndian.Uint48(bb), nil
|
||||
} else {
|
||||
return LittleEndian.Uint48(bb), nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func ReadByteToUint56(r io.Reader) (data uint64, err error) {
|
||||
bb := make([]byte, 7)
|
||||
if _, err := io.ReadFull(r, bb); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint8(bb[0]), nil
|
||||
}
|
||||
*/
|
||||
|
||||
func ReadByteToUint64(r io.Reader, bigEndian bool) (data uint64, err error) {
|
||||
bb := make([]byte, 8)
|
||||
if _, err := io.ReadFull(r, bb); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if bigEndian {
|
||||
return BigEndian.Uint64(bb), nil
|
||||
} else {
|
||||
return LittleEndian.Uint64(bb), nil
|
||||
}
|
||||
}
|
||||
|
||||
func WriteUint8ToByte(w io.Writer, data uint8) error {
|
||||
bb := make([]byte, 8)
|
||||
bb[0] = byte(data)
|
||||
_, err := w.Write(bb[:1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteUint16ToByte(w io.Writer, data uint16, bigEndian bool) error {
|
||||
var bb []byte
|
||||
if bigEndian {
|
||||
bb = BigEndian.ToUint16(data)
|
||||
} else {
|
||||
bb = LittleEndian.ToUint16(data)
|
||||
}
|
||||
|
||||
_, err := w.Write(bb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteUint24ToByte(w io.Writer, data uint32, bigEndian bool) error {
|
||||
var bb []byte
|
||||
if bigEndian {
|
||||
bb = BigEndian.ToUint24(data)
|
||||
} else {
|
||||
bb = LittleEndian.ToUint24(data)
|
||||
}
|
||||
|
||||
_, err := w.Write(bb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteUint32ToByte(w io.Writer, data uint32, bigEndian bool) error {
|
||||
var bb []byte
|
||||
if bigEndian {
|
||||
bb = BigEndian.ToUint32(data)
|
||||
} else {
|
||||
bb = LittleEndian.ToUint32(data)
|
||||
}
|
||||
|
||||
_, err := w.Write(bb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteUint40ToByte(w io.Writer, data uint64, bigEndian bool) error {
|
||||
var bb []byte
|
||||
if bigEndian {
|
||||
bb = BigEndian.ToUint40(data)
|
||||
} else {
|
||||
bb = LittleEndian.ToUint40(data)
|
||||
}
|
||||
|
||||
_, err := w.Write(bb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteUint48ToByte(w io.Writer, data uint64, bigEndian bool) error {
|
||||
var bb []byte
|
||||
if bigEndian {
|
||||
bb = BigEndian.ToUint48(data)
|
||||
} else {
|
||||
bb = LittleEndian.ToUint48(data)
|
||||
}
|
||||
|
||||
_, err := w.Write(bb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteUint64ToByte(w io.Writer, data uint64, bigEndian bool) error {
|
||||
var bb []byte
|
||||
if bigEndian {
|
||||
bb = BigEndian.ToUint64(data)
|
||||
} else {
|
||||
bb = LittleEndian.ToUint64(data)
|
||||
}
|
||||
|
||||
_, err := w.Write(bb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetPtsDts(v uint64) uint64 {
|
||||
// 4 + 3 + 1 + 15 + 1 + 15 + 1
|
||||
// 0011
|
||||
// 0010 + PTS[30-32] + marker_bit + PTS[29-15] + marker_bit + PTS[14-0] + marker_bit
|
||||
pts1 := ((v >> 33) & 0x7) << 30
|
||||
pts2 := ((v >> 17) & 0x7fff) << 15
|
||||
pts3 := ((v >> 1) & 0x7fff)
|
||||
|
||||
return pts1 | pts2 | pts3
|
||||
}
|
||||
|
||||
func PutPtsDts(v uint64) uint64 {
|
||||
// 4 + 3 + 1 + 15 + 1 + 15 + 1
|
||||
// 0011
|
||||
// 0010 + PTS[30-32] + marker_bit + PTS[29-15] + marker_bit + PTS[14-0] + marker_bit
|
||||
// 0x100010001
|
||||
// 0001 0000 0000 0000 0001 0000 0000 0000 0001
|
||||
// 3个 market_it
|
||||
pts1 := (v >> 30) & 0x7 << 33
|
||||
pts2 := (v >> 15) & 0x7fff << 17
|
||||
pts3 := (v & 0x7fff) << 1
|
||||
|
||||
return pts1 | pts2 | pts3 | 0x100010001
|
||||
}
|
||||
|
||||
func GetPCR(v uint64) uint64 {
|
||||
// program_clock_reference_base(33) + Reserved(6) + program_clock_reference_extension(9)
|
||||
base := v >> 15
|
||||
ext := v & 0x1ff
|
||||
return base*300 + ext
|
||||
}
|
||||
|
||||
func PutPCR(pcr uint64) uint64 {
|
||||
base := pcr / 300
|
||||
ext := pcr % 300
|
||||
return base<<15 | 0x3f<<9 | ext
|
||||
}
|
||||
|
||||
func GetFillBytes(data byte, n int) []byte {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = data
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
func ToFloat64(num interface{}) float64 {
|
||||
switch v := num.(type) {
|
||||
case uint:
|
||||
return float64(v)
|
||||
case int:
|
||||
return float64(v)
|
||||
case uint8:
|
||||
return float64(v)
|
||||
case uint16:
|
||||
return float64(v)
|
||||
case uint32:
|
||||
return float64(v)
|
||||
case uint64:
|
||||
return float64(v)
|
||||
case int8:
|
||||
return float64(v)
|
||||
case int16:
|
||||
return float64(v)
|
||||
case int32:
|
||||
return float64(v)
|
||||
case int64:
|
||||
return float64(v)
|
||||
case float64:
|
||||
return v
|
||||
case float32:
|
||||
return float64(v)
|
||||
}
|
||||
return 0
|
||||
}
|
126
monica/util/crc32.go
Normal file
126
monica/util/crc32.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
var Crc32_Table = []uint32{
|
||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
|
||||
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
||||
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
|
||||
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
|
||||
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
|
||||
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
|
||||
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
|
||||
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
|
||||
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
|
||||
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
|
||||
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
|
||||
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
|
||||
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
|
||||
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
|
||||
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
|
||||
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
|
||||
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
|
||||
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
|
||||
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
|
||||
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
|
||||
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
|
||||
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
|
||||
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
|
||||
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
|
||||
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
|
||||
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
|
||||
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
|
||||
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
|
||||
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
|
||||
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
||||
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
|
||||
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
|
||||
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
|
||||
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
|
||||
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
|
||||
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
|
||||
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
|
||||
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
|
||||
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
|
||||
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
|
||||
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
|
||||
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
|
||||
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
|
||||
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
|
||||
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
|
||||
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
|
||||
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
|
||||
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
|
||||
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
|
||||
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
|
||||
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
|
||||
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
|
||||
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
|
||||
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
|
||||
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
|
||||
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
|
||||
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
|
||||
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
|
||||
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
|
||||
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
|
||||
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
|
||||
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
|
||||
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
|
||||
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
|
||||
}
|
||||
|
||||
type Crc32Reader struct {
|
||||
R io.Reader
|
||||
Crc32 uint32
|
||||
}
|
||||
|
||||
type Crc32Writer struct {
|
||||
W io.Writer
|
||||
Crc32 uint32
|
||||
}
|
||||
|
||||
func (cr *Crc32Reader) Read(b []byte) (n int, err error) {
|
||||
if n, err = cr.R.Read(b); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cr.Crc32 = getCrc32(cr.Crc32, b)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (cr *Crc32Reader) ReadCrc32UIntAndCheck() (err error) {
|
||||
_, err = io.CopyN(ioutil.Discard, cr, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cr.Crc32 != 0 {
|
||||
err = fmt.Errorf("crc32(%x) != 0", cr.Crc32)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wr *Crc32Writer) Write(b []byte) (n int, err error) {
|
||||
if n, err = wr.W.Write(b); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wr.Crc32 = getCrc32(wr.Crc32, b)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getCrc32(crc uint32, data []byte) uint32 {
|
||||
for _, v := range data {
|
||||
crc = Crc32_Table[v^byte(crc)] ^ (crc >> 8)
|
||||
}
|
||||
|
||||
return crc
|
||||
}
|
20
monica/util/stderr.go
Normal file
20
monica/util/stderr.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// +build linux
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
logFile, err := os.OpenFile("./fatal.log", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
log.Println("服务启动出错", "打开异常日志文件失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 将进程标准出错重定向至文件,进程崩溃时运行时将向该文件记录协程调用栈信息
|
||||
syscall.Dup2(int(logFile.Fd()), int(os.Stderr.Fd()))
|
||||
}
|
40
monica/util/util.go
Normal file
40
monica/util/util.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// 检查文件或目录是否存在
|
||||
// 如果由 filename 指定的文件或目录存在则返回 true,否则返回 false
|
||||
func Exist(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
return err == nil || os.IsExist(err)
|
||||
}
|
||||
|
||||
func ReadFileLines(filename string) (lines []string, err error) {
|
||||
file, err := os.OpenFile(filename, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
bio := bufio.NewReader(file)
|
||||
for {
|
||||
var line []byte
|
||||
|
||||
line, _, err = bio.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
file.Close()
|
||||
return lines, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
lines = append(lines, string(line))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
205
monica/util/vecio.go
Normal file
205
monica/util/vecio.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
|
||||
//"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <sys/uio.h>
|
||||
// Structure for scatter/gather I/O.
|
||||
struct iovec{
|
||||
void *iov_base; // Pointer to data.
|
||||
size_t iov_len; // Length of data.
|
||||
};
|
||||
*/
|
||||
|
||||
type SysIOVec struct {
|
||||
Base uintptr
|
||||
Length uint64
|
||||
}
|
||||
|
||||
type IOVec struct {
|
||||
Data [][]byte
|
||||
Length int
|
||||
index int
|
||||
}
|
||||
|
||||
func (iov *IOVec) Append(b []byte) {
|
||||
iov.Data = append(iov.Data, b)
|
||||
iov.Length += len(b)
|
||||
}
|
||||
|
||||
// Data模型:
|
||||
// index -> | Data[0][0] | Data[0][1] | Data[0][2] | ... | Data[0][n] |
|
||||
// | Data[1][0] | Data[1][1] | Data[1][2] | ... | Data[1][n] |
|
||||
// ......
|
||||
// | Data[n][0] | Data[n][1] | Data[n][2] | ... | Data[n][n] |
|
||||
//
|
||||
// index是下标
|
||||
|
||||
func (iov *IOVec) WriteTo(w io.Writer, n int) (written int, err error) {
|
||||
for n > 0 && iov.Length > 0 {
|
||||
data := iov.Data[iov.index]
|
||||
|
||||
// 用来存放每次需要写入的数据
|
||||
var b []byte
|
||||
|
||||
// 只会读n个字节,超出n个字节,不管
|
||||
// 如果iov.Data里面有1000个数据,可是每次只读184个字节,那么剩下的数据(856)重新放回Data
|
||||
if n > len(data) {
|
||||
b = data
|
||||
} else {
|
||||
b = data[:n]
|
||||
}
|
||||
|
||||
// n个字节后面的数据
|
||||
// 如果这时候n个字节后面已经没有数据了,我们就将下标index往后移一位
|
||||
// 否则我们将n个字节后面的数据重新放回Data里.
|
||||
data = data[len(b):]
|
||||
if len(data) == 0 {
|
||||
iov.index++
|
||||
} else {
|
||||
iov.Data[iov.index] = data
|
||||
}
|
||||
|
||||
n -= len(b)
|
||||
iov.Length -= len(b)
|
||||
written += len(b)
|
||||
|
||||
if _, err = w.Write(b); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type IOVecWriter struct {
|
||||
fd uintptr
|
||||
smallBuffer []byte
|
||||
sysIOV []SysIOVec
|
||||
}
|
||||
|
||||
func NewIOVecWriter(w io.Writer) (iow *IOVecWriter) {
|
||||
var err error
|
||||
var file *os.File
|
||||
|
||||
// TODO:是否要增加其他的类型断言
|
||||
switch value := w.(type) {
|
||||
case *net.TCPConn:
|
||||
{
|
||||
file, err = value.File()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
case *os.File:
|
||||
{
|
||||
file = value
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
iow = &IOVecWriter{
|
||||
fd: file.Fd(),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 1 2 3 4 5 6
|
||||
// --- -------------- --- --- --- -----------
|
||||
// | | | | | | | | | | | | ......
|
||||
// --- -------------- --- --- --- -----------
|
||||
//
|
||||
// 1 -> 5个字节, 3 -> 15个字节, 4 -> 10个字节, 5 -> 15个字节
|
||||
|
||||
// 1,3,4,5内存块太小(小于16个字节),因此我们将它组装起来为samllbuffer
|
||||
// 并且将Base置于每次组装smallBuffer前总长度的尾部.
|
||||
//
|
||||
// samllbuffer:
|
||||
// 1 3 4 5 ........
|
||||
// ------------------------------
|
||||
// | |
|
||||
// ------------------------------
|
||||
// <--> 第一个小内存块,假设地址为0xF10000
|
||||
// 5
|
||||
// <------> 第二个小内存块,假设地址为0xF20000
|
||||
// 20
|
||||
// <----------> 第三个小内存块,假设地址为0xF30000
|
||||
// 30
|
||||
// <--------------> 第四个小内存块,假设地址为0xF40000
|
||||
// 45
|
||||
//
|
||||
// 开始Base == 每次组装smallBuffer尾部
|
||||
// 即:
|
||||
// Base1 = 0, smallBuffer += 5,
|
||||
// Base3 = 5, smallBuffer += 15,
|
||||
// Base4 = 20, smallBuffer += 10,
|
||||
// Base5 = 30, smallBuffer += 15,
|
||||
//
|
||||
// 然后我们将每一块内存块都取出来,比samllBuffer小的内存块,我们就将Base指向内存块的地址
|
||||
// 之前小于16个字节的内存块,肯定会比smallBuffer小,因为smallBuffer是所有小内存快的总和.
|
||||
// 即:
|
||||
// Base1 = &smallBuffer[0], Base1 = 0xF10000,
|
||||
// Base3 = &smallBuffer[5], Base3 = 0xF20000,
|
||||
// Base4 = &smallBuffer[20], Base4 = 0xF30000,
|
||||
// Base5 = &smallBuffer[30], Base5 = 0xF40000,
|
||||
|
||||
func (iow *IOVecWriter) Write(data []byte) (written int, err error) {
|
||||
siov := SysIOVec{
|
||||
Length: uint64(len(data)),
|
||||
}
|
||||
|
||||
// unsafe.Pointer == void *
|
||||
// Base 用整数的形式来记录内存中有几个数据
|
||||
// 如果数据小于16,这个时候小块内存的Base还不是数据的内存地址
|
||||
if siov.Length < 16 {
|
||||
// Base 置于上一块samllBuffer的末尾
|
||||
// 然后拼接smallBuffer
|
||||
siov.Base = uintptr(len(iow.smallBuffer))
|
||||
iow.smallBuffer = append(iow.smallBuffer, data...)
|
||||
} else {
|
||||
siov.Base = uintptr(unsafe.Pointer(&data[0]))
|
||||
}
|
||||
|
||||
iow.sysIOV = append(iow.sysIOV, siov)
|
||||
|
||||
return written, nil
|
||||
}
|
||||
|
||||
func (iow *IOVecWriter) Flush() error {
|
||||
// 取出每一块内存
|
||||
for i, _ := range iow.sysIOV {
|
||||
siov := &iow.sysIOV[i] // 一定要拿地址,如果这里不是取地址,那么无法改变下面Base的值
|
||||
if siov.Base < uintptr(len(iow.smallBuffer)) {
|
||||
// 这个时候小块内存的Base就是数据的内存地址
|
||||
siov.Base = uintptr(unsafe.Pointer(&iow.smallBuffer[siov.Base]))
|
||||
}
|
||||
}
|
||||
|
||||
N := 1024
|
||||
count := len(iow.sysIOV)
|
||||
// 每次最多取1024个内存块(不管是大内存块,还是小内存块)
|
||||
for i := 0; i < count; i += N {
|
||||
n := count - i
|
||||
if n > N {
|
||||
n = N
|
||||
}
|
||||
|
||||
// _, _, errno := syscall.Syscall(syscall.SYS_WRITEV, iow.fd, uintptr(unsafe.Pointer(&iow.sysIOV[i])), uintptr(n))
|
||||
// if errno != 0 {
|
||||
// return errors.New(errno.Error())
|
||||
// }
|
||||
}
|
||||
|
||||
iow.sysIOV = iow.sysIOV[:0]
|
||||
iow.smallBuffer = iow.smallBuffer[:0]
|
||||
|
||||
return nil
|
||||
}
|
56
plugins/HDL/subscriber.go
Normal file
56
plugins/HDL/subscriber.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package HDL
|
||||
|
||||
import (
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"github.com/langhuihui/monibuca/monica/avformat"
|
||||
"github.com/langhuihui/monibuca/monica/pool"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var config = new(ListenerConfig)
|
||||
|
||||
func init() {
|
||||
InstallPlugin(&PluginConfig{
|
||||
Name: "HDL",
|
||||
Type: PLUGIN_SUBSCRIBER,
|
||||
Config: config,
|
||||
Run: run,
|
||||
})
|
||||
}
|
||||
|
||||
func run() {
|
||||
log.Printf("HDL start at %s", config.ListenAddr)
|
||||
log.Fatal(http.ListenAndServe(config.ListenAddr, http.HandlerFunc(HDLHandler)))
|
||||
}
|
||||
|
||||
func HDLHandler(w http.ResponseWriter, r *http.Request) {
|
||||
sign := r.URL.Query().Get("sign")
|
||||
if err := AuthHooks.Trigger(sign); err != nil {
|
||||
w.WriteHeader(403)
|
||||
return
|
||||
}
|
||||
stringPath := strings.TrimLeft(r.RequestURI, "/")
|
||||
if strings.HasSuffix(stringPath, ".flv") {
|
||||
stringPath = strings.TrimRight(stringPath, ".flv")
|
||||
}
|
||||
if _, ok := AllRoom.Load(stringPath); ok {
|
||||
//atomic.AddInt32(&hdlId, 1)
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
w.Header().Set("Content-Type", "video/x-flv")
|
||||
w.Write(avformat.FLVHeader)
|
||||
p := OutputStream{
|
||||
Sign: sign,
|
||||
SendHandler: func(packet *pool.SendPacket) error {
|
||||
return avformat.WriteFLVTag(w, packet)
|
||||
},
|
||||
SubscriberInfo: SubscriberInfo{
|
||||
ID: r.RemoteAddr, Type: "FLV",
|
||||
},
|
||||
}
|
||||
p.Play(stringPath)
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
}
|
158
plugins/HLS/TS.go
Normal file
158
plugins/HLS/TS.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package HLS
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"github.com/langhuihui/monibuca/monica/avformat"
|
||||
"github.com/langhuihui/monibuca/monica/avformat/mpegts"
|
||||
"github.com/langhuihui/monibuca/monica/pool"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TS struct {
|
||||
InputStream
|
||||
*mpegts.MpegTsStream
|
||||
TSInfo
|
||||
//TsChan chan io.Reader
|
||||
lastDts uint64
|
||||
}
|
||||
type TSInfo struct {
|
||||
TotalPesCount int
|
||||
IsSplitFrame bool
|
||||
PTS uint64
|
||||
DTS uint64
|
||||
PesCount int
|
||||
BufferLength int
|
||||
RoomInfo *RoomInfo
|
||||
}
|
||||
|
||||
func (ts *TS) run() {
|
||||
//defer close(ts.TsChan)
|
||||
totalBuffer := cap(ts.TsPesPktChan)
|
||||
iframeHead := []byte{0x17, 0x01, 0, 0, 0}
|
||||
pframeHead := []byte{0x27, 0x01, 0, 0, 0}
|
||||
spsHead := []byte{0xE1, 0, 0}
|
||||
ppsHead := []byte{0x01, 0, 0}
|
||||
nalLength := []byte{0, 0, 0, 0}
|
||||
for {
|
||||
select {
|
||||
case <-ts.Done():
|
||||
return
|
||||
case tsPesPkt, ok := <-ts.TsPesPktChan:
|
||||
ts.BufferLength = len(ts.TsPesPktChan)
|
||||
if ok {
|
||||
ts.TotalPesCount++
|
||||
switch tsPesPkt.PesPkt.Header.StreamID & 0xF0 {
|
||||
case mpegts.STREAM_ID_AUDIO:
|
||||
av := pool.NewAVPacket(avformat.FLV_TAG_TYPE_AUDIO)
|
||||
av.Payload = tsPesPkt.PesPkt.Payload
|
||||
ts.PushAudio(av)
|
||||
case mpegts.STREAM_ID_VIDEO:
|
||||
var err error
|
||||
av := pool.NewAVPacket(avformat.FLV_TAG_TYPE_VIDEO)
|
||||
ts.PTS = tsPesPkt.PesPkt.Header.Pts
|
||||
ts.DTS = tsPesPkt.PesPkt.Header.Dts
|
||||
lastDts := ts.lastDts
|
||||
dts := ts.DTS
|
||||
pts := ts.PTS
|
||||
if dts == 0 {
|
||||
dts = pts
|
||||
}
|
||||
av.Timestamp = uint32(dts / 90)
|
||||
if ts.lastDts == 0 {
|
||||
ts.lastDts = dts
|
||||
}
|
||||
compostionTime := uint32((pts - dts) / 90)
|
||||
t1 := time.Now()
|
||||
duration := time.Millisecond * time.Duration((dts-ts.lastDts)/90)
|
||||
ts.lastDts = dts
|
||||
nalus0 := bytes.SplitN(tsPesPkt.PesPkt.Payload, avformat.NALU_Delimiter2, -1)
|
||||
nalus := make([][]byte, 0)
|
||||
for _, v := range nalus0 {
|
||||
if len(v) == 0 {
|
||||
continue
|
||||
}
|
||||
nalus = append(nalus, bytes.SplitN(v, avformat.NALU_Delimiter1, -1)...)
|
||||
}
|
||||
r := bytes.NewBuffer([]byte{})
|
||||
for _, v := range nalus {
|
||||
vl := len(v)
|
||||
if vl == 0 {
|
||||
continue
|
||||
}
|
||||
isFirst := v[1]&0x80 == 0x80 //第一个分片
|
||||
switch v[0] & 0x1f {
|
||||
case avformat.NALU_SPS:
|
||||
r.Write(avformat.RTMP_AVC_HEAD)
|
||||
util.BigEndian.PutUint16(spsHead[1:], uint16(vl))
|
||||
_, err = r.Write(spsHead)
|
||||
case avformat.NALU_PPS:
|
||||
util.BigEndian.PutUint16(ppsHead[1:], uint16(vl))
|
||||
_, err = r.Write(ppsHead)
|
||||
_, err = r.Write(v)
|
||||
av.VideoFrameType = 1
|
||||
av.Payload = r.Bytes()
|
||||
ts.PushVideo(av)
|
||||
av = pool.NewAVPacket(avformat.FLV_TAG_TYPE_VIDEO)
|
||||
av.Timestamp = uint32(dts / 90)
|
||||
r = bytes.NewBuffer([]byte{})
|
||||
continue
|
||||
case avformat.NALU_IDR_Picture:
|
||||
if isFirst {
|
||||
av.VideoFrameType = 1
|
||||
util.BigEndian.PutUint24(iframeHead[2:], compostionTime)
|
||||
_, err = r.Write(iframeHead)
|
||||
}
|
||||
util.BigEndian.PutUint32(nalLength, uint32(vl))
|
||||
_, err = r.Write(nalLength)
|
||||
case avformat.NALU_Non_IDR_Picture:
|
||||
if isFirst {
|
||||
av.VideoFrameType = 2
|
||||
util.BigEndian.PutUint24(pframeHead[2:], compostionTime)
|
||||
_, err = r.Write(iframeHead)
|
||||
} else {
|
||||
ts.IsSplitFrame = true
|
||||
}
|
||||
util.BigEndian.PutUint32(nalLength, uint32(vl))
|
||||
_, err = r.Write(nalLength)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
_, err = r.Write(v)
|
||||
}
|
||||
if MayBeError(err) {
|
||||
return
|
||||
}
|
||||
av.Payload = r.Bytes()
|
||||
ts.PushVideo(av)
|
||||
t2 := time.Since(t1)
|
||||
if duration != 0 && t2 < duration {
|
||||
if duration < time.Second {
|
||||
//if ts.BufferLength > 50 {
|
||||
duration = duration - t2
|
||||
//}
|
||||
if ts.BufferLength > 150 {
|
||||
duration = duration - duration*time.Duration(ts.BufferLength)/time.Duration(totalBuffer)
|
||||
}
|
||||
time.Sleep(duration)
|
||||
} else {
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
log.Printf("stream:%s,duration:%d,dts:%d,lastDts:%d\n", ts.StreamName, duration/time.Millisecond, tsPesPkt.PesPkt.Header.Dts, lastDts)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TS) Publish(streamName string, publisher Publisher) (result bool) {
|
||||
if result = ts.InputStream.Publish(streamName, publisher); result {
|
||||
ts.TSInfo.RoomInfo = &ts.Room.RoomInfo
|
||||
ts.MpegTsStream = mpegts.NewMpegTsStream(2048)
|
||||
go ts.run()
|
||||
}
|
||||
return
|
||||
}
|
144
plugins/HLS/m3u8.go
Normal file
144
plugins/HLS/m3u8.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package HLS
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"github.com/quangngotan95/go-m3u8/m3u8"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HLS struct {
|
||||
TS
|
||||
HLSInfo
|
||||
TsHead http.Header
|
||||
SaveContext context.Context
|
||||
}
|
||||
type HLSInfo struct {
|
||||
Video M3u8Info
|
||||
Audio M3u8Info
|
||||
TSInfo *TSInfo
|
||||
}
|
||||
type M3u8Info struct {
|
||||
Req *http.Request `json:"-"`
|
||||
M3U8Count int
|
||||
TSCount int
|
||||
LastM3u8 string
|
||||
M3u8Info []TSCost
|
||||
}
|
||||
type TSCost struct {
|
||||
DownloadCost int
|
||||
DecodeCost int
|
||||
BufferLength int
|
||||
}
|
||||
|
||||
func readM3U8(res *http.Response) (playlist *m3u8.Playlist, err error) {
|
||||
var reader io.Reader = res.Body
|
||||
if res.Header.Get("Content-Encoding") == "gzip" {
|
||||
reader, err = gzip.NewReader(reader)
|
||||
}
|
||||
if err == nil {
|
||||
playlist, err = m3u8.Read(reader)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("readM3U8 error:%s", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
func (p *HLS) run(info *M3u8Info) {
|
||||
client := http.Client{Timeout: time.Second * 5}
|
||||
sequence := 0
|
||||
lastTs := make(map[string]bool)
|
||||
resp, err := client.Do(info.Req)
|
||||
defer func() {
|
||||
log.Printf("hls %s exit:%v", p.StreamName, err)
|
||||
p.Cancel()
|
||||
}()
|
||||
for ; err == nil && p.Err() == nil; resp, err = client.Do(info.Req) {
|
||||
if playlist, err := readM3U8(resp); err == nil {
|
||||
|
||||
info.LastM3u8 = playlist.String()
|
||||
//if !playlist.Live {
|
||||
// log.Println(p.LastM3u8)
|
||||
// return
|
||||
//}
|
||||
if playlist.Sequence <= sequence {
|
||||
log.Printf("same sequence:%d,max:%d", playlist.Sequence, sequence)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
info.M3U8Count++
|
||||
sequence = playlist.Sequence
|
||||
thisTs := make(map[string]bool)
|
||||
tsItems := make([]*m3u8.SegmentItem, 0)
|
||||
discontinuity := false
|
||||
for _, item := range playlist.Items {
|
||||
switch v := item.(type) {
|
||||
case *m3u8.DiscontinuityItem:
|
||||
discontinuity = true
|
||||
case *m3u8.SegmentItem:
|
||||
thisTs[v.Segment] = true
|
||||
if _, ok := lastTs[v.Segment]; ok && !discontinuity {
|
||||
continue
|
||||
}
|
||||
tsItems = append(tsItems, v)
|
||||
}
|
||||
}
|
||||
lastTs = thisTs
|
||||
if len(tsItems) > 3 {
|
||||
tsItems = tsItems[len(tsItems)-3:]
|
||||
}
|
||||
info.M3u8Info = make([]TSCost, len(tsItems))
|
||||
for i, v := range tsItems {
|
||||
tsCost := TSCost{}
|
||||
tsUrl, _ := info.Req.URL.Parse(v.Segment)
|
||||
tsReq, _ := http.NewRequest("GET", tsUrl.String(), nil)
|
||||
tsReq.Header = p.TsHead
|
||||
t1 := time.Now()
|
||||
if tsRes, err := client.Do(tsReq); err == nil && p.Err() == nil {
|
||||
info.TSCount++
|
||||
if body, err := ioutil.ReadAll(tsRes.Body); err == nil && p.Err() == nil {
|
||||
tsCost.DownloadCost = int(time.Since(t1) / time.Millisecond)
|
||||
if p.SaveContext != nil && p.SaveContext.Err() == nil {
|
||||
err = ioutil.WriteFile(filepath.Base(tsUrl.Path), body, 0666)
|
||||
}
|
||||
t1 = time.Now()
|
||||
beginLen := len(p.TsPesPktChan)
|
||||
if err = p.Feed(bytes.NewReader(body)); p.Err() != nil {
|
||||
close(p.TsPesPktChan)
|
||||
}
|
||||
tsCost.DecodeCost = int(time.Since(t1) / time.Millisecond)
|
||||
tsCost.BufferLength = len(p.TsPesPktChan)
|
||||
p.PesCount = tsCost.BufferLength - beginLen
|
||||
} else if err != nil {
|
||||
log.Printf("%s readTs:%v", p.StreamName, err)
|
||||
}
|
||||
} else if err != nil {
|
||||
log.Printf("%s reqTs:%v", p.StreamName, err)
|
||||
}
|
||||
info.M3u8Info[i] = tsCost
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * time.Duration(playlist.Target) * 2)
|
||||
} else {
|
||||
log.Printf("%s readM3u8:%v", p.StreamName, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
func (p *HLS) Publish(streamName string, publisher Publisher) (result bool) {
|
||||
if result = p.TS.Publish(streamName, publisher); result {
|
||||
p.HLSInfo.TSInfo = &p.TS.TSInfo
|
||||
go p.run(&p.HLSInfo.Video)
|
||||
if p.HLSInfo.Audio.Req != nil {
|
||||
go p.run(&p.HLSInfo.Audio)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
46
plugins/QoS/index.go
Normal file
46
plugins/QoS/index.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package QoS
|
||||
|
||||
import (
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
)
|
||||
|
||||
var (
|
||||
selectMap = map[string][]string{
|
||||
"low": {"low", "medium", "high"},
|
||||
"medium": {"medium", "low", "high"},
|
||||
"high": {"high", "medium", "low"},
|
||||
}
|
||||
)
|
||||
|
||||
func getQualityName(name string, qualityLevel string) string {
|
||||
if qualityLevel == "" {
|
||||
return name
|
||||
}
|
||||
for _, l := range selectMap[qualityLevel] {
|
||||
if _, ok := AllRoom.Load(name + "/" + l); ok {
|
||||
return name + "/" + l
|
||||
}
|
||||
}
|
||||
return name + "/" + qualityLevel
|
||||
}
|
||||
|
||||
var config = struct {
|
||||
Suffix []string
|
||||
}{}
|
||||
|
||||
func init() {
|
||||
InstallPlugin(&PluginConfig{
|
||||
Name: "QoS",
|
||||
Type: PLUGIN_HOOK,
|
||||
Config: &config,
|
||||
Run: run,
|
||||
})
|
||||
}
|
||||
func run() {
|
||||
OnDropHooks.AddHook(func(s *OutputStream) {
|
||||
if s.TotalDrop > s.TotalPacket>>2 {
|
||||
//TODO
|
||||
//s.Control<-&ChangeRoomCmd{s,AllRoom.Get()}
|
||||
}
|
||||
})
|
||||
}
|
82
plugins/auth/index.go
Normal file
82
plugins/auth/index.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
signs = make(map[string]time.Time)
|
||||
signChan = make(chan string)
|
||||
config = struct {
|
||||
Key string
|
||||
}{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
InstallPlugin(&PluginConfig{
|
||||
Name: "Auth",
|
||||
Type: PLUGIN_HOOK,
|
||||
Config: &config,
|
||||
Run: ClearSignCache,
|
||||
})
|
||||
AuthHooks.AddHook(CheckSign)
|
||||
OnPublishHooks.AddHook(onPublish)
|
||||
}
|
||||
|
||||
func onPublish(r *Room) {
|
||||
for _, v := range r.Subscribers {
|
||||
if err := CheckSign(v.Sign); err != nil {
|
||||
log.Printf("%s in room %s:%v", v.ID, r.StreamName, err)
|
||||
v.Cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckSign 校验格式
|
||||
func CheckSign(sign string) error {
|
||||
hexBytes, err := hex.DecodeString(sign)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sign is not hex format %s", sign)
|
||||
}
|
||||
originString := string(decryptAES(hexBytes, []byte(config.Key)))
|
||||
if strings.HasPrefix(originString, config.Key) {
|
||||
if the_time, err := time.Parse("2006-01-02 15:04:05", originString[len(config.Key):]); err != nil {
|
||||
return err
|
||||
} else if time.Now().Sub(the_time).Hours() < 1 {
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("sign has been overdue")
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("sign does not HasPrefix %s", config.Key)
|
||||
}
|
||||
}
|
||||
|
||||
// ClearSignCache 删除过期数据
|
||||
func ClearSignCache() {
|
||||
for {
|
||||
select {
|
||||
case now := <-time.After(time.Minute):
|
||||
for sign, t := range signs {
|
||||
if now.Sub(t).Hours() > 1 {
|
||||
delete(signs, sign)
|
||||
}
|
||||
}
|
||||
case sign := <-signChan:
|
||||
signs[sign] = time.Now()
|
||||
}
|
||||
}
|
||||
}
|
||||
func decryptAES(src []byte, key []byte) []byte {
|
||||
block, _ := aes.NewCipher(key)
|
||||
cipher.NewCBCDecrypter(block, key).CryptBlocks(src, src)
|
||||
n := len(src)
|
||||
return src[:n-int(src[n-1])]
|
||||
}
|
43
plugins/cluster/index.go
Normal file
43
plugins/cluster/index.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"log"
|
||||
)
|
||||
|
||||
const (
|
||||
_ byte = iota
|
||||
MSG_AUDIO
|
||||
MSG_VIDEO
|
||||
MSG_SUBSCRIBE
|
||||
MSG_AUTH
|
||||
)
|
||||
|
||||
var config = struct {
|
||||
Master string
|
||||
ListenAddr string
|
||||
}{}
|
||||
|
||||
func init() {
|
||||
InstallPlugin(&PluginConfig{
|
||||
Name: "Cluster",
|
||||
Type: PLUGIN_HOOK | PLUGIN_PUBLISHER | PLUGIN_SUBSCRIBER,
|
||||
Config: &config,
|
||||
Run: run,
|
||||
})
|
||||
}
|
||||
func run() {
|
||||
if config.Master != "" {
|
||||
OnSubscribeHooks.AddHook(onSubscribe)
|
||||
}
|
||||
if config.ListenAddr != "" {
|
||||
log.Printf("server bare start at %s", config.ListenAddr)
|
||||
log.Fatal(ListenBare(config.ListenAddr))
|
||||
}
|
||||
}
|
||||
|
||||
func onSubscribe(s *OutputStream) {
|
||||
if s.Publisher == nil {
|
||||
go PullUpStream(s.StreamName)
|
||||
}
|
||||
}
|
107
plugins/cluster/publisher.go
Normal file
107
plugins/cluster/publisher.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"github.com/langhuihui/monibuca/monica/avformat"
|
||||
"github.com/langhuihui/monibuca/monica/pool"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Receiver struct {
|
||||
InputStream
|
||||
io.Reader
|
||||
*bufio.Writer
|
||||
}
|
||||
|
||||
func (p *Receiver) Auth(authSub *OutputStream) {
|
||||
p.WriteByte(MSG_AUTH)
|
||||
p.WriteString(authSub.ID + "," + authSub.Sign)
|
||||
p.WriteByte(0)
|
||||
p.Flush()
|
||||
}
|
||||
|
||||
func (p *Receiver) readAVPacket(avType byte) (av *pool.AVPacket, err error) {
|
||||
buf := pool.GetSlice(4)
|
||||
_, err = io.ReadFull(p, buf)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
return
|
||||
}
|
||||
av = pool.NewAVPacket(avType)
|
||||
av.Timestamp = binary.BigEndian.Uint32(buf)
|
||||
_, err = io.ReadFull(p, buf)
|
||||
if MayBeError(err) {
|
||||
return
|
||||
}
|
||||
av.Payload = pool.GetSlice(int(binary.BigEndian.Uint32(buf)))
|
||||
_, err = io.ReadFull(p, av.Payload)
|
||||
if MayBeError(err) {
|
||||
return
|
||||
}
|
||||
pool.RecycleSlice(buf)
|
||||
return
|
||||
}
|
||||
func PullUpStream(streamPath string) {
|
||||
addr, err := net.ResolveTCPAddr("tcp", config.Master)
|
||||
if MayBeError(err) {
|
||||
return
|
||||
}
|
||||
conn, err := net.DialTCP("tcp", nil, addr)
|
||||
if MayBeError(err) {
|
||||
return
|
||||
}
|
||||
brw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
|
||||
p := &Receiver{
|
||||
Reader: conn,
|
||||
Writer: brw.Writer,
|
||||
}
|
||||
if p.Publish(streamPath, p) {
|
||||
p.WriteByte(MSG_SUBSCRIBE)
|
||||
p.WriteString(streamPath)
|
||||
p.WriteByte(0)
|
||||
p.Flush()
|
||||
for _, v := range p.Subscribers {
|
||||
p.Auth(v)
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
defer p.Cancel()
|
||||
for {
|
||||
cmd, err := brw.ReadByte()
|
||||
if MayBeError(err) {
|
||||
return
|
||||
}
|
||||
switch cmd {
|
||||
case MSG_AUDIO:
|
||||
if audio, err := p.readAVPacket(avformat.FLV_TAG_TYPE_AUDIO); err == nil {
|
||||
p.PushAudio(audio)
|
||||
}
|
||||
case MSG_VIDEO:
|
||||
if video, err := p.readAVPacket(avformat.FLV_TAG_TYPE_VIDEO); err == nil && len(video.Payload) > 2 {
|
||||
tmp := video.Payload[0] // 第一个字节保存着视频的相关信息.
|
||||
video.VideoFrameType = tmp >> 4 // 帧类型 4Bit, H264一般为1或者2
|
||||
p.PushVideo(video)
|
||||
}
|
||||
case MSG_AUTH:
|
||||
cmd, err = brw.ReadByte()
|
||||
if MayBeError(err) {
|
||||
return
|
||||
}
|
||||
bytes, err := brw.ReadBytes(0)
|
||||
if MayBeError(err) {
|
||||
return
|
||||
}
|
||||
subId := strings.Split(string(bytes[0:len(bytes)-1]), ",")[0]
|
||||
if v, ok := p.Subscribers[subId]; ok {
|
||||
if cmd != 1 {
|
||||
v.Cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
106
plugins/cluster/subscriber.go
Normal file
106
plugins/cluster/subscriber.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"github.com/langhuihui/monibuca/monica/pool"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ListenBare(addr string) error {
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if MayBeError(err) {
|
||||
return err
|
||||
}
|
||||
var tempDelay time.Duration
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
println(conn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Temporary() {
|
||||
if tempDelay == 0 {
|
||||
tempDelay = 5 * time.Millisecond
|
||||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
if max := 1 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
}
|
||||
println("bare: Accept error: " + err.Error() + "; retrying in " + strconv.FormatFloat(tempDelay.Seconds(), 'f', 2, 64))
|
||||
// fmt.Printf("bare: Accept error: %v; retrying in %v", err, tempDelay)
|
||||
time.Sleep(tempDelay)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
tempDelay = 0
|
||||
|
||||
go process(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func process(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
reader := bufio.NewReader(conn)
|
||||
stream := OutputStream{
|
||||
SendHandler: func(p *pool.SendPacket) error {
|
||||
head := pool.GetSlice(9)
|
||||
head[0] = p.Packet.Type - 7
|
||||
binary.BigEndian.PutUint32(head[1:5], p.Timestamp)
|
||||
binary.BigEndian.PutUint32(head[5:9], uint32(len(p.Packet.Payload)))
|
||||
if _, err := conn.Write(head); err != nil {
|
||||
return err
|
||||
}
|
||||
pool.RecycleSlice(head)
|
||||
if _, err := conn.Write(p.Packet.Payload); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, SubscriberInfo: SubscriberInfo{
|
||||
ID: conn.RemoteAddr().String(),
|
||||
Type: "Bare",
|
||||
},
|
||||
}
|
||||
for {
|
||||
cmd, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch cmd {
|
||||
case MSG_SUBSCRIBE:
|
||||
if stream.Room != nil {
|
||||
fmt.Printf("bare stream already exist from %s", conn.RemoteAddr())
|
||||
return
|
||||
}
|
||||
bytes, err := reader.ReadBytes(0)
|
||||
if MayBeError(err) {
|
||||
return
|
||||
}
|
||||
streamName := string(bytes[0 : len(bytes)-1])
|
||||
stream.Play(streamName)
|
||||
case MSG_AUTH:
|
||||
bytes, err := reader.ReadBytes(0)
|
||||
if err != nil {
|
||||
print(err)
|
||||
return
|
||||
}
|
||||
sign := strings.Split(string(bytes[0:len(bytes)-1]), ",")
|
||||
head := []byte{MSG_AUTH, 0}
|
||||
if len(sign) > 1 && AuthHooks.Trigger(sign[1]) == nil {
|
||||
head[1] = 1
|
||||
}
|
||||
conn.Write(head)
|
||||
conn.Write(bytes)
|
||||
default:
|
||||
fmt.Printf("bare receive unknown cmd:%d from %s", cmd, conn.RemoteAddr())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
13
plugins/default.go
Normal file
13
plugins/default.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
_ "github.com/langhuihui/monibuca/plugins/HDL"
|
||||
_ "github.com/langhuihui/monibuca/plugins/HLS"
|
||||
_ "github.com/langhuihui/monibuca/plugins/QoS"
|
||||
_ "github.com/langhuihui/monibuca/plugins/auth"
|
||||
_ "github.com/langhuihui/monibuca/plugins/cluster"
|
||||
_ "github.com/langhuihui/monibuca/plugins/gateway"
|
||||
_ "github.com/langhuihui/monibuca/plugins/jessica"
|
||||
_ "github.com/langhuihui/monibuca/plugins/record"
|
||||
_ "github.com/langhuihui/monibuca/plugins/rtmp"
|
||||
)
|
156
plugins/gateway/index.go
Normal file
156
plugins/gateway/index.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
"github.com/shirou/gopsutil/net"
|
||||
"log"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
config = new(ListenerConfig)
|
||||
sseBegin = []byte("data: ")
|
||||
sseEnd = []byte("\n\n")
|
||||
)
|
||||
|
||||
type SSE struct {
|
||||
http.ResponseWriter
|
||||
context.Context
|
||||
}
|
||||
|
||||
func (sse *SSE) Write(data []byte) (n int, err error) {
|
||||
if err = sse.Err(); err != nil {
|
||||
return
|
||||
}
|
||||
_, err = sse.ResponseWriter.Write(sseBegin)
|
||||
n, err = sse.ResponseWriter.Write(data)
|
||||
_, err = sse.ResponseWriter.Write(sseEnd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sse.ResponseWriter.(http.Flusher).Flush()
|
||||
return
|
||||
}
|
||||
func NewSSE(w http.ResponseWriter, ctx context.Context) *SSE {
|
||||
header := w.Header()
|
||||
header.Set("Content-Type", "text/event-stream")
|
||||
header.Set("Cache-Control", "no-cache")
|
||||
header.Set("Connection", "keep-alive")
|
||||
header.Set("X-Accel-Buffering", "no")
|
||||
header.Set("Access-Control-Allow-Origin", "*")
|
||||
return &SSE{
|
||||
w,
|
||||
ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (sse *SSE) WriteJSON(data interface{}) (err error) {
|
||||
var jsonData []byte
|
||||
if jsonData, err = json.Marshal(data); err == nil {
|
||||
if _, err = sse.Write(jsonData); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
func (sse *SSE) WriteExec(cmd *exec.Cmd) error {
|
||||
cmd.Stderr = sse
|
||||
cmd.Stdout = sse
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func init() {
|
||||
InstallPlugin(&PluginConfig{
|
||||
Name: "GateWay",
|
||||
Type: PLUGIN_HOOK,
|
||||
Config: config,
|
||||
Run: run,
|
||||
})
|
||||
}
|
||||
func run() {
|
||||
http.HandleFunc("/api/summary", summary)
|
||||
log.Printf("server api start at %s", config.ListenAddr)
|
||||
log.Fatal(http.ListenAndServe(config.ListenAddr, nil))
|
||||
}
|
||||
func summary(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
type Summary struct {
|
||||
Memory struct {
|
||||
Total uint64
|
||||
Free uint64
|
||||
Used uint64
|
||||
Usage float64
|
||||
}
|
||||
CPUUsage float64
|
||||
HardDisk struct {
|
||||
Total uint64
|
||||
Free uint64
|
||||
Used uint64
|
||||
Usage float64
|
||||
}
|
||||
NetWork []NetWorkInfo
|
||||
}
|
||||
type NetWorkInfo struct {
|
||||
Name string
|
||||
Receive uint64
|
||||
Sent uint64
|
||||
ReceiveSpeed uint64
|
||||
SentSpeed uint64
|
||||
}
|
||||
|
||||
func collect() (s Summary) {
|
||||
v, _ := mem.VirtualMemory()
|
||||
//c, _ := cpu.Info()
|
||||
cc, _ := cpu.Percent(time.Second, false)
|
||||
d, _ := disk.Usage("/")
|
||||
//n, _ := host.Info()
|
||||
nv, _ := net.IOCounters(true)
|
||||
//boottime, _ := host.BootTime()
|
||||
//btime := time.Unix(int64(boottime), 0).Format("2006-01-02 15:04:05")
|
||||
s.Memory.Total = v.Total / 1024 / 1024
|
||||
s.Memory.Free = v.Available / 1024 / 1024
|
||||
s.Memory.Used = v.Used / 1024 / 1024
|
||||
s.Memory.Usage = v.UsedPercent
|
||||
//fmt.Printf(" Mem : %v MB Free: %v MB Used:%v Usage:%f%%\n", v.Total/1024/1024, v.Available/1024/1024, v.Used/1024/1024, v.UsedPercent)
|
||||
//if len(c) > 1 {
|
||||
// for _, sub_cpu := range c {
|
||||
// modelname := sub_cpu.ModelName
|
||||
// cores := sub_cpu.Cores
|
||||
// fmt.Printf(" CPU : %v %v cores \n", modelname, cores)
|
||||
// }
|
||||
//} else {
|
||||
// sub_cpu := c[0]
|
||||
// modelname := sub_cpu.ModelName
|
||||
// cores := sub_cpu.Cores
|
||||
// fmt.Printf(" CPU : %v %v cores \n", modelname, cores)
|
||||
//}
|
||||
s.CPUUsage = cc[0]
|
||||
s.HardDisk.Free = d.Free / 1024 / 1024 / 1024
|
||||
s.HardDisk.Total = d.Total / 1024 / 1024 / 1024
|
||||
s.HardDisk.Used = d.Used / 1024 / 1024 / 1024
|
||||
s.HardDisk.Usage = d.UsedPercent
|
||||
s.NetWork = make([]NetWorkInfo, len(nv))
|
||||
for i, n := range nv {
|
||||
s.NetWork[i].Name = n.Name
|
||||
s.NetWork[i].Receive = n.BytesRecv
|
||||
s.NetWork[i].Sent = n.BytesSent
|
||||
}
|
||||
|
||||
//fmt.Printf(" Network: %v bytes / %v bytes\n", nv[0].BytesRecv, nv[0].BytesSent)
|
||||
//fmt.Printf(" SystemBoot:%v\n", btime)
|
||||
//fmt.Printf(" CPU Used : used %f%% \n", cc[0])
|
||||
//fmt.Printf(" HD : %v GB Free: %v GB Usage:%f%%\n", d.Total/1024/1024/1024, d.Free/1024/1024/1024, d.UsedPercent)
|
||||
//fmt.Printf(" OS : %v(%v) %v \n", n.Platform, n.PlatformFamily, n.PlatformVersion)
|
||||
//fmt.Printf(" Hostname : %v \n", n.Hostname)
|
||||
return
|
||||
}
|
22
plugins/jessica/index.go
Normal file
22
plugins/jessica/index.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package jessica
|
||||
|
||||
import (
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var config = new(ListenerConfig)
|
||||
|
||||
func init() {
|
||||
InstallPlugin(&PluginConfig{
|
||||
Name: "Jessica",
|
||||
Type: PLUGIN_SUBSCRIBER,
|
||||
Config: config,
|
||||
Run: run,
|
||||
})
|
||||
}
|
||||
func run() {
|
||||
log.Printf("server Jessica start at %s", config.ListenAddr)
|
||||
log.Fatal(http.ListenAndServe(config.ListenAddr, http.HandlerFunc(WsHandler)))
|
||||
}
|
75
plugins/jessica/subscriber.go
Normal file
75
plugins/jessica/subscriber.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package jessica
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/gobwas/ws"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"github.com/langhuihui/monibuca/monica/avformat"
|
||||
"github.com/langhuihui/monibuca/monica/pool"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func WsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
sign := r.URL.Query().Get("sign")
|
||||
isFlv := false
|
||||
if err := AuthHooks.Trigger(sign); err != nil {
|
||||
w.WriteHeader(403)
|
||||
return
|
||||
}
|
||||
streamPath := strings.TrimLeft(r.RequestURI, "/")
|
||||
if strings.HasSuffix(streamPath, ".flv") {
|
||||
streamPath = strings.TrimRight(streamPath, ".flv")
|
||||
isFlv = true
|
||||
}
|
||||
conn, _, _, err := ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
baseStream := OutputStream{Sign: sign}
|
||||
baseStream.ID = conn.RemoteAddr().String()
|
||||
defer conn.Close()
|
||||
if isFlv {
|
||||
baseStream.Type = "JessicaFlv"
|
||||
baseStream.SendHandler = func(packet *pool.SendPacket) error {
|
||||
return avformat.WriteFLVTag(conn, packet)
|
||||
}
|
||||
if err := ws.WriteHeader(conn, ws.Header{
|
||||
Fin: true,
|
||||
OpCode: ws.OpBinary,
|
||||
Length: int64(13),
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = conn.Write(avformat.FLVHeader); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
baseStream.Type = "Jessica"
|
||||
baseStream.SendHandler = func(packet *pool.SendPacket) error {
|
||||
err := ws.WriteHeader(conn, ws.Header{
|
||||
Fin: true,
|
||||
OpCode: ws.OpBinary,
|
||||
Length: int64(len(packet.Packet.Payload) + 5),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
head := pool.GetSlice(5)
|
||||
head[0] = packet.Packet.Type - 7
|
||||
binary.BigEndian.PutUint32(head[1:5], packet.Timestamp)
|
||||
if _, err = conn.Write(head); err != nil {
|
||||
return err
|
||||
}
|
||||
pool.RecycleSlice(head)
|
||||
//if p.Header[0] == 2 {
|
||||
// fmt.Printf("%6d %X\n", (uint32(p.Packet.Payload[5])<<24)|(uint32(p.Packet.Payload[6])<<16)|(uint32(p.Packet.Payload[7])<<8)|uint32(p.Packet.Payload[8]), p.Packet.Payload[9])
|
||||
//}
|
||||
if _, err = conn.Write(packet.Packet.Payload); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
baseStream.Play(streamPath)
|
||||
}
|
53
plugins/record/flv.go
Normal file
53
plugins/record/flv.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package record
|
||||
|
||||
import (
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"github.com/langhuihui/monibuca/monica/avformat"
|
||||
"github.com/langhuihui/monibuca/monica/pool"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func SaveFlv(streamPath string, append bool) error {
|
||||
flag := os.O_CREATE
|
||||
if append {
|
||||
flag = flag | os.O_RDWR | os.O_APPEND
|
||||
} else {
|
||||
flag = flag | os.O_TRUNC | os.O_WRONLY
|
||||
}
|
||||
filePath := config.Path + streamPath + ".flv"
|
||||
file, err := os.OpenFile(filePath, flag, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
p := OutputStream{SendHandler: func(packet *pool.SendPacket) error {
|
||||
return avformat.WriteFLVTag(file, packet)
|
||||
}}
|
||||
p.ID = filePath
|
||||
p.Type = "FlvRecord"
|
||||
if append {
|
||||
_, err = file.Seek(4, syscall.FILE_END)
|
||||
if err == nil {
|
||||
var tagSize uint32
|
||||
if tagSize, err = util.ReadByteToUint32(file, true); err == nil {
|
||||
_, err = file.Seek(int64(tagSize+4), syscall.FILE_END)
|
||||
if err == nil {
|
||||
var tag *pool.AVPacket
|
||||
tag, err = avformat.ReadFLVTag(file)
|
||||
if err == nil {
|
||||
p.OffsetTime = tag.Timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err = file.Write(avformat.FLVHeader)
|
||||
}
|
||||
if err == nil {
|
||||
recordings.Store(filePath, &p)
|
||||
go p.Play(streamPath)
|
||||
}
|
||||
return err
|
||||
}
|
49
plugins/record/index.go
Normal file
49
plugins/record/index.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package record
|
||||
|
||||
import (
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var config = struct {
|
||||
Path string
|
||||
}{}
|
||||
var recordings = sync.Map{}
|
||||
|
||||
func init() {
|
||||
InstallPlugin(&PluginConfig{
|
||||
Name: "RecordFlv",
|
||||
Type: PLUGIN_SUBSCRIBER,
|
||||
Config: &config,
|
||||
Run: run,
|
||||
})
|
||||
}
|
||||
func run() {
|
||||
http.HandleFunc("/api/record/flv", func(writer http.ResponseWriter, r *http.Request) {
|
||||
if streamPath := r.URL.Query().Get("streamPath"); streamPath != "" {
|
||||
if err := SaveFlv(streamPath, r.URL.Query().Get("append") != ""); err != nil {
|
||||
writer.Write([]byte(err.Error()))
|
||||
} else {
|
||||
writer.Write([]byte("success"))
|
||||
}
|
||||
} else {
|
||||
writer.Write([]byte("no streamPath"))
|
||||
}
|
||||
})
|
||||
http.HandleFunc("/api/record/flv/stop", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
if streamPath := r.URL.Query().Get("streamPath"); streamPath != "" {
|
||||
filePath := config.Path + streamPath + ".flv"
|
||||
if stream, ok := recordings.Load(filePath); ok {
|
||||
output := stream.(*OutputStream)
|
||||
output.Close()
|
||||
w.Write([]byte("success"))
|
||||
} else {
|
||||
w.Write([]byte("no query stream"))
|
||||
}
|
||||
} else {
|
||||
w.Write([]byte("no such stream"))
|
||||
}
|
||||
})
|
||||
}
|
39
plugins/record/publisher.go
Normal file
39
plugins/record/publisher.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package record
|
||||
|
||||
import (
|
||||
"errors"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"github.com/langhuihui/monibuca/monica/avformat"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type FlvFile struct {
|
||||
InputStream
|
||||
}
|
||||
|
||||
func PublishFlvFile(streamPath string) error {
|
||||
if file, err := os.Open(config.Path + streamPath + ".flv"); err == nil {
|
||||
stream := FlvFile{}
|
||||
stream.UseTimestamp = true
|
||||
if stream.Publish(streamPath, &stream) {
|
||||
file.Seek(int64(len(avformat.FLVHeader)), syscall.FILE_BEGIN)
|
||||
for {
|
||||
if tag, err := avformat.ReadFLVTag(file); err == nil {
|
||||
switch tag.Type {
|
||||
case avformat.FLV_TAG_TYPE_AUDIO:
|
||||
stream.PushAudio(tag)
|
||||
case avformat.FLV_TAG_TYPE_VIDEO:
|
||||
stream.PushVideo(tag)
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return errors.New("Bad Name")
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
432
plugins/rtmp/amf.go
Normal file
432
plugins/rtmp/amf.go
Normal file
@@ -0,0 +1,432 @@
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/langhuihui/monibuca/monica/pool"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"log"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Action Message Format -- AMF 0
|
||||
// Action Message Format -- AMF 3
|
||||
// http://download.macromedia.com/pub/labs/amf/amf0_spec_121207.pdf
|
||||
// http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/amf/pdf/amf-file-format-spec.pdf
|
||||
|
||||
// AMF Object == AMF Object Type(1 byte) + AMF Object Value
|
||||
//
|
||||
// AMF Object Value :
|
||||
// AMF0_STRING : 2 bytes(datasize,记录string的长度) + data(string)
|
||||
// AMF0_OBJECT : AMF0_STRING + AMF Object
|
||||
// AMF0_NULL : 0 byte
|
||||
// AMF0_NUMBER : 8 bytes
|
||||
// AMF0_DATE : 10 bytes
|
||||
// AMF0_BOOLEAN : 1 byte
|
||||
// AMF0_ECMA_ARRAY : 4 bytes(arraysize,记录数组的长度) + AMF0_OBJECT
|
||||
// AMF0_STRICT_ARRAY : 4 bytes(arraysize,记录数组的长度) + AMF Object
|
||||
|
||||
// 实际测试时,AMF0_ECMA_ARRAY数据如下:
|
||||
// 8 0 0 0 13 0 8 100 117 114 97 116 105 111 110 0 0 0 0 0 0 0 0 0 0 5 119 105 100 116 104 0 64 158 0 0 0 0 0 0 0 6 104 101 105 103 104 116 0 64 144 224 0 0 0 0 0
|
||||
// 8 0 0 0 13 | { 0 8 100 117 114 97 116 105 111 110 --- 0 0 0 0 0 0 0 0 0 } | { 0 5 119 105 100 116 104 --- 0 64 158 0 0 0 0 0 0 } | { 0 6 104 101 105 103 104 116 --- 0 64 144 224 0 0 0 0 0 } |...
|
||||
// 13 | {AMF0_STRING --- AMF0_NUMBER} | {AMF0_STRING --- AMF0_NUMBER} | {AMF0_STRING --- AMF0_NUMBER} | ...
|
||||
// 13 | {AMF0_OBJECT} | {AMF0_OBJECT} | {AMF0_OBJECT} | ...
|
||||
// 13 | {duration --- 0} | {width --- 1920} | {height --- 1080} | ...
|
||||
|
||||
const (
|
||||
AMF0_NUMBER = 0x00 // 浮点数
|
||||
AMF0_BOOLEAN = 0x01 // 布尔型
|
||||
AMF0_STRING = 0x02 // 字符串
|
||||
AMF0_OBJECT = 0x03 // 对象,开始
|
||||
AMF0_MOVIECLIP = 0x04
|
||||
AMF0_NULL = 0x05 // null
|
||||
AMF0_UNDEFINED = 0x06
|
||||
AMF0_REFERENCE = 0x07
|
||||
AMF0_ECMA_ARRAY = 0x08
|
||||
AMF0_END_OBJECT = 0x09 // 对象,结束
|
||||
AMF0_STRICT_ARRAY = 0x0A
|
||||
AMF0_DATE = 0x0B // 日期
|
||||
AMF0_LONG_STRING = 0x0C // 字符串
|
||||
AMF0_UNSUPPORTED = 0x0D
|
||||
AMF0_RECORDSET = 0x0E
|
||||
AMF0_XML_DOCUMENT = 0x0F
|
||||
AMF0_TYPED_OBJECT = 0x10
|
||||
AMF0_AVMPLUS_OBJECT = 0x11
|
||||
|
||||
AMF3_UNDEFINED = 0x00
|
||||
AMF3_NULL = 0x01
|
||||
AMF3_FALSE = 0x02
|
||||
AMF3_TRUE = 0x03
|
||||
AMF3_INTEGER = 0x04
|
||||
AMF3_DOUBLE = 0x05
|
||||
AMF3_STRING = 0x06
|
||||
AMF3_XML_DOC = 0x07
|
||||
AMF3_DATE = 0x08
|
||||
AMF3_ARRAY = 0x09
|
||||
AMF3_OBJECT = 0x0A
|
||||
AMF3_XML = 0x0B
|
||||
AMF3_BYTE_ARRAY = 0x0C
|
||||
AMF3_VECTOR_INT = 0x0D
|
||||
AMF3_VECTOR_UINT = 0x0E
|
||||
AMF3_VECTOR_DOUBLE = 0x0F
|
||||
AMF3_VECTOR_OBJECT = 0x10
|
||||
AMF3_DICTIONARY = 0x11
|
||||
)
|
||||
|
||||
var END_OBJ = []byte{0, 0, AMF0_END_OBJECT}
|
||||
|
||||
type AMFObject interface{}
|
||||
|
||||
type AMFObjects map[string]AMFObject
|
||||
|
||||
func newAMFObjects() AMFObjects {
|
||||
return make(AMFObjects, 0)
|
||||
}
|
||||
|
||||
func DecodeAMFObject(obj interface{}, key string) interface{} {
|
||||
if v, ok := obj.(AMFObjects)[key]; ok {
|
||||
return v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AMF struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func newAMFEncoder() *AMF {
|
||||
return &AMF{
|
||||
new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
func newAMFDecoder(b []byte) *AMF {
|
||||
return &AMF{
|
||||
bytes.NewBuffer(b),
|
||||
}
|
||||
}
|
||||
func (amf *AMF) readSize() (int, error) {
|
||||
b, err := readBytes(amf.Buffer, 4)
|
||||
size := int(util.BigEndian.Uint32(b))
|
||||
pool.RecycleSlice(b)
|
||||
return size, err
|
||||
}
|
||||
func (amf *AMF) readSize16() (int, error) {
|
||||
b, err := readBytes(amf.Buffer, 2)
|
||||
size := int(util.BigEndian.Uint16(b))
|
||||
pool.RecycleSlice(b)
|
||||
return size, err
|
||||
}
|
||||
func (amf *AMF) readObjects() (obj []AMFObject, err error) {
|
||||
obj = make([]AMFObject, 0)
|
||||
|
||||
for amf.Len() > 0 {
|
||||
if v, err := amf.decodeObject(); err == nil {
|
||||
obj = append(obj, v)
|
||||
} else {
|
||||
return obj, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (amf *AMF) writeObjects(obj []AMFObject) (err error) {
|
||||
for _, v := range obj {
|
||||
switch data := v.(type) {
|
||||
case string:
|
||||
err = amf.writeString(data)
|
||||
case float64:
|
||||
err = amf.writeNumber(data)
|
||||
case bool:
|
||||
err = amf.writeBool(data)
|
||||
case AMFObjects:
|
||||
err = amf.encodeObject(data)
|
||||
case nil:
|
||||
err = amf.writeNull()
|
||||
default:
|
||||
log.Printf("amf encode unknown type:%v", reflect.TypeOf(data).Name())
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (amf *AMF) decodeObject() (obj AMFObject, err error) {
|
||||
if amf.Len() == 0 {
|
||||
return nil, errors.New(fmt.Sprintf("no enough bytes, %v/%v", amf.Len(), 1))
|
||||
}
|
||||
var t byte
|
||||
if t, err = amf.ReadByte(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = amf.UnreadByte(); err != nil {
|
||||
return
|
||||
}
|
||||
switch t {
|
||||
case AMF0_NUMBER:
|
||||
return amf.readNumber()
|
||||
case AMF0_BOOLEAN:
|
||||
return amf.readBool()
|
||||
case AMF0_STRING:
|
||||
return amf.readString()
|
||||
case AMF0_OBJECT:
|
||||
return amf.readObject()
|
||||
case AMF0_MOVIECLIP:
|
||||
log.Println("This type is not supported and is reserved for future use.(AMF0_MOVIECLIP)")
|
||||
case AMF0_NULL:
|
||||
return amf.readNull()
|
||||
case AMF0_UNDEFINED:
|
||||
_, err = amf.ReadByte()
|
||||
return "Undefined", err
|
||||
case AMF0_REFERENCE:
|
||||
log.Println("reference-type.(AMF0_REFERENCE)")
|
||||
case AMF0_ECMA_ARRAY:
|
||||
return amf.readECMAArray()
|
||||
case AMF0_END_OBJECT:
|
||||
_, err = amf.ReadByte()
|
||||
return "ObjectEnd", err
|
||||
case AMF0_STRICT_ARRAY:
|
||||
return amf.readStrictArray()
|
||||
case AMF0_DATE:
|
||||
return amf.readDate()
|
||||
case AMF0_LONG_STRING:
|
||||
return amf.readLongString()
|
||||
case AMF0_UNSUPPORTED:
|
||||
log.Println("If a type cannot be serialized a special unsupported marker can be used in place of the type.(AMF0_UNSUPPORTED)")
|
||||
case AMF0_RECORDSET:
|
||||
log.Println("This type is not supported and is reserved for future use.(AMF0_RECORDSET)")
|
||||
case AMF0_XML_DOCUMENT:
|
||||
return amf.readLongString()
|
||||
case AMF0_TYPED_OBJECT:
|
||||
log.Println("If a strongly typed object has an alias registered for its class then the type name will also be serialized. Typed objects are considered complex types and reoccurring instances can be sent by reference.(AMF0_TYPED_OBJECT)")
|
||||
case AMF0_AVMPLUS_OBJECT:
|
||||
log.Println("AMF0_AVMPLUS_OBJECT")
|
||||
default:
|
||||
log.Println("Unkonw type.")
|
||||
}
|
||||
return nil, errors.New(fmt.Sprintf("Unsupported type %v", t))
|
||||
}
|
||||
|
||||
func (amf *AMF) encodeObject(t AMFObjects) (err error) {
|
||||
err = amf.writeObject()
|
||||
defer amf.writeObjectEnd()
|
||||
for k, vv := range t {
|
||||
switch vvv := vv.(type) {
|
||||
case string:
|
||||
if err = amf.writeObjectString(k, vvv); err != nil {
|
||||
return
|
||||
}
|
||||
case float64, uint, float32, int, int16, int32, int64, uint16, uint32, uint64, uint8, int8:
|
||||
if err = amf.writeObjectNumber(k, util.ToFloat64(vvv)); err != nil {
|
||||
return
|
||||
}
|
||||
case bool:
|
||||
if err = amf.writeObjectBool(k, vvv); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (amf *AMF) readDate() (t uint64, err error) {
|
||||
_, err = amf.ReadByte() // 取出第一个字节 8 Bit == 1 Byte. buf - 1.
|
||||
var b []byte
|
||||
b, err = readBytes(amf.Buffer, 8) // 在取出8个字节,并且读到b中. buf - 8
|
||||
t = util.BigEndian.Uint64(b)
|
||||
pool.RecycleSlice(b)
|
||||
b, err = readBytes(amf.Buffer, 2)
|
||||
pool.RecycleSlice(b)
|
||||
return t, err
|
||||
}
|
||||
|
||||
func (amf *AMF) readStrictArray() (list []AMFObject, err error) {
|
||||
list = make([]AMFObject, 0)
|
||||
_, err = amf.ReadByte()
|
||||
var size int
|
||||
size, err = amf.readSize()
|
||||
for i := 0; i < size; i++ {
|
||||
if obj, err := amf.decodeObject(); err != nil {
|
||||
return list, err
|
||||
} else {
|
||||
list = append(list, obj)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (amf *AMF) readECMAArray() (m AMFObjects, err error) {
|
||||
m = make(AMFObjects, 0)
|
||||
_, err = amf.ReadByte()
|
||||
var size int
|
||||
size, err = amf.readSize()
|
||||
for i := 0; i < size; i++ {
|
||||
var k string
|
||||
var v AMFObject
|
||||
if k, err = amf.readString1(); err == nil {
|
||||
if v, err = amf.decodeObject(); err == nil {
|
||||
if k != "" || "ObjectEnd" != v {
|
||||
m[k] = v
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (amf *AMF) readString() (str string, err error) {
|
||||
_, err = amf.ReadByte() // 取出第一个字节 8 Bit == 1 Byte. buf - 1.
|
||||
return amf.readString1()
|
||||
}
|
||||
|
||||
func (amf *AMF) readString1() (str string, err error) {
|
||||
var size int
|
||||
size, err = amf.readSize16()
|
||||
var b []byte
|
||||
b, err = readBytes(amf.Buffer, size) // 读取全部数据,读取长度为l,因为这两个字节(l变量)保存的是数据长度
|
||||
str = string(b)
|
||||
pool.RecycleSlice(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (amf *AMF) readLongString() (str string, err error) {
|
||||
_, err = amf.ReadByte()
|
||||
var size int
|
||||
size, err = amf.readSize()
|
||||
var b []byte
|
||||
b, err = readBytes(amf.Buffer, size) // 读取全部数据,读取长度为l,因为这两个字节(l变量)保存的是数据长度
|
||||
str = string(b)
|
||||
pool.RecycleSlice(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (amf *AMF) readNull() (AMFObject, error) {
|
||||
_, err := amf.ReadByte()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (amf *AMF) readNumber() (num float64, err error) {
|
||||
// binary.read 会读取8个字节(float64),如果小于8个字节返回一个`io.ErrUnexpectedEOF`,如果大于就会返回`io.ErrShortBuffer`,读取完毕会有`io.EOF`
|
||||
_, err = amf.ReadByte()
|
||||
err = binary.Read(amf, binary.BigEndian, &num)
|
||||
return num, err
|
||||
}
|
||||
|
||||
func (amf *AMF) readBool() (f bool, err error) {
|
||||
_, err = amf.ReadByte()
|
||||
if b, err := amf.ReadByte(); err == nil {
|
||||
return b == 1, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (amf *AMF) readObject() (m AMFObjects, err error) {
|
||||
_, err = amf.ReadByte()
|
||||
m = make(AMFObjects, 0)
|
||||
var k string
|
||||
var v AMFObject
|
||||
for {
|
||||
if k, err = amf.readString1(); err != nil {
|
||||
break
|
||||
}
|
||||
if v, err = amf.decodeObject(); err != nil {
|
||||
break
|
||||
}
|
||||
if k == "" && "ObjectEnd" == v {
|
||||
break
|
||||
}
|
||||
m[k] = v
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
func readBytes(buf *bytes.Buffer, length int) (b []byte, err error) {
|
||||
b = pool.GetSlice(length)
|
||||
if i, _ := buf.Read(b); length != i {
|
||||
err = errors.New(fmt.Sprintf("not enough bytes,%v/%v", buf.Len(), length))
|
||||
}
|
||||
return
|
||||
}
|
||||
func (amf *AMF) writeSize16(l int) (err error) {
|
||||
b := pool.GetSlice(2)
|
||||
defer pool.RecycleSlice(b)
|
||||
util.BigEndian.PutUint16(b, uint16(l))
|
||||
_, err = amf.Write(b)
|
||||
return
|
||||
}
|
||||
func (amf *AMF) writeString(value string) error {
|
||||
v := []byte(value)
|
||||
err := amf.WriteByte(byte(AMF0_STRING))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = amf.writeSize16(len(v)); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = amf.Write(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (amf *AMF) writeNull() error {
|
||||
return amf.WriteByte(byte(AMF0_NULL))
|
||||
}
|
||||
|
||||
func (amf *AMF) writeBool(b bool) error {
|
||||
if err := amf.WriteByte(byte(AMF0_BOOLEAN)); err != nil {
|
||||
return err
|
||||
}
|
||||
if b {
|
||||
return amf.WriteByte(byte(1))
|
||||
}
|
||||
return amf.WriteByte(byte(0))
|
||||
}
|
||||
|
||||
func (amf *AMF) writeNumber(b float64) error {
|
||||
if err := amf.WriteByte(byte(AMF0_NUMBER)); err != nil {
|
||||
return err
|
||||
}
|
||||
return binary.Write(amf, binary.BigEndian, b)
|
||||
}
|
||||
|
||||
func (amf *AMF) writeObject() error {
|
||||
return amf.WriteByte(byte(AMF0_OBJECT))
|
||||
}
|
||||
func (amf *AMF) writeKey(key string) (err error) {
|
||||
keyByte := []byte(key)
|
||||
if err = amf.writeSize16(len(keyByte)); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = amf.Write(keyByte); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
func (amf *AMF) writeObjectString(key, value string) error {
|
||||
if err := amf.writeKey(key); err != nil {
|
||||
return err
|
||||
}
|
||||
return amf.writeString(value)
|
||||
}
|
||||
|
||||
func (amf *AMF) writeObjectBool(key string, f bool) error {
|
||||
if err := amf.writeKey(key); err != nil {
|
||||
return err
|
||||
}
|
||||
return amf.writeBool(f)
|
||||
}
|
||||
|
||||
func (amf *AMF) writeObjectNumber(key string, value float64) error {
|
||||
if err := amf.writeKey(key); err != nil {
|
||||
return err
|
||||
}
|
||||
return amf.writeNumber(value)
|
||||
}
|
||||
|
||||
func (amf *AMF) writeObjectEnd() error {
|
||||
_, err := amf.Write(END_OBJ)
|
||||
return err
|
||||
}
|
202
plugins/rtmp/chunk.go
Normal file
202
plugins/rtmp/chunk.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/langhuihui/monibuca/monica/pool"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
)
|
||||
|
||||
// RTMP协议中基本的数据单元称为消息(Message).
|
||||
// 当RTMP协议在互联网中传输数据的时候,消息会被拆分成更小的单元,称为消息块(Chunk).
|
||||
// 在网络上传输数据时,消息需要被拆分成较小的数据块,才适合在相应的网络环境上传输.
|
||||
|
||||
// 理论上Type 0, 1, 2的Chunk都可以使用Extended Timestamp来传递时间
|
||||
// Type 3由于严禁携带Extened Timestamp字段.但实际上只有Type 0才需要带此字段.
|
||||
// 这是因为,对Type 1, 2来说,其时间为一个差值,一般肯定小于0x00FFFFF
|
||||
|
||||
// 对于除Audio,Video以外的基它Message,其时间字段都可以是置为0的,似乎没有被用到.
|
||||
// 只有在发送视频和音频数据时,才需要特别的考虑TimeStamp字段.基本依据是,要以HandShake时为起始点0来计算时间.
|
||||
// 一般来说,建立一个相对时间,把一个视频帧的TimeStamp特意的在当前时间的基础上延迟3秒,则可以达到缓存的效果
|
||||
|
||||
const (
|
||||
RTMP_CHUNK_HEAD_12 = 0 << 6 // Chunk Basic Header = (Chunk Type << 6) | Chunk Stream ID.
|
||||
RTMP_CHUNK_HEAD_8 = 1 << 6
|
||||
RTMP_CHUNK_HEAD_4 = 2 << 6
|
||||
RTMP_CHUNK_HEAD_1 = 3 << 6
|
||||
)
|
||||
|
||||
type Chunk struct {
|
||||
*ChunkHeader
|
||||
Body []byte
|
||||
MsgData interface{}
|
||||
}
|
||||
|
||||
func (c *Chunk) Encode(msg RtmpMessage) {
|
||||
c.MsgData = msg
|
||||
c.Body = msg.Encode()
|
||||
c.MessageLength = uint32(len(c.Body))
|
||||
}
|
||||
func (c *Chunk) Recycle() {
|
||||
chunkMsgPool.Put(c)
|
||||
}
|
||||
|
||||
type ChunkHeader struct {
|
||||
ChunkBasicHeader
|
||||
ChunkMessageHeader
|
||||
ChunkExtendedTimestamp
|
||||
}
|
||||
|
||||
// Basic Header (1 to 3 bytes) : This field encodes the chunk stream ID
|
||||
// and the chunk type. Chunk type determines the format of the
|
||||
// encoded message header. The length(Basic Header) depends entirely on the chunk
|
||||
// stream ID, which is a variable-length field.
|
||||
type ChunkBasicHeader struct {
|
||||
ChunkStreamID uint32 `json:""` // 6 bit. 3 ~ 65559, 0,1,2 reserved
|
||||
ChunkType byte `json:""` // 2 bit.
|
||||
}
|
||||
|
||||
// Message Header (0, 3, 7, or 11 bytes): This field encodes
|
||||
// information about the message being sent (whether in whole or in
|
||||
// part). The length can be determined using the chunk type
|
||||
// specified in the chunk header.
|
||||
type ChunkMessageHeader struct {
|
||||
Timestamp uint32 `json:""` // 3 byte
|
||||
MessageLength uint32 `json:""` // 3 byte
|
||||
MessageTypeID byte `json:""` // 1 byte
|
||||
MessageStreamID uint32 `json:""` // 4 byte
|
||||
}
|
||||
|
||||
// Extended Timestamp (0 or 4 bytes): This field is present in certain
|
||||
// circumstances depending on the encoded timestamp or timestamp
|
||||
// delta field in the Chunk Message header. See Section 5.3.1.3 for
|
||||
// more information
|
||||
type ChunkExtendedTimestamp struct {
|
||||
ExtendTimestamp uint32 `json:",omitempty"` // 标识该字段的数据可忽略
|
||||
}
|
||||
|
||||
// ChunkBasicHeader会决定ChunkMessgaeHeader,ChunkMessgaeHeader有4种(0,3,7,11 Bytes),因此可能有4种头.
|
||||
|
||||
// 1 -> ChunkBasicHeader(1) + ChunkMessageHeader(0)
|
||||
// 4 -> ChunkBasicHeader(1) + ChunkMessageHeader(3)
|
||||
// 8 -> ChunkBasicHeader(1) + ChunkMessageHeader(7)
|
||||
// 12 -> ChunkBasicHeader(1) + ChunkMessageHeader(11)
|
||||
|
||||
func encodeChunk12(head *ChunkHeader, payload []byte, size int) (mark []byte, need []byte, err error) {
|
||||
if size > RTMP_MAX_CHUNK_SIZE || payload == nil || len(payload) == 0 {
|
||||
return nil, nil, errors.New("chunk error")
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
chunkBasicHead := byte(RTMP_CHUNK_HEAD_12 + head.ChunkBasicHeader.ChunkStreamID)
|
||||
buf.WriteByte(chunkBasicHead)
|
||||
|
||||
b := pool.GetSlice(3)
|
||||
util.BigEndian.PutUint24(b, head.ChunkMessageHeader.Timestamp)
|
||||
buf.Write(b)
|
||||
|
||||
util.BigEndian.PutUint24(b, head.ChunkMessageHeader.MessageLength)
|
||||
buf.Write(b)
|
||||
|
||||
buf.WriteByte(head.ChunkMessageHeader.MessageTypeID)
|
||||
pool.RecycleSlice(b)
|
||||
b = pool.GetSlice(4)
|
||||
util.LittleEndian.PutUint32(b, uint32(head.ChunkMessageHeader.MessageStreamID))
|
||||
buf.Write(b)
|
||||
|
||||
if head.ChunkMessageHeader.Timestamp == 0xffffff {
|
||||
util.LittleEndian.PutUint32(b, head.ChunkExtendedTimestamp.ExtendTimestamp)
|
||||
buf.Write(b)
|
||||
}
|
||||
pool.RecycleSlice(b)
|
||||
if len(payload) > size {
|
||||
buf.Write(payload[0:size])
|
||||
need = payload[size:]
|
||||
} else {
|
||||
buf.Write(payload)
|
||||
}
|
||||
|
||||
mark = buf.Bytes()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func encodeChunk8(head *ChunkHeader, payload []byte, size int) (mark []byte, need []byte, err error) {
|
||||
if size > RTMP_MAX_CHUNK_SIZE || payload == nil || len(payload) == 0 {
|
||||
return nil, nil, errors.New("chunk error")
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
chunkBasicHead := byte(RTMP_CHUNK_HEAD_8 + head.ChunkBasicHeader.ChunkStreamID)
|
||||
buf.WriteByte(chunkBasicHead)
|
||||
|
||||
b := pool.GetSlice(3)
|
||||
util.BigEndian.PutUint24(b, head.ChunkMessageHeader.Timestamp)
|
||||
buf.Write(b)
|
||||
|
||||
util.BigEndian.PutUint24(b, head.ChunkMessageHeader.MessageLength)
|
||||
buf.Write(b)
|
||||
pool.RecycleSlice(b)
|
||||
buf.WriteByte(head.ChunkMessageHeader.MessageTypeID)
|
||||
|
||||
if len(payload) > size {
|
||||
buf.Write(payload[0:size])
|
||||
need = payload[size:]
|
||||
} else {
|
||||
buf.Write(payload)
|
||||
}
|
||||
|
||||
mark = buf.Bytes()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func encodeChunk4(head *ChunkHeader, payload []byte, size int) (mark []byte, need []byte, err error) {
|
||||
if size > RTMP_MAX_CHUNK_SIZE || payload == nil || len(payload) == 0 {
|
||||
return nil, nil, errors.New("chunk error")
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
chunkBasicHead := byte(RTMP_CHUNK_HEAD_4 + head.ChunkBasicHeader.ChunkStreamID)
|
||||
buf.WriteByte(chunkBasicHead)
|
||||
|
||||
b := pool.GetSlice(3)
|
||||
util.BigEndian.PutUint24(b, head.ChunkMessageHeader.Timestamp)
|
||||
buf.Write(b)
|
||||
pool.RecycleSlice(b)
|
||||
if len(payload) > size {
|
||||
buf.Write(payload[0:size])
|
||||
need = payload[size:]
|
||||
} else {
|
||||
buf.Write(payload)
|
||||
}
|
||||
|
||||
mark = buf.Bytes()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func encodeChunk1(head *ChunkHeader, payload []byte, size int) (mark []byte, need []byte, err error) {
|
||||
if size > RTMP_MAX_CHUNK_SIZE || payload == nil || len(payload) == 0 {
|
||||
return nil, nil, errors.New("chunk error")
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
chunkBasicHead := byte(RTMP_CHUNK_HEAD_1 + head.ChunkBasicHeader.ChunkStreamID)
|
||||
buf.WriteByte(chunkBasicHead)
|
||||
|
||||
if len(payload) > size {
|
||||
buf.Write(payload[0:size])
|
||||
need = payload[size:]
|
||||
} else {
|
||||
buf.Write(payload)
|
||||
}
|
||||
|
||||
mark = buf.Bytes()
|
||||
|
||||
return
|
||||
}
|
77
plugins/rtmp/event.go
Normal file
77
plugins/rtmp/event.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package rtmp
|
||||
|
||||
// http://help.adobe.com/zh_CN/AIR/1.5/jslr/flash/events/NetStatusEvent.html
|
||||
|
||||
const (
|
||||
Response_OnStatus = "onStatus"
|
||||
Response_Result = "_result"
|
||||
Response_Error = "_error"
|
||||
|
||||
/* Level */
|
||||
Level_Status = "status"
|
||||
Level_Error = "error"
|
||||
Level_Warning = "warning"
|
||||
|
||||
/* Code */
|
||||
/* NetStream */
|
||||
NetStream_Play_Reset = "NetStream.Play.Reset" // "status" 由播放列表重置导致
|
||||
NetStream_Play_Start = "NetStream.Play.Start" // "status" 播放已开始
|
||||
NetStream_Play_StreamNotFound = "NetStream.Play.StreamNotFound" // "error" 无法找到传递给 play()方法的 FLV
|
||||
NetStream_Play_Stop = "NetStream.Play.Stop" // "status" 播放已结束
|
||||
NetStream_Play_Failed = "NetStream.Play.Failed" // "error" 出于此表中列出的原因之外的某一原因(例如订阅者没有读取权限),播放发生了错误
|
||||
|
||||
NetStream_Play_Switch = "NetStream.Play.Switch"
|
||||
NetStream_Play_Complete = "NetStream.Play.Switch"
|
||||
|
||||
NetStream_Data_Start = "NetStream.Data.Start"
|
||||
|
||||
NetStream_Publish_Start = "NetStream.Publish.Start" // "status" 已经成功发布.
|
||||
NetStream_Publish_BadName = "NetStream.Publish.BadName" // "error" 试图发布已经被他人发布的流.
|
||||
NetStream_Publish_Idle = "NetStream.Publish.Idle" // "status" 流发布者空闲而没有在传输数据.
|
||||
NetStream_Unpublish_Success = "NetStream.Unpublish.Success" // "status" 已成功执行取消发布操作.
|
||||
|
||||
NetStream_Buffer_Empty = "NetStream.Buffer.Empty" // "status" 数据的接收速度不足以填充缓冲区.数据流将在缓冲区重新填充前中断,此时将发送 NetStream.Buffer.Full 消息,并且该流将重新开始播放
|
||||
NetStream_Buffer_Full = "NetStream.Buffer.Full" // "status" 缓冲区已满并且流将开始播放
|
||||
NetStream_Buffe_Flush = "NetStream.Buffer.Flush" // "status" 数据已完成流式处理,剩余的缓冲区将被清空
|
||||
NetStream_Pause_Notify = "NetStream.Pause.Notify" // "status" 流已暂停
|
||||
NetStream_Unpause_Notify = "NetStream.Unpause.Notify" // "status" 流已恢复
|
||||
|
||||
NetStream_Record_Start = "NetStream.Record.Start" // "status" 录制已开始.
|
||||
NetStream_Record_NoAccess = "NetStream.Record.NoAccess" // "error" 试图录制仍处于播放状态的流或客户端没有访问权限的流.
|
||||
NetStream_Record_Stop = "NetStream.Record.Stop" // "status" 录制已停止.
|
||||
NetStream_Record_Failed = "NetStream.Record.Failed" // "error" 尝试录制流失败.
|
||||
|
||||
NetStream_Seek_Failed = "NetStream.Seek.Failed" // "error" 搜索失败,如果流处于不可搜索状态,则会发生搜索失败.
|
||||
NetStream_Seek_InvalidTime = "NetStream.Seek.InvalidTime" // "error" 对于使用渐进式下载方式下载的视频,用户已尝试跳过到目前为止已下载的视频数据的结尾或在整个文件已下载后跳过视频的结尾进行搜寻或播放. message.details 属性包含一个时间代码,该代码指出用户可以搜寻的最后一个有效位置.
|
||||
NetStream_Seek_Notify = "NetStream.Seek.Notify" // "status" 搜寻操作完成.
|
||||
|
||||
/* NetConnect */
|
||||
NetConnection_Call_BadVersion = "NetConnection.Call.BadVersion" // "error" 以不能识别的格式编码的数据包.
|
||||
NetConnection_Call_Failed = "NetConnection.Call.Failed" // "error" NetConnection.call 方法无法调用服务器端的方法或命令.
|
||||
NetConnection_Call_Prohibited = "NetConnection.Call.Prohibited" // "error" Action Message Format (AMF) 操作因安全原因而被阻止. 或者是 AMF URL 与 SWF 不在同一个域,或者是 AMF 服务器没有信任 SWF 文件的域的策略文件.
|
||||
NetConnection_Connect_AppShutdown = "NetConnection.Connect.AppShutdown" // "error" 正在关闭指定的应用程序.
|
||||
NetConnection_Connect_InvalidApp = "NetConnection.Connect.InvalidApp" // "error" 连接时指定的应用程序名无效.
|
||||
NetConnection_Connect_Success = "NetConnection.Connect.Success" // "status" 连接尝试成功.
|
||||
NetConnection_Connect_Closed = "NetConnection.Connect.Closed" // "status" 成功关闭连接.
|
||||
NetConnection_Connect_Failed = "NetConnection.Connect.Failed" // "error" 连接尝试失败.
|
||||
NetConnection_Connect_Rejected = "NetConnection.Connect.Rejected" // "error" 连接尝试没有访问应用程序的权限.
|
||||
|
||||
/* SharedObject */
|
||||
SharedObject_Flush_Success = "SharedObject.Flush.Success" //"status" "待定"状态已解析并且 SharedObject.flush() 调用成功.
|
||||
SharedObject_Flush_Failed = "SharedObject.Flush.Failed" //"error" "待定"状态已解析,但 SharedObject.flush() 失败.
|
||||
SharedObject_BadPersistence = "SharedObject.BadPersistence" //"error" 使用永久性标志对共享对象进行了请求,但请求无法被批准,因为已经使用其它标记创建了该对象.
|
||||
SharedObject_UriMismatch = "SharedObject.UriMismatch" //"error" 试图连接到拥有与共享对象不同的 URI (URL) 的 NetConnection 对象.
|
||||
)
|
||||
|
||||
// NetConnection、NetStream 或 SharedObject 对象报告其状态时,将调度 NetStatusEvent 对象
|
||||
|
||||
type NetStatusEvent struct {
|
||||
Code string
|
||||
Level string
|
||||
}
|
||||
|
||||
func newNetStatusEvent(code, level string) (e *NetStatusEvent) {
|
||||
e.Code = code
|
||||
e.Level = level
|
||||
return e
|
||||
}
|
345
plugins/rtmp/handshake.go
Normal file
345
plugins/rtmp/handshake.go
Normal file
@@ -0,0 +1,345 @@
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
const (
|
||||
C1S1_SIZE = 1536
|
||||
|
||||
C1S1_TIME_SIZE = 4
|
||||
C1S1_VERSION_SIZE = 4
|
||||
|
||||
C1S1_DIGEST_SIZE = 764
|
||||
C1S1_DIGEST_OFFSET_SIZE = 4
|
||||
C1S1_DIGEST_OFFSET_MAX = 764 - 32 - 4
|
||||
C1S1_DIGEST_DATA_SIZE = 32
|
||||
|
||||
C1S1_KEY_SIZE = 764
|
||||
C1S1_KEY_OFFSET_SIZE = 4
|
||||
C1S1_KEY_OFFSET_MAX = 764 - 128 - 4
|
||||
C1S1_KEY_DATA_SIZE = 128
|
||||
|
||||
RTMP_HANDSHAKE_VERSION = 0x03
|
||||
)
|
||||
|
||||
var (
|
||||
FMS_KEY = []byte{
|
||||
0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20,
|
||||
0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c,
|
||||
0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69,
|
||||
0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001
|
||||
0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8,
|
||||
0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57,
|
||||
0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab,
|
||||
0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae,
|
||||
} // 68
|
||||
FP_KEY = []byte{
|
||||
0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20,
|
||||
0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C,
|
||||
0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79,
|
||||
0x65, 0x72, 0x20, 0x30, 0x30, 0x31, /* Genuine Adobe Flash Player 001 */
|
||||
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8,
|
||||
0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57,
|
||||
0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
|
||||
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE,
|
||||
} // 62
|
||||
)
|
||||
|
||||
// C0 S0 (1 byte) : 版本号
|
||||
|
||||
// C1 S1 :
|
||||
// Time (4 bytes)
|
||||
// Zero (4 bytes) -> 这个字段必须都是0.如果不是0,代表要使用complex handshack
|
||||
// Random data (128 Bytes)
|
||||
|
||||
// C2 S2 : 参考C1 S1
|
||||
|
||||
func ReadBuf(r io.Reader, length int) (buf []byte) {
|
||||
buf = make([]byte, length)
|
||||
io.ReadFull(r, buf)
|
||||
return
|
||||
}
|
||||
|
||||
func Handshake(brw *bufio.ReadWriter) error {
|
||||
C0C1 := ReadBuf(brw, 1536+1)
|
||||
if C0C1[0] != RTMP_HANDSHAKE_VERSION {
|
||||
return errors.New("C0 Error")
|
||||
}
|
||||
|
||||
if len(C0C1[1:]) != 1536 {
|
||||
return errors.New("C1 Error")
|
||||
}
|
||||
|
||||
C1 := make([]byte, 1536)
|
||||
copy(C1, C0C1[1:])
|
||||
temp := C1[4] & 0xff
|
||||
|
||||
if temp == 0 {
|
||||
return simple_handshake(brw, C1)
|
||||
}
|
||||
|
||||
return complex_handshake(brw, C1)
|
||||
}
|
||||
|
||||
func simple_handshake(brw *bufio.ReadWriter, C1 []byte) error {
|
||||
var S0 byte
|
||||
S0 = 0x03
|
||||
S1 := make([]byte, 1536-4)
|
||||
S2 := C1
|
||||
S1_Time := uint32(0)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteByte(S0)
|
||||
binary.Write(buf, binary.BigEndian, S1_Time)
|
||||
buf.Write(S1)
|
||||
buf.Write(S2)
|
||||
|
||||
brw.Write(buf.Bytes())
|
||||
brw.Flush() // Don't forget to flush
|
||||
|
||||
ReadBuf(brw, 1536)
|
||||
return nil
|
||||
}
|
||||
|
||||
func complex_handshake(brw *bufio.ReadWriter, C1 []byte) error {
|
||||
// 验证客户端,digest偏移位置和scheme由客户端定.
|
||||
scheme, challenge, digest, ok, err := validateClient(C1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Sprintf("digested handshake, scheme : %v\nchallenge : %v\ndigest : %v\nok : %v\nerr : %v\n", scheme, challenge, digest, ok, err)
|
||||
|
||||
if !ok {
|
||||
return errors.New("validateClient failed")
|
||||
}
|
||||
|
||||
// s0
|
||||
var S0 byte
|
||||
S0 = 0x03
|
||||
|
||||
// s1
|
||||
S1 := create_S1()
|
||||
S1_Digest_Offset := scheme_Digest_Offset(S1, scheme)
|
||||
S1_Part1 := S1[:S1_Digest_Offset]
|
||||
S1_Part2 := S1[S1_Digest_Offset+C1S1_DIGEST_DATA_SIZE:]
|
||||
|
||||
// s1 part1 + part2
|
||||
buf := new(bytes.Buffer)
|
||||
buf.Write(S1_Part1)
|
||||
buf.Write(S1_Part2)
|
||||
S1_Part1_Part2 := buf.Bytes()
|
||||
|
||||
// s1 digest
|
||||
tmp_Hash, err := HMAC_SHA256(S1_Part1_Part2, FMS_KEY[:36])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// incomplete s1
|
||||
copy(S1[S1_Digest_Offset:], tmp_Hash)
|
||||
|
||||
// s2
|
||||
S2_Random := cerate_S2()
|
||||
|
||||
tmp_Hash, err = HMAC_SHA256(digest, FMS_KEY[:68])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// s2 digest
|
||||
S2_Digest, err := HMAC_SHA256(S2_Random, tmp_Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
buffer.WriteByte(S0)
|
||||
buffer.Write(S1)
|
||||
buffer.Write(S2_Random)
|
||||
buffer.Write(S2_Digest)
|
||||
|
||||
brw.Write(buffer.Bytes())
|
||||
brw.Flush()
|
||||
|
||||
ReadBuf(brw, 1536)
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateClient(C1 []byte) (scheme int, challenge []byte, digest []byte, ok bool, err error) {
|
||||
scheme, challenge, digest, ok, err = clientScheme(C1, 1)
|
||||
if ok {
|
||||
return scheme, challenge, digest, ok, nil
|
||||
}
|
||||
|
||||
scheme, challenge, digest, ok, err = clientScheme(C1, 0)
|
||||
if ok {
|
||||
return scheme, challenge, digest, ok, nil
|
||||
}
|
||||
|
||||
return scheme, challenge, digest, ok, errors.New("Client scheme error")
|
||||
}
|
||||
|
||||
func clientScheme(C1 []byte, schem int) (scheme int, challenge []byte, digest []byte, ok bool, err error) {
|
||||
digest_offset := -1
|
||||
key_offset := -1
|
||||
|
||||
if schem == 0 {
|
||||
digest_offset = scheme0_Digest_Offset(C1)
|
||||
key_offset = scheme0_Key_Offset(C1)
|
||||
} else if schem == 1 {
|
||||
digest_offset = scheme1_Digest_Offset(C1)
|
||||
key_offset = scheme1_Key_Offset(C1)
|
||||
}
|
||||
|
||||
// digest
|
||||
c1_Part1 := C1[:digest_offset]
|
||||
c1_Part2 := C1[digest_offset+C1S1_DIGEST_DATA_SIZE:]
|
||||
digest = C1[digest_offset : digest_offset+C1S1_DIGEST_DATA_SIZE]
|
||||
|
||||
// part1 + part2
|
||||
buf := new(bytes.Buffer)
|
||||
buf.Write(c1_Part1)
|
||||
buf.Write(c1_Part2)
|
||||
c1_Part1_Part2 := buf.Bytes()
|
||||
|
||||
tmp_Hash, err := HMAC_SHA256(c1_Part1_Part2, FP_KEY[:30])
|
||||
if err != nil {
|
||||
return 0, nil, nil, false, err
|
||||
}
|
||||
|
||||
// ok
|
||||
if bytes.Compare(digest, tmp_Hash) == 0 {
|
||||
ok = true
|
||||
} else {
|
||||
ok = false
|
||||
}
|
||||
|
||||
// challenge scheme
|
||||
challenge = C1[key_offset : key_offset+C1S1_KEY_DATA_SIZE]
|
||||
scheme = schem
|
||||
return
|
||||
}
|
||||
|
||||
func scheme_Digest_Offset(C1S1 []byte, scheme int) int {
|
||||
if scheme == 0 {
|
||||
return scheme0_Digest_Offset(C1S1)
|
||||
} else if scheme == 1 {
|
||||
return scheme1_Digest_Offset(C1S1)
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// scheme0:
|
||||
// time + version + digest + key
|
||||
// time + version + [offset + random + digest-data + random-data] + key
|
||||
// 4 + 4 + [4 + offset + 32 + 728-offset ] + 764
|
||||
// 4 + 4 + 764 + 764
|
||||
// 0 <= scheme0_digest_offset <= 728 == 764 - 32 - 4
|
||||
// 如果digest.offset == 3,那么digest[7~38]为digest.digest-data,如果offset == 728, 那么digest[732~763]为digest-data)
|
||||
func scheme0_Digest_Offset(C1S1 []byte) int {
|
||||
scheme0_digest_offset := int(C1S1[8]&0xff) + int(C1S1[9]&0xff) + int(C1S1[10]&0xff) + int(C1S1[11]&0xff)
|
||||
|
||||
scheme0_digest_offset = (scheme0_digest_offset % C1S1_DIGEST_OFFSET_MAX) + C1S1_TIME_SIZE + C1S1_VERSION_SIZE + C1S1_DIGEST_OFFSET_SIZE
|
||||
if scheme0_digest_offset+32 >= C1S1_SIZE {
|
||||
// digest error
|
||||
// digest 数据超出1536.
|
||||
}
|
||||
|
||||
return scheme0_digest_offset
|
||||
}
|
||||
|
||||
// key:
|
||||
// random-data + key-data + random-data + offset
|
||||
// offset + 128 + 764-offset-128-4 + 4
|
||||
// 0 <= scheme0_key_offset <= 632 == 764 - 128 - 4
|
||||
// 如果key.offset == 3, 那么key[3~130]为key-data,这个位置是相对于key结构的第0个字节开始
|
||||
func scheme0_Key_Offset(C1S1 []byte) int {
|
||||
scheme0_key_offset := int(C1S1[1532]) + int(C1S1[1533]) + int(C1S1[1534]) + int(C1S1[1535])
|
||||
|
||||
scheme0_key_offset = (scheme0_key_offset % C1S1_KEY_OFFSET_MAX) + C1S1_TIME_SIZE + C1S1_VERSION_SIZE + C1S1_DIGEST_SIZE
|
||||
if scheme0_key_offset+128 >= C1S1_SIZE {
|
||||
// key error
|
||||
}
|
||||
|
||||
return scheme0_key_offset
|
||||
}
|
||||
|
||||
// scheme1:
|
||||
// time + version + key + digest
|
||||
// 0 <= scheme1_digest_offset <= 728 == 764 - 32 - 4
|
||||
func scheme1_Digest_Offset(C1S1 []byte) int {
|
||||
scheme1_digest_offset := int(C1S1[772]&0xff) + int(C1S1[773]&0xff) + int(C1S1[774]&0xff) + int(C1S1[775]&0xff)
|
||||
|
||||
scheme1_digest_offset = (scheme1_digest_offset % C1S1_DIGEST_OFFSET_MAX) + C1S1_TIME_SIZE + C1S1_VERSION_SIZE + C1S1_KEY_SIZE + C1S1_DIGEST_OFFSET_SIZE
|
||||
if scheme1_digest_offset+32 >= C1S1_SIZE {
|
||||
// digest error
|
||||
}
|
||||
|
||||
return scheme1_digest_offset
|
||||
}
|
||||
|
||||
// time + version + key + digest
|
||||
// 0 <= scheme1_key_offset <= 632 == 764 - 128 - 4
|
||||
func scheme1_Key_Offset(C1S1 []byte) int {
|
||||
scheme1_key_offset := int(C1S1[768]) + int(C1S1[769]) + int(C1S1[770]) + int(C1S1[771])
|
||||
|
||||
scheme1_key_offset = (scheme1_key_offset % C1S1_KEY_OFFSET_MAX) + C1S1_TIME_SIZE + C1S1_VERSION_SIZE + C1S1_DIGEST_SIZE
|
||||
if scheme1_key_offset+128 >= C1S1_SIZE {
|
||||
// key error
|
||||
}
|
||||
|
||||
return scheme1_key_offset
|
||||
}
|
||||
|
||||
// HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出
|
||||
// 哈希算法sha256.New, 密钥 key, 消息 message.
|
||||
func HMAC_SHA256(message []byte, key []byte) ([]byte, error) {
|
||||
mac := hmac.New(sha256.New, key)
|
||||
_, err := mac.Write(message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mac.Sum(nil), nil
|
||||
}
|
||||
|
||||
func create_S1() []byte {
|
||||
s1_Time := []byte{0, 0, 0, 0}
|
||||
s1_Version := []byte{1, 1, 1, 1}
|
||||
s1_key_Digest := make([]byte, 1536-8)
|
||||
|
||||
for i, _ := range s1_key_Digest {
|
||||
s1_key_Digest[i] = byte(rand.Int() % 256)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.Write(s1_Time)
|
||||
buf.Write(s1_Version)
|
||||
buf.Write(s1_key_Digest)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func cerate_S2() []byte {
|
||||
s2_Random := make([]byte, 1536-32)
|
||||
|
||||
for i, _ := range s2_Random {
|
||||
s2_Random[i] = byte(rand.Int() % 256)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.Write(s2_Random)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
21
plugins/rtmp/index.go
Normal file
21
plugins/rtmp/index.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"log"
|
||||
)
|
||||
|
||||
var config = new(ListenerConfig)
|
||||
|
||||
func init() {
|
||||
InstallPlugin(&PluginConfig{
|
||||
Name: "RTMP",
|
||||
Type: PLUGIN_SUBSCRIBER | PLUGIN_PUBLISHER,
|
||||
Config: config,
|
||||
Run: run,
|
||||
})
|
||||
}
|
||||
func run() {
|
||||
log.Printf("server rtmp start at %s", config.ListenAddr)
|
||||
log.Fatal(ListenRtmp(config.ListenAddr))
|
||||
}
|
894
plugins/rtmp/msg.go
Normal file
894
plugins/rtmp/msg.go
Normal file
@@ -0,0 +1,894 @@
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
/* RTMP Message ID*/
|
||||
|
||||
// Protocal Control Messgae(1-7)
|
||||
|
||||
// Chunk
|
||||
RTMP_MSG_CHUNK_SIZE = 1
|
||||
RTMP_MSG_ABORT = 2
|
||||
|
||||
// RTMP
|
||||
RTMP_MSG_ACK = 3
|
||||
RTMP_MSG_USER_CONTROL = 4
|
||||
RTMP_MSG_ACK_SIZE = 5
|
||||
RTMP_MSG_BANDWIDTH = 6
|
||||
RTMP_MSG_EDGE = 7
|
||||
RTMP_MSG_AUDIO = 8
|
||||
RTMP_MSG_VIDEO = 9
|
||||
RTMP_MSG_AMF3_METADATA = 15
|
||||
RTMP_MSG_AMF3_SHARED = 16
|
||||
RTMP_MSG_AMF3_COMMAND = 17
|
||||
|
||||
RTMP_MSG_AMF0_METADATA = 18
|
||||
RTMP_MSG_AMF0_SHARED = 19
|
||||
RTMP_MSG_AMF0_COMMAND = 20
|
||||
|
||||
RTMP_MSG_AGGREGATE = 22
|
||||
|
||||
RTMP_DEFAULT_CHUNK_SIZE = 128
|
||||
RTMP_MAX_CHUNK_SIZE = 65536
|
||||
RTMP_MAX_CHUNK_HEADER = 18
|
||||
|
||||
// User Control Event
|
||||
RTMP_USER_STREAM_BEGIN = 0
|
||||
RTMP_USER_STREAM_EOF = 1
|
||||
RTMP_USER_STREAM_DRY = 2
|
||||
RTMP_USER_SET_BUFFLEN = 3
|
||||
RTMP_USER_STREAM_IS_RECORDED = 4
|
||||
RTMP_USER_PING_REQUEST = 6
|
||||
RTMP_USER_PING_RESPONSE = 7
|
||||
RTMP_USER_EMPTY = 31
|
||||
|
||||
// StreamID == (ChannelID-4)/5+1
|
||||
// ChannelID == Chunk Stream ID
|
||||
// StreamID == Message Stream ID
|
||||
// Chunk Stream ID == 0, 第二个byte + 64
|
||||
// Chunk Stream ID == 1, (第三个byte) * 256 + 第二个byte + 64
|
||||
// Chunk Stream ID == 2.
|
||||
// 2 < Chunk Stream ID < 64(2的6次方)
|
||||
RTMP_CSID_CONTROL = 0x02
|
||||
RTMP_CSID_COMMAND = 0x03
|
||||
RTMP_CSID_AUDIO = 0x06
|
||||
RTMP_CSID_DATA = 0x05
|
||||
RTMP_CSID_VIDEO = 0x05
|
||||
)
|
||||
|
||||
var (
|
||||
rtmpHeaderPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(ChunkHeader)
|
||||
},
|
||||
}
|
||||
chunkMsgPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(Chunk)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func newChunkHeader(messageType byte) *ChunkHeader {
|
||||
head := rtmpHeaderPool.Get().(*ChunkHeader)
|
||||
head.ChunkStreamID = RTMP_CSID_CONTROL
|
||||
head.Timestamp = 0
|
||||
head.MessageTypeID = messageType
|
||||
head.MessageStreamID = 0
|
||||
head.ExtendTimestamp = 0
|
||||
return head
|
||||
}
|
||||
func newRtmpHeader(chunkID uint32, timestamp uint32, messageLength uint32, messageType byte, messageStreamID uint32, extendTimestamp uint32) *ChunkHeader {
|
||||
head := rtmpHeaderPool.Get().(*ChunkHeader)
|
||||
head.ChunkStreamID = chunkID
|
||||
head.Timestamp = timestamp
|
||||
head.MessageLength = messageLength
|
||||
head.MessageTypeID = messageType
|
||||
head.MessageStreamID = messageStreamID
|
||||
head.ExtendTimestamp = extendTimestamp
|
||||
return head
|
||||
}
|
||||
|
||||
func (h *ChunkHeader) Clone() *ChunkHeader {
|
||||
head := rtmpHeaderPool.Get().(*ChunkHeader)
|
||||
head.ChunkStreamID = h.ChunkStreamID
|
||||
head.Timestamp = h.Timestamp
|
||||
head.MessageLength = h.MessageLength
|
||||
head.MessageTypeID = h.MessageTypeID
|
||||
head.MessageStreamID = h.MessageStreamID
|
||||
head.ExtendTimestamp = h.ExtendTimestamp
|
||||
|
||||
return head
|
||||
}
|
||||
|
||||
type RtmpMessage interface {
|
||||
Encode() []byte
|
||||
}
|
||||
type HaveStreamID interface {
|
||||
GetStreamID() uint32
|
||||
}
|
||||
|
||||
func GetRtmpMessage(chunk *Chunk) {
|
||||
switch chunk.MessageTypeID {
|
||||
case RTMP_MSG_CHUNK_SIZE, RTMP_MSG_ABORT, RTMP_MSG_ACK, RTMP_MSG_ACK_SIZE:
|
||||
chunk.MsgData = util.BigEndian.Uint32(chunk.Body)
|
||||
case RTMP_MSG_USER_CONTROL: // RTMP消息类型ID=4, 用户控制消息.客户端或服务端发送本消息通知对方用户的控制事件.
|
||||
{
|
||||
base := UserControlMessage{
|
||||
EventType: util.BigEndian.Uint16(chunk.Body),
|
||||
EventData: chunk.Body[2:],
|
||||
}
|
||||
switch base.EventType {
|
||||
case RTMP_USER_STREAM_BEGIN: // 服务端向客户端发送本事件通知对方一个流开始起作用可以用于通讯.在默认情况下,服务端在成功地从客户端接收连接命令之后发送本事件,事件ID为0.事件数据是表示开始起作用的流的ID.
|
||||
m := &StreamIDMessage{
|
||||
UserControlMessage: base,
|
||||
StreamID: 0,
|
||||
}
|
||||
if len(base.EventData) >= 4 {
|
||||
//服务端在成功地从客户端接收连接命令之后发送本事件,事件ID为0.事件数据是表示开始起作用的流的ID.
|
||||
m.StreamID = util.BigEndian.Uint32(base.EventData)
|
||||
}
|
||||
chunk.MsgData = m
|
||||
case RTMP_USER_STREAM_EOF, RTMP_USER_STREAM_DRY, RTMP_USER_STREAM_IS_RECORDED: // 服务端向客户端发送本事件通知客户端,数据回放完成.果没有发行额外的命令,就不再发送数据.客户端丢弃从流中接收的消息.4字节的事件数据表示,回放结束的流的ID.
|
||||
m := &StreamIDMessage{
|
||||
UserControlMessage: base,
|
||||
StreamID: util.BigEndian.Uint32(base.EventData),
|
||||
}
|
||||
chunk.MsgData = m
|
||||
case RTMP_USER_SET_BUFFLEN: // 客户端向服务端发送本事件,告知对方自己存储一个流的数据的缓存的长度(毫秒单位).当服务端开始处理一个流得时候发送本事件.事件数据的头四个字节表示流ID,后4个字节表示缓存长度(毫秒单位).
|
||||
m := &SetBufferMessage{
|
||||
StreamIDMessage: StreamIDMessage{
|
||||
UserControlMessage: base,
|
||||
StreamID: util.BigEndian.Uint32(base.EventData),
|
||||
},
|
||||
Millisecond: util.BigEndian.Uint32(base.EventData[4:]),
|
||||
}
|
||||
chunk.MsgData = m
|
||||
case RTMP_USER_PING_REQUEST: // 服务端通过本事件测试客户端是否可达.事件数据是4个字节的事件戳.代表服务调用本命令的本地时间.客户端在接收到kMsgPingRequest之后返回kMsgPingResponse事件
|
||||
m := &PingRequestMessage{
|
||||
UserControlMessage: base,
|
||||
Timestamp: util.BigEndian.Uint32(base.EventData),
|
||||
}
|
||||
chunk.MsgData = m
|
||||
case RTMP_USER_PING_RESPONSE, RTMP_USER_EMPTY: // 客户端向服务端发送本消息响应ping请求.事件数据是接kMsgPingRequest请求的时间.
|
||||
chunk.MsgData = &base
|
||||
default:
|
||||
chunk.MsgData = &base
|
||||
}
|
||||
}
|
||||
case RTMP_MSG_BANDWIDTH: // RTMP消息类型ID=6, 置对等端带宽.客户端或服务端发送本消息更新对等端的输出带宽.
|
||||
m := &SetPeerBandwidthMessage{
|
||||
AcknowledgementWindowsize: util.BigEndian.Uint32(chunk.Body),
|
||||
}
|
||||
if len(chunk.Body) > 4 {
|
||||
m.LimitType = chunk.Body[4]
|
||||
}
|
||||
chunk.MsgData = m
|
||||
case RTMP_MSG_EDGE: // RTMP消息类型ID=7, 用于边缘服务与源服务器.
|
||||
case RTMP_MSG_AUDIO: // RTMP消息类型ID=8, 音频数据.客户端或服务端发送本消息用于发送音频数据.
|
||||
case RTMP_MSG_VIDEO: // RTMP消息类型ID=9, 视频数据.客户端或服务端发送本消息用于发送视频数据.
|
||||
case RTMP_MSG_AMF3_METADATA: // RTMP消息类型ID=15, 数据消息.用AMF3编码.
|
||||
case RTMP_MSG_AMF3_SHARED: // RTMP消息类型ID=16, 共享对象消息.用AMF3编码.
|
||||
case RTMP_MSG_AMF3_COMMAND: // RTMP消息类型ID=17, 命令消息.用AMF3编码.
|
||||
decodeCommandAMF3(chunk)
|
||||
case RTMP_MSG_AMF0_METADATA: // RTMP消息类型ID=18, 数据消息.用AMF0编码.
|
||||
case RTMP_MSG_AMF0_SHARED: // RTMP消息类型ID=19, 共享对象消息.用AMF0编码.
|
||||
case RTMP_MSG_AMF0_COMMAND: // RTMP消息类型ID=20, 命令消息.用AMF0编码.
|
||||
decodeCommandAMF0(chunk) // 解析具体的命令消息
|
||||
case RTMP_MSG_AGGREGATE:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// 03 00 00 00 00 01 02 14 00 00 00 00 02 00 07 63 6F 6E 6E 65 63 74 00 3F F0 00 00 00 00 00 00 08
|
||||
//
|
||||
// 这个函数解析的是从02(第13个字节)开始,前面12个字节是Header,后面的是Payload,即解析Payload.
|
||||
//
|
||||
// 解析用AMF0编码的命令消息.(Payload)
|
||||
// 第一个字节(Byte)为此数据的类型.例如:string,int,bool...
|
||||
|
||||
// string就是字符类型,一个byte的amf类型,两个bytes的字符长度,和N个bytes的数据.
|
||||
// 比如: 02 00 02 33 22,第一个byte为amf类型,其后两个bytes为长度,注意这里的00 02是大端模式,33 22是字符数据
|
||||
|
||||
// umber类型其实就是double,占8bytes.
|
||||
// 比如: 00 00 00 00 00 00 00 00,第一个byte为amf类型,其后8bytes为double值0.0
|
||||
|
||||
// boolean就是布尔类型,占用1byte.
|
||||
// 比如:01 00,第一个byte为amf类型,其后1byte是值,false.
|
||||
|
||||
// object类型要复杂点.
|
||||
// 第一个byte是03表示object,其后跟的是N个(key+value).最后以00 00 09表示object结束
|
||||
func decodeCommandAMF0(chunk *Chunk) {
|
||||
amf := newAMFDecoder(chunk.Body) // rtmp_amf.go, amf 是 bytes类型, 将rtmp body(payload)放到bytes.Buffer(amf)中去.
|
||||
cmd := readString(amf) // rtmp_amf.go, 将payload的bytes类型转换成string类型.
|
||||
cmdMsg := CommandMessage{
|
||||
cmd,
|
||||
readTransactionId(amf),
|
||||
}
|
||||
switch cmd {
|
||||
case "connect", "call":
|
||||
chunk.MsgData = &CallMessage{
|
||||
cmdMsg,
|
||||
readObject(amf),
|
||||
readObject(amf),
|
||||
}
|
||||
case "createStream":
|
||||
amf.readNull()
|
||||
chunk.MsgData = &CreateStreamMessage{
|
||||
cmdMsg, readObject(amf),
|
||||
}
|
||||
case "play":
|
||||
amf.readNull()
|
||||
chunk.MsgData = &PlayMessage{
|
||||
cmdMsg,
|
||||
readString(amf),
|
||||
readNumber(amf),
|
||||
readNumber(amf),
|
||||
readBool(amf),
|
||||
}
|
||||
case "play2":
|
||||
amf.readNull()
|
||||
chunk.MsgData = &Play2Message{
|
||||
cmdMsg,
|
||||
readNumber(amf),
|
||||
readString(amf),
|
||||
readString(amf),
|
||||
readNumber(amf),
|
||||
readString(amf),
|
||||
}
|
||||
case "publish":
|
||||
amf.readNull()
|
||||
chunk.MsgData = &PublishMessage{
|
||||
cmdMsg,
|
||||
readString(amf),
|
||||
readString(amf),
|
||||
}
|
||||
case "pause":
|
||||
amf.readNull()
|
||||
chunk.MsgData = &PauseMessage{
|
||||
cmdMsg,
|
||||
readBool(amf),
|
||||
readNumber(amf),
|
||||
}
|
||||
case "seek":
|
||||
amf.readNull()
|
||||
chunk.MsgData = &SeekMessage{
|
||||
cmdMsg,
|
||||
readNumber(amf),
|
||||
}
|
||||
case "deleteStream", "closeStream", "releaseStream":
|
||||
amf.readNull()
|
||||
chunk.MsgData = &CURDStreamMessage{
|
||||
cmdMsg,
|
||||
uint32(readNumber(amf)),
|
||||
}
|
||||
case "receiveAudio", "receiveVideo":
|
||||
amf.readNull()
|
||||
chunk.MsgData = &ReceiveAVMessage{
|
||||
cmdMsg,
|
||||
readBool(amf),
|
||||
}
|
||||
case "_result", "_error", "onStatus":
|
||||
chunk.MsgData = &ResponseMessage{
|
||||
cmdMsg,
|
||||
readObject(amf),
|
||||
readObject(amf), "",
|
||||
}
|
||||
case "FCPublish", "FCUnpublish":
|
||||
default:
|
||||
log.Println("decode command amf0 cmd:", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeCommandAMF3(chunk *Chunk) {
|
||||
chunk.Body = chunk.Body[1:]
|
||||
decodeCommandAMF0(chunk)
|
||||
}
|
||||
|
||||
func readTransactionId(amf *AMF) uint64 {
|
||||
v, _ := amf.readNumber()
|
||||
return uint64(v)
|
||||
}
|
||||
func readString(amf *AMF) string {
|
||||
v, _ := amf.readString()
|
||||
return v
|
||||
}
|
||||
func readNumber(amf *AMF) uint64 {
|
||||
v, _ := amf.readNumber()
|
||||
return uint64(v)
|
||||
}
|
||||
func readBool(amf *AMF) bool {
|
||||
v, _ := amf.readBool()
|
||||
return v
|
||||
}
|
||||
|
||||
func readObject(amf *AMF) AMFObjects {
|
||||
v, _ := amf.readObject()
|
||||
return v
|
||||
}
|
||||
|
||||
/* Command Message */
|
||||
type CommandMessage struct {
|
||||
CommandName string // 命令名. 字符串. 命令名.设置为"connect"
|
||||
TransactionId uint64 // 传输ID. 数字. 总是设为1
|
||||
}
|
||||
type Commander interface {
|
||||
GetCommand() *CommandMessage
|
||||
}
|
||||
|
||||
func (cmd *CommandMessage) GetCommand() *CommandMessage {
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Protocol control message 1.
|
||||
// Set Chunk Size, is used to notify the peer of a new maximum chunk size
|
||||
|
||||
// chunk size (31 bits): This field holds the new maximum chunk size,in bytes, which will be used for all of the sender’s subsequent chunks until further notice
|
||||
type Uint32Message uint32
|
||||
|
||||
func (msg Uint32Message) Encode() (b []byte) {
|
||||
b = make([]byte, 4)
|
||||
util.BigEndian.PutUint32(b, uint32(msg))
|
||||
return b
|
||||
}
|
||||
|
||||
// Protocol control message 4, User Control Messages.
|
||||
// User Control messages SHOULD use message stream ID 0 (known as the control stream) and, when sent over RTMP Chunk Stream,
|
||||
// be sent on chunk stream ID 2. User Control messages are effective at the point they are received in the stream; their timestamps are ignored.
|
||||
|
||||
// Event Type (16 bits) : The first 2 bytes of the message data are used to identify the Event type. Event type is followed by Event data.
|
||||
// Event Data
|
||||
type UserControlMessage struct {
|
||||
EventType uint16
|
||||
EventData []byte
|
||||
}
|
||||
|
||||
// Protocol control message 6, Set Peer Bandwidth Message.
|
||||
// The client or the server sends this message to limit the output bandwidth of its peer.
|
||||
|
||||
// AcknowledgementWindowsize (4 bytes)
|
||||
// LimitType : The Limit Type is one of the following values: 0 - Hard, 1 - Soft, 2- Dynamic.
|
||||
type SetPeerBandwidthMessage struct {
|
||||
AcknowledgementWindowsize uint32 // 4 bytes
|
||||
LimitType byte
|
||||
}
|
||||
|
||||
func (msg *SetPeerBandwidthMessage) Encode() (b []byte) {
|
||||
b = make([]byte, 5)
|
||||
util.BigEndian.PutUint32(b, msg.AcknowledgementWindowsize)
|
||||
b[4] = msg.LimitType
|
||||
return
|
||||
}
|
||||
|
||||
// Message 15, 18. Data Message. The client or the server sends this message to send Metadata or any
|
||||
// user data to the peer. Metadata includes details about the data(audio, video etc.) like creation time, duration,
|
||||
// theme and so on. These messages have been assigned message type value of 18 for AMF0 and message type value of 15 for AMF3
|
||||
type MetadataMessage struct {
|
||||
Proterties map[string]interface{} `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Object 可选值:
|
||||
// App 客户端要连接到的服务应用名 Testapp
|
||||
// Flashver Flash播放器版本.和应用文档中getversion()函数返回的字符串相同. FMSc/1.0
|
||||
// SwfUrl 发起连接的swf文件的url file://C:/ FlvPlayer.swf
|
||||
// TcUrl 服务url.有下列的格式.protocol://servername:port/appName/appInstance rtmp://localhost::1935/testapp/instance1
|
||||
// fpad 是否使用代理 true or false
|
||||
// audioCodecs 指示客户端支持的音频编解码器 SUPPORT_SND_MP3
|
||||
// videoCodecs 指示支持的视频编解码器 SUPPORT_VID_SORENSON
|
||||
// pageUrl SWF文件被加载的页面的Url http:// somehost/sample.html
|
||||
// objectEncoding AMF编码方法 AMF编码方法 kAMF3
|
||||
|
||||
// Call Message.
|
||||
// The call method of the NetConnection object runs remote procedure calls (RPC) at the receiving end.
|
||||
// The called RPC name is passed as a parameter to the call command.
|
||||
type CallMessage struct {
|
||||
CommandMessage
|
||||
Object interface{} `json:",omitempty"`
|
||||
Optional interface{} `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (msg *CallMessage) Encode() []byte {
|
||||
amf := newAMFEncoder()
|
||||
amf.writeString(msg.CommandName)
|
||||
amf.writeNumber(float64(msg.TransactionId))
|
||||
|
||||
if msg.Object != nil {
|
||||
amf.encodeObject(msg.Object.(AMFObjects))
|
||||
}
|
||||
if msg.Optional != nil {
|
||||
amf.encodeObject(msg.Optional.(AMFObjects))
|
||||
}
|
||||
|
||||
return amf.Bytes()
|
||||
}
|
||||
|
||||
func (msg *CallMessage) Encode3() []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteByte(0)
|
||||
buf.Write(msg.Encode())
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Create Stream Message.
|
||||
// The client sends this command to the server to create a logical channel for message communication The publishing of audio,
|
||||
// video, and metadata is carried out over stream channel created using the createStream command.
|
||||
|
||||
type CreateStreamMessage struct {
|
||||
CommandMessage
|
||||
Object interface{}
|
||||
}
|
||||
|
||||
func (msg *CreateStreamMessage) Encode() []byte {
|
||||
amf := newAMFEncoder()
|
||||
amf.writeString(msg.CommandName)
|
||||
amf.writeNumber(float64(msg.TransactionId))
|
||||
|
||||
if msg.Object != nil {
|
||||
amf.encodeObject(msg.Object.(AMFObjects))
|
||||
}
|
||||
return amf.Bytes()
|
||||
}
|
||||
|
||||
/*
|
||||
func (msg *CreateStreamMessage) Encode3() {
|
||||
msg.Encode0()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteByte(0)
|
||||
buf.Write(msg.RtmpBody)
|
||||
msg.RtmpBody = buf.Bytes()
|
||||
}*/
|
||||
|
||||
// The following commands can be sent on the NetStream by the client to the server:
|
||||
|
||||
// Play
|
||||
// Play2
|
||||
// DeleteStream
|
||||
// CloseStream
|
||||
// ReceiveAudio
|
||||
// ReceiveVideo
|
||||
// Publish
|
||||
// Seek
|
||||
// Pause
|
||||
// Release(37)
|
||||
// FCPublish
|
||||
|
||||
// Play Message
|
||||
// The client sends this command to the server to play a stream. A playlist can also be created using this command multiple times
|
||||
type PlayMessage struct {
|
||||
CommandMessage
|
||||
StreamName string
|
||||
Start uint64
|
||||
Duration uint64
|
||||
Rest bool
|
||||
}
|
||||
|
||||
// 命令名 -> 命令名,设置为”play”
|
||||
// 传输ID -> 0
|
||||
// 命令对象
|
||||
// 流名字 -> 要播放流的名字
|
||||
// start -> 可选的参数,以秒为单位定义开始时间.默认值为 -2,表示用户首先尝试播放流名字段中定义的直播流.
|
||||
// Duration -> 可选的参数,以秒为单位定义了回放的持续时间.默认值为 -1.-1 值意味着一个直播流会一直播放直到它不再可用或者一个录制流一直播放直到结束
|
||||
// Reset -> 可选的布尔值或者数字定义了是否对以前的播放列表进行 flush
|
||||
|
||||
func (msg *PlayMessage) Encode() []byte {
|
||||
amf := newAMFEncoder()
|
||||
amf.writeString(msg.CommandName)
|
||||
amf.writeNumber(float64(msg.TransactionId))
|
||||
amf.writeNull()
|
||||
amf.writeString(msg.StreamName)
|
||||
|
||||
if msg.Start > 0 {
|
||||
amf.writeNumber(float64(msg.Start))
|
||||
}
|
||||
if msg.Duration > 0 {
|
||||
amf.writeNumber(float64(msg.Duration))
|
||||
}
|
||||
|
||||
amf.writeBool(msg.Rest)
|
||||
return amf.Bytes()
|
||||
}
|
||||
|
||||
/*
|
||||
func (msg *PlayMessage) Encode3() {
|
||||
}*/
|
||||
|
||||
// Play2 Message
|
||||
// Unlike the play command, play2 can switch to a different bit rate stream without changing the timeline of the content played. The
|
||||
// server maintains multiple files for all supported bitrates that the client can request in play2.
|
||||
type Play2Message struct {
|
||||
CommandMessage
|
||||
StartTime uint64
|
||||
OldStreamName string
|
||||
StreamName string
|
||||
Duration uint64
|
||||
Transition string
|
||||
}
|
||||
|
||||
func (msg *Play2Message) Encode0() {
|
||||
}
|
||||
|
||||
// Delete Stream Message
|
||||
// NetStream sends the deleteStream command when the NetStream object is getting destroyed
|
||||
type CURDStreamMessage struct {
|
||||
CommandMessage
|
||||
StreamId uint32
|
||||
}
|
||||
|
||||
func (msg *CURDStreamMessage) Encode0() {
|
||||
}
|
||||
|
||||
// Receive Audio Message
|
||||
// NetStream sends the receiveAudio message to inform the server whether to send or not to send the audio to the client
|
||||
type ReceiveAVMessage struct {
|
||||
CommandMessage
|
||||
BoolFlag bool
|
||||
}
|
||||
|
||||
func (msg *ReceiveAVMessage) Encode0() {
|
||||
}
|
||||
|
||||
// Publish Message
|
||||
// The client sends the publish command to publish a named stream to the server. Using this name,
|
||||
// any client can play this stream and receive the published audio, video, and data messages
|
||||
type PublishMessage struct {
|
||||
CommandMessage
|
||||
PublishingName string
|
||||
PublishingType string
|
||||
}
|
||||
|
||||
// 命令名 -> 命令名,设置为”publish”
|
||||
// 传输ID -> 0
|
||||
// 命令对象
|
||||
// 发布名 -> 流发布的名字
|
||||
// 发布类型 -> 设置为”live”,”record”或”append”.
|
||||
|
||||
// “record”:流被发布,并且数据被录制到一个新的文件,文件被存储到服务端的服务应用的目录的一个子目录下.如果文件已经存在则重写文件.
|
||||
// “append”:流被发布并且附加到一个文件之后.如果没有发现文件则创建一个文件.
|
||||
// “live”:发布直播数据而不录制到文件
|
||||
|
||||
func (msg *PublishMessage) Encode0() {
|
||||
}
|
||||
|
||||
// Seek Message
|
||||
// The client sends the seek command to seek the offset (in milliseconds) within a media file or playlist.
|
||||
type SeekMessage struct {
|
||||
CommandMessage
|
||||
Milliseconds uint64
|
||||
}
|
||||
|
||||
func (msg *SeekMessage) Encode0() {
|
||||
}
|
||||
|
||||
// Pause Message
|
||||
// The client sends the pause command to tell the server to pause or start playing.
|
||||
type PauseMessage struct {
|
||||
CommandMessage
|
||||
Pause bool
|
||||
Milliseconds uint64
|
||||
}
|
||||
|
||||
// 命令名 -> 命令名,设置为”pause”
|
||||
// 传输ID -> 0
|
||||
// 命令对象 -> null
|
||||
// Pause/Unpause Flag -> true 或者 false,来指示暂停或者重新播放
|
||||
// milliSeconds -> 流暂停或者重新开始所在的毫秒数.这个是客户端暂停的当前流时间.当回放已恢复时,服务器端值发送带有比这个值大的 timestamp 消息
|
||||
|
||||
func (msg *PauseMessage) Encode0() {
|
||||
}
|
||||
|
||||
//
|
||||
// Response Message. Server -> Response -> Client
|
||||
//
|
||||
|
||||
//
|
||||
// Response Connect Message
|
||||
//
|
||||
type ResponseConnectMessage struct {
|
||||
CommandMessage
|
||||
Properties interface{} `json:",omitempty"`
|
||||
Infomation interface{} `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (msg *ResponseConnectMessage) Encode() []byte {
|
||||
amf := newAMFEncoder()
|
||||
amf.writeString(msg.CommandName)
|
||||
amf.writeNumber(float64(msg.TransactionId))
|
||||
|
||||
if msg.Properties != nil {
|
||||
amf.encodeObject(msg.Properties.(AMFObjects))
|
||||
}
|
||||
if msg.Infomation != nil {
|
||||
amf.encodeObject(msg.Infomation.(AMFObjects))
|
||||
}
|
||||
|
||||
return amf.Bytes()
|
||||
}
|
||||
|
||||
/*
|
||||
func (msg *ResponseConnectMessage) Encode3() {
|
||||
}*/
|
||||
|
||||
// Response Call Message
|
||||
//
|
||||
type ResponseCallMessage struct {
|
||||
CommandMessage
|
||||
Object interface{}
|
||||
Response interface{}
|
||||
}
|
||||
|
||||
func (msg *ResponseCallMessage) Encode0() []byte {
|
||||
amf := newAMFEncoder()
|
||||
amf.writeString(msg.CommandName)
|
||||
amf.writeNumber(float64(msg.TransactionId))
|
||||
|
||||
if msg.Object != nil {
|
||||
amf.encodeObject(msg.Object.(AMFObjects))
|
||||
}
|
||||
if msg.Response != nil {
|
||||
amf.encodeObject(msg.Response.(AMFObjects))
|
||||
}
|
||||
|
||||
return amf.Bytes()
|
||||
}
|
||||
|
||||
//
|
||||
// Response Create Stream Message
|
||||
//
|
||||
type ResponseCreateStreamMessage struct {
|
||||
CommandMessage
|
||||
Object interface{} `json:",omitempty"`
|
||||
StreamId uint32
|
||||
}
|
||||
|
||||
func (msg *ResponseCreateStreamMessage) Encode() []byte {
|
||||
amf := newAMFEncoder() // rtmp_amf.go
|
||||
amf.writeString(msg.CommandName)
|
||||
amf.writeNumber(float64(msg.TransactionId))
|
||||
amf.writeNull()
|
||||
amf.writeNumber(float64(msg.StreamId))
|
||||
return amf.Bytes()
|
||||
}
|
||||
|
||||
/*
|
||||
func (msg *ResponseCreateStreamMessage) Encode3() {
|
||||
}*/
|
||||
|
||||
func (msg *ResponseCreateStreamMessage) Decode0(chunk *Chunk) {
|
||||
amf := newAMFDecoder(chunk.Body)
|
||||
if obj, err := amf.decodeObject(); err == nil {
|
||||
msg.CommandName = obj.(string)
|
||||
}
|
||||
if obj, err := amf.decodeObject(); err == nil {
|
||||
msg.TransactionId = uint64(obj.(float64))
|
||||
}
|
||||
|
||||
amf.decodeObject()
|
||||
if obj, err := amf.decodeObject(); err == nil {
|
||||
msg.StreamId = uint32(obj.(float64))
|
||||
}
|
||||
}
|
||||
func (msg *ResponseCreateStreamMessage) Decode3(chunk *Chunk) {
|
||||
chunk.Body = chunk.Body[1:]
|
||||
msg.Decode0(chunk)
|
||||
}
|
||||
|
||||
//
|
||||
// Response Play Message
|
||||
//
|
||||
type ResponsePlayMessage struct {
|
||||
CommandMessage
|
||||
Object interface{} `json:",omitempty"`
|
||||
Description string
|
||||
StreamID uint32
|
||||
}
|
||||
|
||||
func (msg *ResponsePlayMessage) GetStreamID() uint32 {
|
||||
return msg.StreamID
|
||||
}
|
||||
func (msg *ResponsePlayMessage) Encode() []byte {
|
||||
amf := newAMFEncoder() // rtmp_amf.go
|
||||
amf.writeString(msg.CommandName)
|
||||
amf.writeNumber(float64(msg.TransactionId))
|
||||
amf.writeNull()
|
||||
if msg.Object != nil {
|
||||
amf.encodeObject(msg.Object.(AMFObjects))
|
||||
}
|
||||
amf.writeString(msg.Description)
|
||||
return amf.Bytes()
|
||||
}
|
||||
|
||||
/*
|
||||
func (msg *ResponsePlayMessage) Encode3() {
|
||||
}*/
|
||||
|
||||
func (msg *ResponsePlayMessage) Decode0(chunk *Chunk) {
|
||||
amf := newAMFDecoder(chunk.Body)
|
||||
if obj, err := amf.decodeObject(); err == nil {
|
||||
msg.CommandName = obj.(string)
|
||||
}
|
||||
if obj, err := amf.decodeObject(); err == nil {
|
||||
msg.TransactionId = uint64(obj.(float64))
|
||||
}
|
||||
|
||||
obj, err := amf.decodeObject()
|
||||
if err == nil && obj != nil {
|
||||
msg.Object = obj
|
||||
} else if obj, err := amf.decodeObject(); err == nil {
|
||||
msg.Object = obj
|
||||
}
|
||||
}
|
||||
func (msg *ResponsePlayMessage) Decode3(chunk *Chunk) {
|
||||
chunk.Body = chunk.Body[1:]
|
||||
msg.Decode0(chunk)
|
||||
}
|
||||
|
||||
//
|
||||
// Response Publish Message
|
||||
//
|
||||
type ResponsePublishMessage struct {
|
||||
CommandMessage
|
||||
Properties interface{} `json:",omitempty"`
|
||||
Infomation interface{} `json:",omitempty"`
|
||||
StreamID uint32
|
||||
}
|
||||
|
||||
func (msg *ResponsePublishMessage) GetStreamID() uint32 {
|
||||
return msg.StreamID
|
||||
}
|
||||
|
||||
// 命令名 -> 命令名,设置为"OnStatus"
|
||||
// 传输ID -> 0
|
||||
// 属性 -> null
|
||||
// 信息 -> level, code, description
|
||||
|
||||
func (msg *ResponsePublishMessage) Encode() []byte {
|
||||
amf := newAMFEncoder()
|
||||
amf.writeString(msg.CommandName)
|
||||
amf.writeNumber(float64(msg.TransactionId))
|
||||
amf.writeNull()
|
||||
|
||||
if msg.Properties != nil {
|
||||
amf.encodeObject(msg.Properties.(AMFObjects))
|
||||
}
|
||||
if msg.Infomation != nil {
|
||||
amf.encodeObject(msg.Infomation.(AMFObjects))
|
||||
}
|
||||
|
||||
return amf.Bytes()
|
||||
}
|
||||
|
||||
/*
|
||||
func (msg *ResponsePublishMessage) Encode3() {
|
||||
}*/
|
||||
|
||||
//
|
||||
// Response Seek Message
|
||||
//
|
||||
type ResponseSeekMessage struct {
|
||||
CommandMessage
|
||||
Description string
|
||||
}
|
||||
|
||||
func (msg *ResponseSeekMessage) Encode0() {
|
||||
}
|
||||
|
||||
//func (msg *ResponseSeekMessage) Encode3() {
|
||||
//}
|
||||
|
||||
//
|
||||
// Response Pause Message
|
||||
//
|
||||
type ResponsePauseMessage struct {
|
||||
CommandMessage
|
||||
Description string
|
||||
}
|
||||
|
||||
// 命令名 -> 命令名,设置为"OnStatus"
|
||||
// 传输ID -> 0
|
||||
// 描述
|
||||
|
||||
func (msg *ResponsePauseMessage) Encode0() {
|
||||
}
|
||||
|
||||
//func (msg *ResponsePauseMessage) Encode3() {
|
||||
//}
|
||||
|
||||
//
|
||||
// Response Message
|
||||
//
|
||||
type ResponseMessage struct {
|
||||
CommandMessage
|
||||
Properties interface{} `json:",omitempty"`
|
||||
Infomation interface{} `json:",omitempty"`
|
||||
Description string
|
||||
}
|
||||
|
||||
func (msg *ResponseMessage) Encode0() {
|
||||
}
|
||||
|
||||
//func (msg *ResponseMessage) Encode3() {
|
||||
//}
|
||||
|
||||
func (msg *ResponseMessage) Decode0(chunk *Chunk) {
|
||||
amf := newAMFDecoder(chunk.Body)
|
||||
if obj, err := amf.decodeObject(); err == nil {
|
||||
msg.CommandName = obj.(string)
|
||||
}
|
||||
if obj, err := amf.decodeObject(); err == nil {
|
||||
msg.TransactionId = uint64(obj.(float64))
|
||||
}
|
||||
}
|
||||
|
||||
// User Control Message 4.
|
||||
// The client or the server sends this message to notify the peer about the user control events.
|
||||
// For information about the message format, see Section 6.2.
|
||||
|
||||
// The following user control event types are supported:
|
||||
|
||||
// Stream Begin (=0)
|
||||
// The server sends this event to notify the client that a stream has become functional and can be
|
||||
// used for communication. By default, this event is sent on ID 0 after the application connect
|
||||
// command is successfully received from the client. The event data is 4-byte and represents
|
||||
// the stream ID of the stream that became functional.
|
||||
type StreamIDMessage struct {
|
||||
UserControlMessage
|
||||
StreamID uint32
|
||||
}
|
||||
|
||||
func (msg *StreamIDMessage) Encode() (b []byte) {
|
||||
b = make([]byte, 6)
|
||||
util.BigEndian.PutUint16(b, msg.EventType)
|
||||
util.BigEndian.PutUint32(b[2:], msg.StreamID)
|
||||
msg.EventData = b[2:]
|
||||
return
|
||||
}
|
||||
|
||||
// SetBuffer Length (=3)
|
||||
// The client sends this event to inform the server of the buffer size (in milliseconds) that is
|
||||
// used to buffer any data coming over a stream. This event is sent before the server starts |
|
||||
// processing the stream. The first 4 bytes of the event data represent the stream ID and the next |
|
||||
// 4 bytes represent the buffer length, in milliseconds.
|
||||
type SetBufferMessage struct {
|
||||
StreamIDMessage
|
||||
Millisecond uint32
|
||||
}
|
||||
|
||||
func (msg *SetBufferMessage) Encode() []byte {
|
||||
b := make([]byte, 10)
|
||||
util.BigEndian.PutUint16(b, msg.EventType)
|
||||
util.BigEndian.PutUint32(b[2:], msg.StreamID)
|
||||
util.BigEndian.PutUint32(b[6:], msg.Millisecond)
|
||||
msg.EventData = b[2:]
|
||||
return b
|
||||
}
|
||||
|
||||
// PingRequest (=6)
|
||||
// The server sends this event to test whether the client is reachable. Event data is a 4-byte
|
||||
// timestamp, representing the local server time when the server dispatched the command.
|
||||
// The client responds with PingResponse on receiving MsgPingRequest.
|
||||
type PingRequestMessage struct {
|
||||
UserControlMessage
|
||||
Timestamp uint32
|
||||
}
|
||||
|
||||
func (msg *PingRequestMessage) Encode() (b []byte) {
|
||||
b = make([]byte, 6)
|
||||
util.BigEndian.PutUint16(b, msg.EventType)
|
||||
util.BigEndian.PutUint32(b[2:], msg.Timestamp)
|
||||
msg.EventData = b[2:]
|
||||
return
|
||||
}
|
||||
|
||||
func (msg *UserControlMessage) Encode() []byte {
|
||||
b := make([]byte, 2)
|
||||
util.BigEndian.PutUint16(b, msg.EventType)
|
||||
msg.EventData = b[2:]
|
||||
return b
|
||||
}
|
730
plugins/rtmp/netConnection.go
Normal file
730
plugins/rtmp/netConnection.go
Normal file
@@ -0,0 +1,730 @@
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"github.com/langhuihui/monibuca/monica/pool"
|
||||
"github.com/langhuihui/monibuca/monica/util"
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
const (
|
||||
SEND_CHUNK_SIZE_MESSAGE = "Send Chunk Size Message"
|
||||
SEND_ACK_MESSAGE = "Send Acknowledgement Message"
|
||||
SEND_ACK_WINDOW_SIZE_MESSAGE = "Send Window Acknowledgement Size Message"
|
||||
SEND_SET_PEER_BANDWIDTH_MESSAGE = "Send Set Peer Bandwidth Message"
|
||||
|
||||
SEND_STREAM_BEGIN_MESSAGE = "Send Stream Begin Message"
|
||||
SEND_SET_BUFFER_LENGTH_MESSAGE = "Send Set Buffer Lengh Message"
|
||||
SEND_STREAM_IS_RECORDED_MESSAGE = "Send Stream Is Recorded Message"
|
||||
|
||||
SEND_PING_REQUEST_MESSAGE = "Send Ping Request Message"
|
||||
SEND_PING_RESPONSE_MESSAGE = "Send Ping Response Message"
|
||||
|
||||
SEND_CONNECT_MESSAGE = "Send Connect Message"
|
||||
SEND_CONNECT_RESPONSE_MESSAGE = "Send Connect Response Message"
|
||||
|
||||
SEND_CREATE_STREAM_MESSAGE = "Send Create Stream Message"
|
||||
SEND_CREATE_STREAM_RESPONSE_MESSAGE = "Send Create Stream Response Message"
|
||||
|
||||
SEND_PLAY_MESSAGE = "Send Play Message"
|
||||
SEND_PLAY_RESPONSE_MESSAGE = "Send Play Response Message"
|
||||
|
||||
SEND_PUBLISH_RESPONSE_MESSAGE = "Send Publish Response Message"
|
||||
SEND_PUBLISH_START_MESSAGE = "Send Publish Start Message"
|
||||
|
||||
SEND_UNPUBLISH_RESPONSE_MESSAGE = "Send Unpublish Response Message"
|
||||
|
||||
SEND_AUDIO_MESSAGE = "Send Audio Message"
|
||||
SEND_FULL_AUDIO_MESSAGE = "Send Full Audio Message"
|
||||
SEND_VIDEO_MESSAGE = "Send Video Message"
|
||||
SEND_FULL_VDIEO_MESSAGE = "Send Full Video Message"
|
||||
)
|
||||
|
||||
func newConnectResponseMessageData(objectEncoding float64) (amfobj AMFObjects) {
|
||||
amfobj = newAMFObjects()
|
||||
amfobj["fmsVer"] = "monibuca/1.0"
|
||||
amfobj["capabilities"] = 31
|
||||
amfobj["mode"] = 1
|
||||
amfobj["Author"] = "dexter"
|
||||
amfobj["level"] = Level_Status
|
||||
amfobj["code"] = NetConnection_Connect_Success
|
||||
amfobj["objectEncoding"] = uint64(objectEncoding)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func newPublishResponseMessageData(streamid uint32, code, level string) (amfobj AMFObjects) {
|
||||
amfobj = newAMFObjects()
|
||||
amfobj["code"] = code
|
||||
amfobj["level"] = level
|
||||
amfobj["streamid"] = streamid
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func newPlayResponseMessageData(streamid uint32, code, level string) (amfobj AMFObjects) {
|
||||
amfobj = newAMFObjects()
|
||||
amfobj["code"] = code
|
||||
amfobj["level"] = level
|
||||
amfobj["streamid"] = streamid
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type NetConnection struct {
|
||||
*bufio.ReadWriter
|
||||
bandwidth uint32
|
||||
readSeqNum uint32 // 当前读的字节
|
||||
writeSeqNum uint32 // 当前写的字节
|
||||
totalWrite uint32 // 总共写了多少字节
|
||||
totalRead uint32 // 总共读了多少字节
|
||||
writeChunkSize int
|
||||
readChunkSize int
|
||||
incompleteRtmpBody map[uint32][]byte // 完整的RtmpBody,在网络上是被分成一块一块的,需要将其组装起来
|
||||
nextStreamID func(uint32) uint32 // 下一个流ID
|
||||
streamID uint32 // 流ID
|
||||
rtmpHeader map[uint32]*ChunkHeader // RtmpHeader
|
||||
objectEncoding float64
|
||||
appName string
|
||||
}
|
||||
|
||||
func (conn *NetConnection) OnConnect() (err error) {
|
||||
var msg *Chunk
|
||||
if msg, err = conn.RecvMessage(); err == nil {
|
||||
defer chunkMsgPool.Put(msg)
|
||||
if connect, ok := msg.MsgData.(*CallMessage); ok {
|
||||
if connect.CommandName == "connect" {
|
||||
app := DecodeAMFObject(connect.Object, "app") // 客户端要连接到的服务应用名
|
||||
objectEncoding := DecodeAMFObject(connect.Object, "objectEncoding") // AMF编码方法
|
||||
if objectEncoding != nil {
|
||||
conn.objectEncoding = objectEncoding.(float64)
|
||||
}
|
||||
conn.appName = app.(string)
|
||||
log.Printf("app:%v,objectEncoding:%v", app, objectEncoding)
|
||||
err = conn.SendMessage(SEND_ACK_WINDOW_SIZE_MESSAGE, uint32(512<<10))
|
||||
err = conn.SendMessage(SEND_SET_PEER_BANDWIDTH_MESSAGE, uint32(512<<10))
|
||||
err = conn.SendMessage(SEND_STREAM_BEGIN_MESSAGE, nil)
|
||||
err = conn.SendMessage(SEND_CONNECT_RESPONSE_MESSAGE, conn.objectEncoding)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (conn *NetConnection) SendMessage(message string, args interface{}) error {
|
||||
switch message {
|
||||
case SEND_CHUNK_SIZE_MESSAGE:
|
||||
size, ok := args.(uint32)
|
||||
if !ok {
|
||||
return errors.New(SEND_CHUNK_SIZE_MESSAGE + ", The parameter only one(size uint32)!")
|
||||
}
|
||||
return conn.writeMessage(RTMP_MSG_CHUNK_SIZE, Uint32Message(size))
|
||||
case SEND_ACK_MESSAGE:
|
||||
num, ok := args.(uint32)
|
||||
if !ok {
|
||||
return errors.New(SEND_ACK_MESSAGE + ", The parameter only one(number uint32)!")
|
||||
}
|
||||
return conn.writeMessage(RTMP_MSG_ACK, Uint32Message(num))
|
||||
case SEND_ACK_WINDOW_SIZE_MESSAGE:
|
||||
size, ok := args.(uint32)
|
||||
if !ok {
|
||||
return errors.New(SEND_ACK_WINDOW_SIZE_MESSAGE + ", The parameter only one(size uint32)!")
|
||||
}
|
||||
return conn.writeMessage(RTMP_MSG_ACK_SIZE, Uint32Message(size))
|
||||
case SEND_SET_PEER_BANDWIDTH_MESSAGE:
|
||||
size, ok := args.(uint32)
|
||||
if !ok {
|
||||
return errors.New(SEND_SET_PEER_BANDWIDTH_MESSAGE + ", The parameter only one(size uint32)!")
|
||||
}
|
||||
return conn.writeMessage(RTMP_MSG_BANDWIDTH, &SetPeerBandwidthMessage{
|
||||
AcknowledgementWindowsize: size,
|
||||
LimitType: byte(2),
|
||||
})
|
||||
case SEND_STREAM_BEGIN_MESSAGE:
|
||||
if args != nil {
|
||||
return errors.New(SEND_STREAM_BEGIN_MESSAGE + ", The parameter is nil")
|
||||
}
|
||||
return conn.writeMessage(RTMP_MSG_USER_CONTROL, &StreamIDMessage{UserControlMessage{EventType: RTMP_USER_STREAM_BEGIN}, conn.streamID})
|
||||
case SEND_STREAM_IS_RECORDED_MESSAGE:
|
||||
if args != nil {
|
||||
return errors.New(SEND_STREAM_IS_RECORDED_MESSAGE + ", The parameter is nil")
|
||||
}
|
||||
return conn.writeMessage(RTMP_MSG_USER_CONTROL, &StreamIDMessage{UserControlMessage{EventType: RTMP_USER_STREAM_IS_RECORDED}, conn.streamID})
|
||||
|
||||
case SEND_SET_BUFFER_LENGTH_MESSAGE:
|
||||
if args != nil {
|
||||
return errors.New(SEND_SET_BUFFER_LENGTH_MESSAGE + ", The parameter is nil")
|
||||
}
|
||||
m := new(SetBufferMessage)
|
||||
m.EventType = RTMP_USER_SET_BUFFLEN
|
||||
m.Millisecond = 100
|
||||
m.StreamID = conn.streamID
|
||||
return conn.writeMessage(RTMP_MSG_USER_CONTROL, m)
|
||||
case SEND_PING_REQUEST_MESSAGE:
|
||||
if args != nil {
|
||||
return errors.New(SEND_PING_REQUEST_MESSAGE + ", The parameter is nil")
|
||||
}
|
||||
return conn.writeMessage(RTMP_MSG_USER_CONTROL, &UserControlMessage{EventType: RTMP_USER_PING_REQUEST})
|
||||
case SEND_PING_RESPONSE_MESSAGE:
|
||||
if args != nil {
|
||||
return errors.New(SEND_PING_RESPONSE_MESSAGE + ", The parameter is nil")
|
||||
}
|
||||
return conn.writeMessage(RTMP_MSG_USER_CONTROL, &UserControlMessage{EventType: RTMP_USER_PING_RESPONSE})
|
||||
case SEND_CREATE_STREAM_MESSAGE:
|
||||
if args != nil {
|
||||
return errors.New(SEND_CREATE_STREAM_MESSAGE + ", The parameter is nil")
|
||||
}
|
||||
|
||||
m := &CreateStreamMessage{}
|
||||
m.CommandName = "createStream"
|
||||
m.TransactionId = 1
|
||||
return conn.writeMessage(RTMP_MSG_AMF0_COMMAND, m)
|
||||
case SEND_CREATE_STREAM_RESPONSE_MESSAGE:
|
||||
tid, ok := args.(uint64)
|
||||
if !ok {
|
||||
return errors.New(SEND_CREATE_STREAM_RESPONSE_MESSAGE + ", The parameter only one(TransactionId uint64)!")
|
||||
}
|
||||
m := &ResponseCreateStreamMessage{}
|
||||
m.CommandName = Response_Result
|
||||
m.TransactionId = tid
|
||||
m.StreamId = conn.streamID
|
||||
return conn.writeMessage(RTMP_MSG_AMF0_COMMAND, m)
|
||||
case SEND_PLAY_MESSAGE:
|
||||
data, ok := args.(map[interface{}]interface{})
|
||||
if !ok {
|
||||
errors.New(SEND_PLAY_MESSAGE + ", The parameter is map[interface{}]interface{}")
|
||||
}
|
||||
m := new(PlayMessage)
|
||||
m.CommandName = "play"
|
||||
m.TransactionId = 1
|
||||
for i, v := range data {
|
||||
if i == "StreamName" {
|
||||
m.StreamName = v.(string)
|
||||
} else if i == "Start" {
|
||||
m.Start = v.(uint64)
|
||||
} else if i == "Duration" {
|
||||
m.Duration = v.(uint64)
|
||||
} else if i == "Rest" {
|
||||
m.Rest = v.(bool)
|
||||
}
|
||||
}
|
||||
return conn.writeMessage(RTMP_MSG_AMF0_COMMAND, m)
|
||||
case SEND_PLAY_RESPONSE_MESSAGE:
|
||||
data, ok := args.(AMFObjects)
|
||||
if !ok {
|
||||
errors.New(SEND_PLAY_RESPONSE_MESSAGE + ", The parameter is AMFObjects(map[string]interface{})")
|
||||
}
|
||||
|
||||
obj := newAMFObjects()
|
||||
var streamID uint32
|
||||
|
||||
for i, v := range data {
|
||||
switch i {
|
||||
case "code", "level":
|
||||
obj[i] = v
|
||||
case "streamid":
|
||||
if t, ok := v.(uint32); ok {
|
||||
streamID = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
obj["clientid"] = 1
|
||||
|
||||
m := new(ResponsePlayMessage)
|
||||
m.CommandName = Response_OnStatus
|
||||
m.TransactionId = 0
|
||||
m.Object = obj
|
||||
m.StreamID = streamID
|
||||
return conn.writeMessage(RTMP_MSG_AMF0_COMMAND, m)
|
||||
case SEND_CONNECT_RESPONSE_MESSAGE:
|
||||
data := newConnectResponseMessageData(args.(float64))
|
||||
//if !ok {
|
||||
// errors.New(SEND_CONNECT_RESPONSE_MESSAGE + ", The parameter is AMFObjects(map[string]interface{})")
|
||||
//}
|
||||
|
||||
//pro := newAMFObjects()
|
||||
info := newAMFObjects()
|
||||
|
||||
//for i, v := range data {
|
||||
// switch i {
|
||||
// case "fmsVer", "capabilities", "mode", "Author", "level", "code", "objectEncoding":
|
||||
// pro[i] = v
|
||||
// }
|
||||
//}
|
||||
m := new(ResponseConnectMessage)
|
||||
m.CommandName = Response_Result
|
||||
m.TransactionId = 1
|
||||
m.Properties = data
|
||||
m.Infomation = info
|
||||
return conn.writeMessage(RTMP_MSG_AMF0_COMMAND, m)
|
||||
case SEND_CONNECT_MESSAGE:
|
||||
data, ok := args.(AMFObjects)
|
||||
if !ok {
|
||||
errors.New(SEND_CONNECT_MESSAGE + ", The parameter is AMFObjects(map[string]interface{})")
|
||||
}
|
||||
|
||||
obj := newAMFObjects()
|
||||
info := newAMFObjects()
|
||||
|
||||
for i, v := range data {
|
||||
switch i {
|
||||
case "videoFunction", "objectEncoding", "fpad", "flashVer", "capabilities", "pageUrl", "swfUrl", "tcUrl", "videoCodecs", "app", "audioCodecs":
|
||||
obj[i] = v
|
||||
}
|
||||
}
|
||||
|
||||
m := new(CallMessage)
|
||||
m.CommandName = "connect"
|
||||
m.TransactionId = 1
|
||||
m.Object = obj
|
||||
m.Optional = info
|
||||
return conn.writeMessage(RTMP_MSG_AMF0_COMMAND, m)
|
||||
case SEND_PUBLISH_RESPONSE_MESSAGE, SEND_PUBLISH_START_MESSAGE:
|
||||
data, ok := args.(AMFObjects)
|
||||
if !ok {
|
||||
errors.New(SEND_CONNECT_MESSAGE + "or" + SEND_PUBLISH_START_MESSAGE + ", The parameter is AMFObjects(map[string]interface{})")
|
||||
}
|
||||
|
||||
info := newAMFObjects()
|
||||
var streamID uint32
|
||||
|
||||
for i, v := range data {
|
||||
switch i {
|
||||
case "code", "level":
|
||||
info[i] = v
|
||||
case "streamid":
|
||||
if t, ok := v.(uint32); ok {
|
||||
streamID = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info["clientid"] = 1
|
||||
|
||||
m := new(ResponsePublishMessage)
|
||||
m.CommandName = Response_OnStatus
|
||||
m.TransactionId = 0
|
||||
m.Infomation = info
|
||||
m.StreamID = streamID
|
||||
return conn.writeMessage(RTMP_MSG_AMF0_COMMAND, m)
|
||||
case SEND_UNPUBLISH_RESPONSE_MESSAGE:
|
||||
case SEND_FULL_AUDIO_MESSAGE:
|
||||
audio, ok := args.(*pool.SendPacket)
|
||||
if !ok {
|
||||
errors.New(message + ", The parameter is AVPacket")
|
||||
}
|
||||
|
||||
return conn.sendAVMessage(audio, true, true)
|
||||
case SEND_AUDIO_MESSAGE:
|
||||
audio, ok := args.(*pool.SendPacket)
|
||||
if !ok {
|
||||
errors.New(message + ", The parameter is AVPacket")
|
||||
}
|
||||
|
||||
return conn.sendAVMessage(audio, true, false)
|
||||
case SEND_FULL_VDIEO_MESSAGE:
|
||||
video, ok := args.(*pool.SendPacket)
|
||||
if !ok {
|
||||
errors.New(message + ", The parameter is AVPacket")
|
||||
}
|
||||
|
||||
return conn.sendAVMessage(video, false, true)
|
||||
case SEND_VIDEO_MESSAGE:
|
||||
{
|
||||
video, ok := args.(*pool.SendPacket)
|
||||
if !ok {
|
||||
errors.New(message + ", The parameter is AVPacket")
|
||||
}
|
||||
|
||||
return conn.sendAVMessage(video, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("send message no exist")
|
||||
}
|
||||
|
||||
// 当发送音视频数据的时候,当块类型为12的时候,Chunk Message Header有一个字段TimeStamp,指明一个时间
|
||||
// 当块类型为4,8的时候,Chunk Message Header有一个字段TimeStamp Delta,记录与上一个Chunk的时间差值
|
||||
// 当块类型为0的时候,Chunk Message Header没有时间字段,与上一个Chunk时间值相同
|
||||
func (conn *NetConnection) sendAVMessage(av *pool.SendPacket, isAudio bool, isFirst bool) error {
|
||||
if conn.writeSeqNum > conn.bandwidth {
|
||||
conn.totalWrite += conn.writeSeqNum
|
||||
conn.writeSeqNum = 0
|
||||
conn.SendMessage(SEND_ACK_MESSAGE, conn.totalWrite)
|
||||
conn.SendMessage(SEND_PING_REQUEST_MESSAGE, nil)
|
||||
}
|
||||
|
||||
var err error
|
||||
var mark []byte
|
||||
var need []byte
|
||||
var head *ChunkHeader
|
||||
|
||||
if isAudio {
|
||||
head = newRtmpHeader(RTMP_CSID_AUDIO, av.Timestamp, uint32(len(av.Packet.Payload)), RTMP_MSG_AUDIO, conn.streamID, 0)
|
||||
} else {
|
||||
head = newRtmpHeader(RTMP_CSID_VIDEO, av.Timestamp, uint32(len(av.Packet.Payload)), RTMP_MSG_VIDEO, conn.streamID, 0)
|
||||
}
|
||||
|
||||
// 第一次是发送关键帧,需要完整的消息头(Chunk Basic Header(1) + Chunk Message Header(11) + Extended Timestamp(4)(可能会要包括))
|
||||
// 后面开始,就是直接发送音视频数据,那么直接发送,不需要完整的块(Chunk Basic Header(1) + Chunk Message Header(7))
|
||||
// 当Chunk Type为0时(即Chunk12),
|
||||
if isFirst {
|
||||
mark, need, err = encodeChunk12(head, av.Packet.Payload, conn.writeChunkSize)
|
||||
} else {
|
||||
mark, need, err = encodeChunk8(head, av.Packet.Payload, conn.writeChunkSize)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write(mark)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.writeSeqNum += uint32(len(mark))
|
||||
|
||||
// 如果音视频数据太大,一次发送不完,那么在这里进行分割(data + Chunk Basic Header(1))
|
||||
for need != nil && len(need) > 0 {
|
||||
mark, need, err = encodeChunk1(head, need, conn.writeChunkSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write(mark)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.writeSeqNum += uint32(len(mark))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *NetConnection) readChunk() (msg *Chunk, err error) {
|
||||
head, err := conn.ReadByte()
|
||||
conn.readSeqNum++
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ChunkStreamID := uint32(head & 0x3f) // 0011 1111
|
||||
ChunkType := (head & 0xc0) >> 6 // 1100 0000
|
||||
|
||||
// 如果块流ID为0,1的话,就需要计算.
|
||||
ChunkStreamID, err = conn.readChunkStreamID(ChunkStreamID)
|
||||
if err != nil {
|
||||
return nil, errors.New("get chunk stream id error :" + err.Error())
|
||||
}
|
||||
|
||||
h, ok := conn.rtmpHeader[ChunkStreamID]
|
||||
if !ok {
|
||||
h = new(ChunkHeader)
|
||||
h.ChunkStreamID = ChunkStreamID
|
||||
h.ChunkType = ChunkType
|
||||
conn.rtmpHeader[ChunkStreamID] = h
|
||||
}
|
||||
currentBody, ok := conn.incompleteRtmpBody[ChunkStreamID]
|
||||
if ChunkType != 3 && ok {
|
||||
// 如果块类型不为3,那么这个rtmp的body应该为空.
|
||||
return nil, errors.New("incompleteRtmpBody error")
|
||||
}
|
||||
|
||||
chunkHead, err := conn.readChunkType(h, ChunkType)
|
||||
if err != nil {
|
||||
return nil, errors.New("get chunk type error :" + err.Error())
|
||||
}
|
||||
msgLen := int(chunkHead.MessageLength)
|
||||
if !ok {
|
||||
currentBody = (pool.GetSlice(msgLen))[:0]
|
||||
conn.incompleteRtmpBody[ChunkStreamID] = currentBody
|
||||
}
|
||||
|
||||
markRead := len(currentBody)
|
||||
needRead := conn.readChunkSize
|
||||
unRead := msgLen - markRead
|
||||
if unRead < needRead {
|
||||
needRead = unRead
|
||||
}
|
||||
if n, err := io.ReadFull(conn, currentBody[markRead:needRead+markRead]); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
markRead += n
|
||||
conn.readSeqNum += uint32(n)
|
||||
}
|
||||
currentBody = currentBody[:markRead]
|
||||
conn.incompleteRtmpBody[ChunkStreamID] = currentBody
|
||||
|
||||
// 如果读完了一个完整的块,那么就返回这个消息,没读完继续递归读块.
|
||||
if markRead == msgLen {
|
||||
|
||||
msg := chunkMsgPool.Get().(*Chunk)
|
||||
msg.Body = currentBody
|
||||
msg.ChunkHeader = chunkHead.Clone()
|
||||
GetRtmpMessage(msg)
|
||||
delete(conn.incompleteRtmpBody, ChunkStreamID)
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
return conn.readChunk()
|
||||
}
|
||||
|
||||
func (conn *NetConnection) readChunkStreamID(csid uint32) (chunkStreamID uint32, err error) {
|
||||
chunkStreamID = csid
|
||||
|
||||
switch csid {
|
||||
case 0:
|
||||
{
|
||||
u8, err := conn.ReadByte()
|
||||
conn.readSeqNum++
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
chunkStreamID = 64 + uint32(u8)
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
u16_0, err1 := conn.ReadByte()
|
||||
if err1 != nil {
|
||||
return 0, err1
|
||||
}
|
||||
u16_1, err1 := conn.ReadByte()
|
||||
if err1 != nil {
|
||||
return 0, err1
|
||||
}
|
||||
conn.readSeqNum += 2
|
||||
chunkStreamID = 64 + uint32(u16_0) + (uint32(u16_1) << 8)
|
||||
}
|
||||
}
|
||||
|
||||
return chunkStreamID, nil
|
||||
}
|
||||
|
||||
func (conn *NetConnection) readChunkType(h *ChunkHeader, chunkType byte) (head *ChunkHeader, err error) {
|
||||
switch chunkType {
|
||||
case 0:
|
||||
{
|
||||
// Timestamp 3 bytes
|
||||
b := pool.GetSlice(3)
|
||||
if _, err := io.ReadFull(conn, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.readSeqNum += 3
|
||||
h.Timestamp = util.BigEndian.Uint24(b) //type = 0的时间戳为绝对时间,其他的都为相对时间
|
||||
|
||||
// Message Length 3 bytes
|
||||
if _, err = io.ReadFull(conn, b); err != nil { // 读取Message Length,这里的长度指的是一条信令或者一帧视频数据或音频数据的长度,而不是Chunk data的长度.
|
||||
return nil, err
|
||||
}
|
||||
conn.readSeqNum += 3
|
||||
h.MessageLength = util.BigEndian.Uint24(b)
|
||||
pool.RecycleSlice(b)
|
||||
// Message Type ID 1 bytes
|
||||
v, err := conn.ReadByte() // 读取Message Type ID
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.readSeqNum++
|
||||
h.MessageTypeID = v
|
||||
|
||||
// Message Stream ID 4bytes
|
||||
bb := pool.GetSlice(4)
|
||||
if _, err = io.ReadFull(conn, bb); err != nil { // 读取Message Stream ID
|
||||
return nil, err
|
||||
}
|
||||
conn.readSeqNum += 4
|
||||
h.MessageStreamID = util.LittleEndian.Uint32(bb)
|
||||
|
||||
// ExtendTimestamp 4 bytes
|
||||
if h.Timestamp == 0xffffff { // 对于type 0的chunk,绝对时间戳在这里表示,如果时间戳值大于等于0xffffff(16777215),该值必须是0xffffff,且时间戳扩展字段必须发送,其他情况没有要求
|
||||
if _, err = io.ReadFull(conn, bb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.readSeqNum += 4
|
||||
h.ExtendTimestamp = util.BigEndian.Uint32(bb)
|
||||
}
|
||||
pool.RecycleSlice(bb)
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// Timestamp 3 bytes
|
||||
b := pool.GetSlice(3)
|
||||
if _, err = io.ReadFull(conn, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.readSeqNum += 3
|
||||
h.ChunkType = chunkType
|
||||
h.Timestamp = util.BigEndian.Uint24(b)
|
||||
|
||||
// Message Length 3 bytes
|
||||
if _, err = io.ReadFull(conn, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.readSeqNum += 3
|
||||
h.MessageLength = util.BigEndian.Uint24(b)
|
||||
pool.RecycleSlice(b)
|
||||
// Message Type ID 1 bytes
|
||||
v, err := conn.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.readSeqNum++
|
||||
h.MessageTypeID = v
|
||||
|
||||
// ExtendTimestamp 4 bytes
|
||||
if h.Timestamp == 0xffffff {
|
||||
bb := pool.GetSlice(4)
|
||||
if _, err := io.ReadFull(conn, bb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.readSeqNum += 4
|
||||
h.ExtendTimestamp = util.BigEndian.Uint32(bb)
|
||||
pool.RecycleSlice(bb)
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// Timestamp 3 bytes
|
||||
b := pool.GetSlice(3)
|
||||
if _, err = io.ReadFull(conn, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.readSeqNum += 3
|
||||
h.ChunkType = chunkType
|
||||
h.Timestamp = util.BigEndian.Uint24(b)
|
||||
pool.RecycleSlice(b)
|
||||
// ExtendTimestamp 4 bytes
|
||||
if h.Timestamp == 0xffffff {
|
||||
bb := pool.GetSlice(4)
|
||||
if _, err := io.ReadFull(conn, bb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.readSeqNum += 4
|
||||
h.ExtendTimestamp = util.BigEndian.Uint32(bb)
|
||||
pool.RecycleSlice(bb)
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
h.ChunkType = chunkType
|
||||
}
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (conn *NetConnection) RecvMessage() (msg *Chunk, err error) {
|
||||
if conn.readSeqNum >= conn.bandwidth {
|
||||
conn.totalRead += conn.readSeqNum
|
||||
conn.readSeqNum = 0
|
||||
//sendAck(conn, conn.totalRead)
|
||||
conn.SendMessage(SEND_ACK_MESSAGE, conn.totalRead)
|
||||
}
|
||||
|
||||
msg, err = conn.readChunk()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果消息是类型是用户控制消息,那么我们就简单做一些相应的处理,
|
||||
// 然后继续读取下一个消息.如果不是用户控制消息,就将消息返回就好.
|
||||
messageType := msg.MessageTypeID
|
||||
if RTMP_MSG_CHUNK_SIZE <= messageType && messageType <= RTMP_MSG_EDGE {
|
||||
switch messageType {
|
||||
case RTMP_MSG_CHUNK_SIZE:
|
||||
m := msg.MsgData.(Uint32Message)
|
||||
conn.readChunkSize = int(m)
|
||||
return conn.RecvMessage()
|
||||
case RTMP_MSG_ABORT:
|
||||
m := msg.MsgData.(Uint32Message)
|
||||
delete(conn.incompleteRtmpBody, uint32(m))
|
||||
return conn.RecvMessage()
|
||||
case RTMP_MSG_ACK, RTMP_MSG_EDGE:
|
||||
return conn.RecvMessage()
|
||||
case RTMP_MSG_USER_CONTROL:
|
||||
if _, ok := msg.MsgData.(*PingRequestMessage); ok {
|
||||
//sendPingResponse(conn)
|
||||
conn.SendMessage(SEND_PING_RESPONSE_MESSAGE, nil)
|
||||
}
|
||||
return conn.RecvMessage()
|
||||
case RTMP_MSG_ACK_SIZE:
|
||||
m := msg.MsgData.(Uint32Message)
|
||||
conn.bandwidth = uint32(m)
|
||||
return conn.RecvMessage()
|
||||
case RTMP_MSG_BANDWIDTH:
|
||||
m := msg.MsgData.(*SetPeerBandwidthMessage)
|
||||
conn.bandwidth = m.AcknowledgementWindowsize
|
||||
return conn.RecvMessage()
|
||||
}
|
||||
}
|
||||
|
||||
return msg, err
|
||||
}
|
||||
func (conn *NetConnection) writeMessage(t byte, msg RtmpMessage) error {
|
||||
body := msg.Encode()
|
||||
head := newChunkHeader(t)
|
||||
head.MessageLength = uint32(len(body))
|
||||
if sid, ok := msg.(HaveStreamID); ok {
|
||||
head.MessageStreamID = sid.GetStreamID()
|
||||
}
|
||||
if conn.writeSeqNum > conn.bandwidth {
|
||||
conn.totalWrite += conn.writeSeqNum
|
||||
conn.writeSeqNum = 0
|
||||
conn.SendMessage(SEND_ACK_MESSAGE, conn.totalWrite)
|
||||
conn.SendMessage(SEND_PING_REQUEST_MESSAGE, nil)
|
||||
}
|
||||
|
||||
mark, need, err := encodeChunk12(head, body, conn.writeChunkSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write(mark)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.writeSeqNum += uint32(len(mark))
|
||||
|
||||
for need != nil && len(need) > 0 {
|
||||
mark, need, err = encodeChunk1(head, need, conn.writeChunkSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write(mark)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.writeSeqNum += uint32(len(mark))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
187
plugins/rtmp/netStream.go
Normal file
187
plugins/rtmp/netStream.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package rtmp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
. "github.com/langhuihui/monibuca/monica"
|
||||
"github.com/langhuihui/monibuca/monica/avformat"
|
||||
"github.com/langhuihui/monibuca/monica/pool"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RTMP struct {
|
||||
InputStream
|
||||
}
|
||||
|
||||
func ListenRtmp(addr string) error {
|
||||
defer log.Println("rtmp server start!")
|
||||
// defer fmt.Println("server start!")
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var tempDelay time.Duration
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Temporary() {
|
||||
if tempDelay == 0 {
|
||||
tempDelay = 5 * time.Millisecond
|
||||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
if max := 1 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
}
|
||||
fmt.Printf("rtmp: Accept error: %v; retrying in %v", err, tempDelay)
|
||||
time.Sleep(tempDelay)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
tempDelay = 0
|
||||
go processRtmp(conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var gstreamid = uint32(64)
|
||||
|
||||
func processRtmp(conn net.Conn) {
|
||||
var room *Room
|
||||
streams := make(map[uint32]*OutputStream)
|
||||
defer func() {
|
||||
conn.Close()
|
||||
if room != nil {
|
||||
room.Cancel()
|
||||
}
|
||||
}()
|
||||
var totalDuration uint32
|
||||
nc := &NetConnection{
|
||||
ReadWriter: bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)),
|
||||
writeChunkSize: RTMP_DEFAULT_CHUNK_SIZE,
|
||||
readChunkSize: RTMP_DEFAULT_CHUNK_SIZE,
|
||||
rtmpHeader: make(map[uint32]*ChunkHeader),
|
||||
incompleteRtmpBody: make(map[uint32][]byte),
|
||||
bandwidth: RTMP_MAX_CHUNK_SIZE << 3,
|
||||
nextStreamID: func(u uint32) uint32 {
|
||||
gstreamid++
|
||||
return gstreamid
|
||||
},
|
||||
}
|
||||
/* Handshake */
|
||||
if MayBeError(Handshake(nc.ReadWriter)) {
|
||||
return
|
||||
}
|
||||
if MayBeError(nc.OnConnect()) {
|
||||
return
|
||||
}
|
||||
for {
|
||||
if msg, err := nc.RecvMessage(); err == nil {
|
||||
if msg.MessageLength <= 0 {
|
||||
continue
|
||||
}
|
||||
switch msg.MessageTypeID {
|
||||
case RTMP_MSG_AMF0_COMMAND:
|
||||
cmd := msg.MsgData.(Commander).GetCommand()
|
||||
switch cmd.CommandName {
|
||||
case "createStream":
|
||||
nc.streamID = nc.nextStreamID(msg.ChunkStreamID)
|
||||
err = nc.SendMessage(SEND_CREATE_STREAM_RESPONSE_MESSAGE, cmd.TransactionId)
|
||||
if MayBeError(err) {
|
||||
return
|
||||
}
|
||||
case "publish":
|
||||
pm := msg.MsgData.(*PublishMessage)
|
||||
streamPath := nc.appName + "/" + strings.Split(pm.PublishingName, "?")[0]
|
||||
pub := new(RTMP)
|
||||
if pub.Publish(streamPath, pub) {
|
||||
pub.FirstScreen = make([]*pool.AVPacket, 0)
|
||||
room = pub.Room
|
||||
err = nc.SendMessage(SEND_STREAM_BEGIN_MESSAGE, nil)
|
||||
err = nc.SendMessage(SEND_PUBLISH_START_MESSAGE, newPublishResponseMessageData(nc.streamID, NetStream_Publish_Start, Level_Status))
|
||||
} else {
|
||||
err = nc.SendMessage(SEND_PUBLISH_RESPONSE_MESSAGE, newPublishResponseMessageData(nc.streamID, Level_Error, NetStream_Publish_BadName))
|
||||
}
|
||||
case "play":
|
||||
pm := msg.MsgData.(*PlayMessage)
|
||||
streamPath := nc.appName + "/" + strings.Split(pm.StreamName, "?")[0]
|
||||
nc.writeChunkSize = 512
|
||||
stream := &OutputStream{SendHandler: func(packet *pool.SendPacket) (err error) {
|
||||
switch true {
|
||||
case packet.Packet.IsADTS:
|
||||
tagPacket := pool.NewAVPacket(RTMP_MSG_AUDIO)
|
||||
tagPacket.Payload = avformat.ADTSToAudioSpecificConfig(packet.Packet.Payload)
|
||||
err = nc.SendMessage(SEND_FULL_AUDIO_MESSAGE, tagPacket)
|
||||
ADTSLength := 7 + (int(packet.Packet.Payload[1]&1) << 1)
|
||||
if len(packet.Packet.Payload) > ADTSLength {
|
||||
contentPacket := pool.NewAVPacket(RTMP_MSG_AUDIO)
|
||||
contentPacket.Timestamp = packet.Timestamp
|
||||
contentPacket.Payload = make([]byte, len(packet.Packet.Payload)-ADTSLength+2)
|
||||
contentPacket.Payload[0] = 0xAF
|
||||
contentPacket.Payload[1] = 0x01 //raw AAC
|
||||
copy(contentPacket.Payload[2:], packet.Packet.Payload[ADTSLength:])
|
||||
err = nc.SendMessage(SEND_AUDIO_MESSAGE, contentPacket)
|
||||
}
|
||||
case packet.Packet.IsAVCSequence:
|
||||
err = nc.SendMessage(SEND_FULL_VDIEO_MESSAGE, packet)
|
||||
case packet.Packet.Type == RTMP_MSG_VIDEO:
|
||||
err = nc.SendMessage(SEND_VIDEO_MESSAGE, packet)
|
||||
case packet.Packet.IsAACSequence:
|
||||
err = nc.SendMessage(SEND_FULL_AUDIO_MESSAGE, packet)
|
||||
case packet.Packet.Type == RTMP_MSG_AUDIO:
|
||||
err = nc.SendMessage(SEND_AUDIO_MESSAGE, packet)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
stream.Type = "RTMP"
|
||||
stream.ID = fmt.Sprintf("%s|%d", conn.RemoteAddr().String(), nc.streamID)
|
||||
err = nc.SendMessage(SEND_CHUNK_SIZE_MESSAGE, uint32(nc.writeChunkSize))
|
||||
err = nc.SendMessage(SEND_STREAM_IS_RECORDED_MESSAGE, nil)
|
||||
err = nc.SendMessage(SEND_STREAM_BEGIN_MESSAGE, nil)
|
||||
err = nc.SendMessage(SEND_PLAY_RESPONSE_MESSAGE, newPlayResponseMessageData(nc.streamID, NetStream_Play_Reset, Level_Status))
|
||||
err = nc.SendMessage(SEND_PLAY_RESPONSE_MESSAGE, newPlayResponseMessageData(nc.streamID, NetStream_Play_Start, Level_Status))
|
||||
if err == nil {
|
||||
streams[nc.streamID] = stream
|
||||
go stream.Play(streamPath)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
case "closeStream":
|
||||
cm := msg.MsgData.(*CURDStreamMessage)
|
||||
if stream, ok := streams[cm.StreamId]; ok {
|
||||
stream.Cancel()
|
||||
delete(streams, cm.StreamId)
|
||||
}
|
||||
}
|
||||
case RTMP_MSG_AUDIO:
|
||||
pkt := pool.NewAVPacket(RTMP_MSG_AUDIO)
|
||||
if msg.Timestamp == 0xffffff {
|
||||
totalDuration += msg.ExtendTimestamp
|
||||
} else {
|
||||
totalDuration += msg.Timestamp // 绝对时间戳
|
||||
}
|
||||
pkt.Timestamp = totalDuration
|
||||
pkt.Payload = msg.Body
|
||||
room.PushAudio(pkt)
|
||||
case RTMP_MSG_VIDEO:
|
||||
pkt := pool.NewAVPacket(RTMP_MSG_VIDEO)
|
||||
if msg.Timestamp == 0xffffff {
|
||||
totalDuration += msg.ExtendTimestamp
|
||||
} else {
|
||||
totalDuration += msg.Timestamp // 绝对时间戳
|
||||
}
|
||||
pkt.Timestamp = totalDuration
|
||||
pkt.Payload = msg.Body
|
||||
room.PushVideo(pkt)
|
||||
}
|
||||
msg.Recycle()
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user