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  }