├── README.md ├── .gitignore ├── LICENSE └── mpegts.go /README.md: -------------------------------------------------------------------------------- 1 | # mpeg-ts 2 | Mpeg2TS segmenter written in pure golang 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Andrew Chernov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /mpegts.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/binary" 7 | "errors" 8 | "flag" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "log" 13 | "os" 14 | ) 15 | 16 | const ( 17 | MPEGTS_PACKET_SIZE = 188 18 | SYNC_BYTE = 0x47 19 | ) 20 | 21 | type SegmentBuffer struct { 22 | bytes.Buffer 23 | Pts int64 24 | } 25 | 26 | type MpegTSDemuxer struct { 27 | r io.Reader 28 | cur *SegmentBuffer 29 | } 30 | 31 | func NewMpegTSDemuxer(src io.Reader) *MpegTSDemuxer { 32 | return &MpegTSDemuxer{ 33 | r: src, 34 | cur: &SegmentBuffer{}, 35 | } 36 | } 37 | 38 | func (d *MpegTSDemuxer) Parse() error { 39 | 40 | buf := make([]byte, MPEGTS_PACKET_SIZE) 41 | 42 | i := 1 43 | 44 | for { 45 | 46 | _, err := io.ReadFull(d.r, buf) 47 | if err != nil { 48 | log.Println(err.Error()) 49 | if e := d.Flush(); e != nil { 50 | log.Println(e.Error()) 51 | } 52 | return err 53 | } 54 | 55 | header := binary.BigEndian.Uint32(buf) 56 | 57 | // transportError := (header & 0x800000) != 0 58 | payloadUnitStart := (header & 0x400000) != 0 59 | // transportPriority := (header & 0x200000) != 0 60 | pid := (header & 0x1fff00) >> 8 61 | // isScrambled := ((header & 0xc0) >> 8) != 0 62 | adaptationFieldExist := (header & 0x20) != 0 63 | // containsPayload := (header & 0x10) != 0 64 | // cc := header & 0xf 65 | 66 | if buf[0] != SYNC_BYTE { 67 | return errors.New("can't find sync byte 0x47") 68 | } 69 | 70 | // log.Printf("transportError:%v transportPriority:%v pid:%v isScrambled:%v adaptationFieldExist:%v containsPayload:%v cc:%v", 71 | // transportError, transportPriority, pid, 72 | // isScrambled, adaptationFieldExist, containsPayload, cc) 73 | 74 | p := buf[4:] 75 | 76 | if adaptationFieldExist { 77 | adaptationFieldLength := buf[4] 78 | 79 | p = p[adaptationFieldLength+1:] 80 | 81 | // // Set to 1 if current TS packet is in a discontinuity state 82 | // // with respect to either the continuity counter or the program clock reference 83 | // discontinuity := (buf[5] & 0x80) != 0 84 | 85 | // // Set to 1 if the PES packet in this 86 | // // TS packet starts a video/audio sequence 87 | // randomAccess := (buf[5] & 0x40) != 0 88 | 89 | // esPriority := (buf[5] & 0x20) != 0 90 | 91 | // // Set to 1 if adaptation field contains a PCR field 92 | // containsPCR := (buf[5] & 0x10) != 0 93 | 94 | // // Set to 1 if adaptation field contains an OPCR field 95 | // containsOPCR := (buf[5] & 0x08) != 0 96 | 97 | // // Set to 1 if adaptation field contains a splice countdown field 98 | // splicing := (buf[5] & 0x04) != 0 99 | 100 | // // Set to 1 if adaptation field contains private data bytes 101 | // transportPrivateData := (buf[5] & 0x02) 102 | 103 | // // Set to 1 if adaptation field contains an extension 104 | // adaptationFieldExtension := (buf[5] & 0x01) 105 | } 106 | 107 | if payloadUnitStart { 108 | log.Printf("parse PES pid: %d len:%d", pid, len(p)) 109 | 110 | if pid == 256 { 111 | log.Printf("frame:%d", i) 112 | i++ 113 | 114 | isIdr, pts, err := d.parsePes(p) 115 | if err != nil { 116 | return err 117 | } 118 | 119 | if isIdr { 120 | log.Printf("pts:%d prev pts:%d delta:%d", 121 | pts, d.cur.Pts, pts-d.cur.Pts) 122 | 123 | if d.cur.Pts != 0 { 124 | if err := d.Flush(); err != nil { 125 | return err 126 | } 127 | } 128 | 129 | d.cur.Reset() 130 | d.cur.Pts = pts 131 | } 132 | } 133 | } 134 | 135 | d.cur.Write(buf) 136 | } 137 | 138 | return nil 139 | } 140 | 141 | func (d *MpegTSDemuxer) Flush() error { 142 | filename := fmt.Sprintf("%d.ts", d.cur.Pts) 143 | err := ioutil.WriteFile(filename, d.cur.Bytes(), 0666) 144 | if err != nil { 145 | return err 146 | } 147 | return nil 148 | } 149 | 150 | func (d *MpegTSDemuxer) parsePes(p []byte) (bool, int64, error) { 151 | 152 | if !(p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x01) { 153 | return false, -1, errors.New("error start code") 154 | } 155 | 156 | streamId := p[3] 157 | 158 | log.Printf("streamId:%X", streamId) 159 | 160 | pesPacketLength := binary.BigEndian.Uint16(p[4:6]) 161 | 162 | log.Printf("pesPacketLength:%d", pesPacketLength) 163 | 164 | pesHeaderFlags := binary.BigEndian.Uint16(p[6:8]) 165 | 166 | pesHeaderLength := int(p[8]) 167 | 168 | log.Printf("pesHeaderLength:%d", pesHeaderLength) 169 | 170 | var pts, dts int64 171 | if (pesHeaderFlags & 0x8000) != 0 { 172 | pts = d.parsePesPtsDts(p[9:]) 173 | log.Printf("pts:%d", pts) 174 | } 175 | 176 | if (pesHeaderFlags & 0x4000) != 0 { 177 | dts = d.parsePesPtsDts(p[14:]) 178 | log.Printf("dts:%d", dts) 179 | } 180 | 181 | es := p[pesHeaderLength+9:] 182 | 183 | isIdr := parseEs(es) 184 | 185 | return isIdr, pts, nil 186 | } 187 | 188 | func parseEs(es []byte) bool { 189 | 190 | pos := 0 191 | for { 192 | for i := pos; i < len(es)-4; i++ { 193 | 194 | if es[i] == 0x00 && es[i+1] == 0x00 && es[i+2] == 0x01 { 195 | log.Println("Found a NAL unit with 3-byte startcode") 196 | pos = i + 3 197 | break 198 | } 199 | 200 | if es[i] == 0x00 && es[i+1] == 0x00 && es[i+2] == 0x00 && es[i+3] == 0x01 { 201 | log.Println("Found a NAL unit with 4-byte startcode") 202 | pos = i + 4 203 | break 204 | } 205 | 206 | if i >= len(es)-6 { 207 | return false 208 | } 209 | } 210 | 211 | // fragmentType := es[0] & 0x80 212 | nalType := es[pos] & 0x1F 213 | 214 | log.Printf("es:%X", es[pos:]) 215 | 216 | switch nalType { 217 | case 5: 218 | log.Println("IDR (Instantaneous Decoding Refresh) Picture") 219 | return true 220 | case 6: 221 | log.Println("SEI (Supplemental Enhancement Information)") 222 | case 7: 223 | log.Println("SPS (Sequence Parameter Set)") 224 | case 8: 225 | log.Println("PPS (Picture Parameter Set)") 226 | case 9: 227 | log.Println("Access Unit Delimiter") 228 | default: 229 | log.Println("unknown") 230 | } 231 | } 232 | 233 | return false 234 | } 235 | 236 | func (d *MpegTSDemuxer) parsePesPtsDts(p []byte) int64 { 237 | res := (int64(p[0]) & 0x0e) << 29 238 | res |= int64(binary.BigEndian.Uint16(p[1:])>>1) << 15 239 | res |= int64(binary.BigEndian.Uint16(p[3:]) >> 1) 240 | return res 241 | } 242 | 243 | func main() { 244 | flag.Parse() 245 | name := flag.Arg(0) 246 | 247 | f, err := os.Open(name) 248 | if err != nil { 249 | log.Println(err.Error()) 250 | return 251 | } 252 | defer f.Close() 253 | 254 | demux := NewMpegTSDemuxer(bufio.NewReaderSize(f, MPEGTS_PACKET_SIZE*1024)) 255 | if err := demux.Parse(); err != nil { 256 | log.Println(err.Error()) 257 | } 258 | } 259 | --------------------------------------------------------------------------------