github.com/cnotch/ipchub@v1.1.0/av/format/flv/muxer.go (about)

     1  // Copyright (c) 2019,CAOHONGJU All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package flv
     6  
     7  import (
     8  	"fmt"
     9  	"runtime/debug"
    10  	"time"
    11  
    12  	"github.com/cnotch/ipchub/av/codec"
    13  	"github.com/cnotch/ipchub/av/format/amf"
    14  	"github.com/cnotch/queue"
    15  	"github.com/cnotch/xlog"
    16  )
    17  
    18  // Packetizer 封包器
    19  type Packetizer interface {
    20  	PacketizeSequenceHeader() error
    21  	Packetize(frame *codec.Frame) error
    22  }
    23  
    24  type emptyPacketizer struct{}
    25  
    26  func (emptyPacketizer) PacketizeSequenceHeader() error     { return nil }
    27  func (emptyPacketizer) Packetize(frame *codec.Frame) error { return nil }
    28  
    29  // Muxer flv muxer from av.Frame(H264[+AAC])
    30  type Muxer struct {
    31  	videoMeta *codec.VideoMeta
    32  	audioMeta *codec.AudioMeta
    33  	vp        Packetizer
    34  	ap        Packetizer
    35  	typeFlags byte
    36  	recvQueue *queue.SyncQueue
    37  	tagWriter TagWriter
    38  	closed    bool
    39  
    40  	logger *xlog.Logger // 日志对象
    41  }
    42  
    43  // NewMuxer .
    44  func NewMuxer(videoMeta *codec.VideoMeta, audioMeta *codec.AudioMeta, tagWriter TagWriter, logger *xlog.Logger) (*Muxer, error) {
    45  	muxer := &Muxer{
    46  		recvQueue: queue.NewSyncQueue(),
    47  		videoMeta: videoMeta,
    48  		audioMeta: audioMeta,
    49  		vp:        emptyPacketizer{},
    50  		ap:        emptyPacketizer{},
    51  		typeFlags: byte(TypeFlagsVideo),
    52  		tagWriter: tagWriter,
    53  		closed:    false,
    54  		logger:    logger,
    55  	}
    56  	switch videoMeta.Codec {
    57  	case "H264":
    58  		muxer.vp = NewH264Packetizer(videoMeta, tagWriter)
    59  	case "H265":
    60  		muxer.vp = NewH265Packetizer(videoMeta, tagWriter)
    61  	default:
    62  		return nil, fmt.Errorf("flv muxer unsupport video codec type:%s", videoMeta.Codec)
    63  	}
    64  
    65  	if audioMeta.Codec == "AAC" {
    66  		muxer.typeFlags |= TypeFlagsAudio
    67  		muxer.ap = NewAacPacketizer(audioMeta, tagWriter)
    68  	}
    69  
    70  	go muxer.process()
    71  	return muxer, nil
    72  }
    73  
    74  // WriteFrame .
    75  func (muxer *Muxer) WriteFrame(frame *codec.Frame) error {
    76  	muxer.recvQueue.Push(frame)
    77  	return nil
    78  }
    79  
    80  // Close .
    81  func (muxer *Muxer) Close() error {
    82  	if muxer.closed {
    83  		return nil
    84  	}
    85  
    86  	muxer.closed = true
    87  	muxer.recvQueue.Signal()
    88  	return nil
    89  }
    90  
    91  // TypeFlags 返回 flv header 中的 TypeFlags
    92  func (muxer *Muxer) TypeFlags() byte {
    93  	return muxer.typeFlags
    94  }
    95  
    96  func (muxer *Muxer) process() {
    97  	defer func() {
    98  		defer func() { // 避免 handler 再 panic
    99  			recover()
   100  		}()
   101  
   102  		if r := recover(); r != nil {
   103  			muxer.logger.Errorf("flvmuxer routine panic;r = %v \n %s", r, debug.Stack())
   104  		}
   105  
   106  		// 尽早通知GC,回收内存
   107  		muxer.recvQueue.Reset()
   108  	}()
   109  
   110  	var packSequenceHeader bool
   111  
   112  	for !muxer.closed {
   113  		f := muxer.recvQueue.Pop()
   114  		if f == nil {
   115  			if !muxer.closed {
   116  				muxer.logger.Warn("flvmuxer:receive nil frame")
   117  			}
   118  			continue
   119  		}
   120  
   121  		if !packSequenceHeader{
   122  			muxer.muxMetadataTag()
   123  			muxer.vp.PacketizeSequenceHeader()
   124  			muxer.ap.PacketizeSequenceHeader()
   125  			packSequenceHeader = true
   126  		}
   127  		
   128  		frame := f.(*codec.Frame)
   129  
   130  		switch frame.MediaType {
   131  		case codec.MediaTypeVideo:
   132  			if err := muxer.vp.Packetize(frame); err != nil {
   133  				muxer.logger.Errorf("flvmuxer: muxVideoTag error - %s", err.Error())
   134  			}
   135  		case codec.MediaTypeAudio:
   136  			if err := muxer.ap.Packetize(frame); err != nil {
   137  				muxer.logger.Errorf("flvmuxer: muxAudioTag error - %s", err.Error())
   138  			}
   139  		default:
   140  		}
   141  	}
   142  }
   143  
   144  func (muxer *Muxer) muxMetadataTag() error {
   145  	properties := make(amf.EcmaArray, 0, 12)
   146  
   147  	properties = append(properties,
   148  		amf.ObjectProperty{
   149  			Name:  "creator",
   150  			Value: "ipchub stream media server"})
   151  	properties = append(properties,
   152  		amf.ObjectProperty{
   153  			Name:  MetaDataCreationDate,
   154  			Value: time.Now().Format(time.RFC3339)})
   155  
   156  	if muxer.typeFlags&TypeFlagsAudio > 0 {
   157  		properties = append(properties,
   158  			amf.ObjectProperty{
   159  				Name:  MetaDataAudioCodecID,
   160  				Value: SoundFormatAAC})
   161  		properties = append(properties,
   162  			amf.ObjectProperty{
   163  				Name:  MetaDataAudioDateRate,
   164  				Value: muxer.audioMeta.DataRate})
   165  		properties = append(properties,
   166  			amf.ObjectProperty{
   167  				Name:  MetaDataAudioSampleRate,
   168  				Value: muxer.audioMeta.SampleRate})
   169  		properties = append(properties,
   170  			amf.ObjectProperty{
   171  				Name:  MetaDataAudioSampleSize,
   172  				Value: muxer.audioMeta.SampleSize})
   173  		properties = append(properties,
   174  			amf.ObjectProperty{
   175  				Name:  MetaDataStereo,
   176  				Value: muxer.audioMeta.Channels > 1})
   177  	}
   178  
   179  	vcodecID := CodecIDAVC
   180  	if muxer.videoMeta.Codec == "H265" {
   181  		vcodecID = CodecIDHEVC
   182  	}
   183  
   184  	properties = append(properties,
   185  		amf.ObjectProperty{
   186  			Name:  MetaDataVideoCodecID,
   187  			Value: vcodecID})
   188  	properties = append(properties,
   189  		amf.ObjectProperty{
   190  			Name:  MetaDataVideoDataRate,
   191  			Value: muxer.videoMeta.DataRate})
   192  	properties = append(properties,
   193  		amf.ObjectProperty{
   194  			Name:  MetaDataFrameRate,
   195  			Value: muxer.videoMeta.FrameRate})
   196  	properties = append(properties,
   197  		amf.ObjectProperty{
   198  			Name:  MetaDataWidth,
   199  			Value: muxer.videoMeta.Width})
   200  	properties = append(properties,
   201  		amf.ObjectProperty{
   202  			Name:  MetaDataHeight,
   203  			Value: muxer.videoMeta.Height})
   204  
   205  	scriptData := ScriptData{
   206  		Name:  ScriptOnMetaData,
   207  		Value: properties,
   208  	}
   209  	data, _ := scriptData.Marshal()
   210  
   211  	tag := &Tag{
   212  		TagType:   TagTypeAmf0Data,
   213  		DataSize:  uint32(len(data)),
   214  		Timestamp: 0,
   215  		StreamID:  0,
   216  		Data:      data,
   217  	}
   218  
   219  	return muxer.tagWriter.WriteFlvTag(tag)
   220  }