github.com/bluenviron/mediacommon@v1.9.3/pkg/formats/mpegts/writer.go (about) 1 package mpegts 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 9 "github.com/asticode/go-astits" 10 11 "github.com/bluenviron/mediacommon/pkg/codecs/h264" 12 "github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio" 13 "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" 14 "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4video" 15 ) 16 17 const ( 18 streamIDVideo = 224 19 streamIDAudio = 192 20 21 // PCR is needed to read H265 tracks with VLC+VDPAU hardware encoder 22 // (and is probably needed by other combinations too) 23 dtsPCRDiff = (90000 / 10) 24 ) 25 26 func opusMarshalSize(packets [][]byte) int { 27 n := 0 28 for _, packet := range packets { 29 au := opusAccessUnit{ 30 ControlHeader: opusControlHeader{ 31 PayloadSize: len(packet), 32 }, 33 Packet: packet, 34 } 35 n += au.marshalSize() 36 } 37 return n 38 } 39 40 func mpeg1AudioMarshalSize(frames [][]byte) int { 41 n := 0 42 for _, frame := range frames { 43 n += len(frame) 44 } 45 return n 46 } 47 48 // Writer is a MPEG-TS writer. 49 type Writer struct { 50 nextPID uint16 51 mux *astits.Muxer 52 pcrCounter int 53 leadingTrackChosen bool 54 } 55 56 // NewWriter allocates a Writer. 57 func NewWriter( 58 bw io.Writer, 59 tracks []*Track, 60 ) *Writer { 61 w := &Writer{ 62 nextPID: 256, 63 } 64 65 w.mux = astits.NewMuxer( 66 context.Background(), 67 bw) 68 69 for _, track := range tracks { 70 if track.PID == 0 { 71 track.PID = w.nextPID 72 w.nextPID++ 73 } 74 es, _ := track.marshal() 75 76 err := w.mux.AddElementaryStream(*es) 77 if err != nil { 78 panic(err) // TODO: return error instead of panicking 79 } 80 } 81 82 // WriteTables() is not necessary 83 // since it's called automatically when WriteData() is called with 84 // * PID == PCRPID 85 // * AdaptationField != nil 86 // * RandomAccessIndicator = true 87 88 return w 89 } 90 91 // WriteH26x writes a H26x access unit. 92 func (w *Writer) WriteH26x( 93 track *Track, 94 pts int64, 95 dts int64, 96 randomAccess bool, 97 au [][]byte, 98 ) error { 99 enc, err := h264.AnnexBMarshal(au) 100 if err != nil { 101 return err 102 } 103 104 return w.writeVideo(track, pts, dts, randomAccess, enc) 105 } 106 107 // WriteMPEG4Video writes a MPEG-4 Video frame. 108 func (w *Writer) WriteMPEG4Video( 109 track *Track, 110 pts int64, 111 frame []byte, 112 ) error { 113 randomAccess := bytes.Contains(frame, []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)}) 114 115 return w.writeVideo(track, pts, pts, randomAccess, frame) 116 } 117 118 // WriteMPEG1Video writes a MPEG-1/2 Video frame. 119 func (w *Writer) WriteMPEG1Video( 120 track *Track, 121 pts int64, 122 frame []byte, 123 ) error { 124 randomAccess := bytes.Contains(frame, []byte{0, 0, 1, 0xB8}) 125 126 return w.writeVideo(track, pts, pts, randomAccess, frame) 127 } 128 129 // WriteOpus writes Opus packets. 130 func (w *Writer) WriteOpus( 131 track *Track, 132 pts int64, 133 packets [][]byte, 134 ) error { 135 enc := make([]byte, opusMarshalSize(packets)) 136 n := 0 137 for _, packet := range packets { 138 au := opusAccessUnit{ 139 ControlHeader: opusControlHeader{ 140 PayloadSize: len(packet), 141 }, 142 Packet: packet, 143 } 144 mn, err := au.marshalTo(enc[n:]) 145 if err != nil { 146 return err 147 } 148 n += mn 149 } 150 151 return w.writeAudio(track, pts, enc) 152 } 153 154 // WriteMPEG4Audio writes MPEG-4 Audio access units. 155 func (w *Writer) WriteMPEG4Audio( 156 track *Track, 157 pts int64, 158 aus [][]byte, 159 ) error { 160 aacCodec := track.Codec.(*CodecMPEG4Audio) 161 pkts := make(mpeg4audio.ADTSPackets, len(aus)) 162 163 for i, au := range aus { 164 pkts[i] = &mpeg4audio.ADTSPacket{ 165 Type: aacCodec.Config.Type, 166 SampleRate: aacCodec.SampleRate, 167 ChannelCount: aacCodec.Config.ChannelCount, 168 AU: au, 169 } 170 } 171 172 enc, err := pkts.Marshal() 173 if err != nil { 174 return err 175 } 176 177 return w.writeAudio(track, pts, enc) 178 } 179 180 // WriteMPEG1Audio writes MPEG-1 Audio packets. 181 func (w *Writer) WriteMPEG1Audio( 182 track *Track, 183 pts int64, 184 frames [][]byte, 185 ) error { 186 if !track.mp3Checked { 187 var h mpeg1audio.FrameHeader 188 err := h.Unmarshal(frames[0]) 189 if err != nil { 190 return err 191 } 192 193 if h.MPEG2 { 194 return fmt.Errorf("only MPEG-1 audio is supported") 195 } 196 197 track.mp3Checked = true 198 } 199 200 enc := make([]byte, mpeg1AudioMarshalSize(frames)) 201 n := 0 202 for _, frame := range frames { 203 n += copy(enc[n:], frame) 204 } 205 206 return w.writeAudio(track, pts, enc) 207 } 208 209 // WriteAC3 writes a AC-3 frame. 210 func (w *Writer) WriteAC3( 211 track *Track, 212 pts int64, 213 frame []byte, 214 ) error { 215 return w.writeAudio(track, pts, frame) 216 } 217 218 func (w *Writer) writeVideo( 219 track *Track, 220 pts int64, 221 dts int64, 222 randomAccess bool, 223 data []byte, 224 ) error { 225 if !w.leadingTrackChosen { 226 w.leadingTrackChosen = true 227 track.isLeading = true 228 w.mux.SetPCRPID(track.PID) 229 } 230 231 var af *astits.PacketAdaptationField 232 233 if randomAccess { 234 af = &astits.PacketAdaptationField{} 235 af.RandomAccessIndicator = true 236 } 237 238 if track.isLeading { 239 if randomAccess || w.pcrCounter == 0 { 240 if af == nil { 241 af = &astits.PacketAdaptationField{} 242 } 243 af.HasPCR = true 244 af.PCR = &astits.ClockReference{Base: dts - dtsPCRDiff} 245 w.pcrCounter = 3 246 } 247 w.pcrCounter-- 248 } 249 250 oh := &astits.PESOptionalHeader{ 251 MarkerBits: 2, 252 } 253 254 if dts == pts { 255 oh.PTSDTSIndicator = astits.PTSDTSIndicatorOnlyPTS 256 oh.PTS = &astits.ClockReference{Base: pts} 257 } else { 258 oh.PTSDTSIndicator = astits.PTSDTSIndicatorBothPresent 259 oh.DTS = &astits.ClockReference{Base: dts} 260 oh.PTS = &astits.ClockReference{Base: pts} 261 } 262 263 _, err := w.mux.WriteData(&astits.MuxerData{ 264 PID: track.PID, 265 AdaptationField: af, 266 PES: &astits.PESData{ 267 Header: &astits.PESHeader{ 268 OptionalHeader: oh, 269 StreamID: streamIDVideo, 270 }, 271 Data: data, 272 }, 273 }) 274 return err 275 } 276 277 func (w *Writer) writeAudio(track *Track, pts int64, data []byte) error { 278 if !w.leadingTrackChosen { 279 w.leadingTrackChosen = true 280 track.isLeading = true 281 w.mux.SetPCRPID(track.PID) 282 } 283 284 af := &astits.PacketAdaptationField{ 285 RandomAccessIndicator: true, 286 } 287 288 if track.isLeading { 289 if w.pcrCounter == 0 { 290 af.HasPCR = true 291 af.PCR = &astits.ClockReference{Base: pts - dtsPCRDiff} 292 w.pcrCounter = 3 293 } 294 w.pcrCounter-- 295 } 296 297 _, err := w.mux.WriteData(&astits.MuxerData{ 298 PID: track.PID, 299 AdaptationField: af, 300 PES: &astits.PESData{ 301 Header: &astits.PESHeader{ 302 OptionalHeader: &astits.PESOptionalHeader{ 303 MarkerBits: 2, 304 PTSDTSIndicator: astits.PTSDTSIndicatorOnlyPTS, 305 PTS: &astits.ClockReference{Base: pts}, 306 }, 307 StreamID: streamIDAudio, 308 }, 309 Data: data, 310 }, 311 }) 312 return err 313 }