first commit
This commit is contained in:
		
							
								
								
									
										316
									
								
								av/av.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										316
									
								
								av/av.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,316 @@ | ||||
|  | ||||
| // Package av defines basic interfaces and data structures of container demux/mux and audio encode/decode. | ||||
| package av | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // Audio sample format. | ||||
| type SampleFormat uint8 | ||||
|  | ||||
| const ( | ||||
| 	U8 = SampleFormat(iota + 1) // 8-bit unsigned integer | ||||
| 	S16 // signed 16-bit integer | ||||
| 	S32 // signed 32-bit integer | ||||
| 	FLT // 32-bit float | ||||
| 	DBL // 64-bit float | ||||
| 	U8P // 8-bit unsigned integer in planar | ||||
| 	S16P // signed 16-bit integer in planar | ||||
| 	S32P // signed 32-bit integer in planar | ||||
| 	FLTP // 32-bit float in planar | ||||
| 	DBLP // 64-bit float in planar | ||||
| 	U32 // unsigned 32-bit integer | ||||
| ) | ||||
|  | ||||
| func (self SampleFormat) BytesPerSample() int { | ||||
| 	switch self { | ||||
| 	case U8, U8P: | ||||
| 		return 1 | ||||
| 	case S16, S16P: | ||||
| 		return 2 | ||||
| 	case FLT, FLTP, S32, S32P, U32: | ||||
| 		return 4 | ||||
| 	case DBL, DBLP: | ||||
| 		return 8 | ||||
| 	default: | ||||
| 		return 0 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self SampleFormat) String() string { | ||||
| 	switch self { | ||||
| 	case U8: | ||||
| 		return "U8" | ||||
| 	case S16: | ||||
| 		return "S16" | ||||
| 	case S32: | ||||
| 		return "S32" | ||||
| 	case FLT: | ||||
| 		return "FLT" | ||||
| 	case DBL: | ||||
| 		return "DBL" | ||||
| 	case U8P: | ||||
| 		return "U8P" | ||||
| 	case S16P: | ||||
| 		return "S16P" | ||||
| 	case FLTP: | ||||
| 		return "FLTP" | ||||
| 	case DBLP: | ||||
| 		return "DBLP" | ||||
| 	case U32: | ||||
| 		return "U32" | ||||
| 	default: | ||||
| 		return "?" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Check if this sample format is in planar. | ||||
| func (self SampleFormat) IsPlanar() bool { | ||||
| 	switch self { | ||||
| 	case S16P, S32P, FLTP, DBLP: | ||||
| 		return true | ||||
| 	default: | ||||
| 		return false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Audio channel layout. | ||||
| type ChannelLayout uint16 | ||||
|  | ||||
| func (self ChannelLayout) String() string { | ||||
| 	return fmt.Sprintf("%dch", self.Count()) | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	CH_FRONT_CENTER = ChannelLayout(1 << iota) | ||||
| 	CH_FRONT_LEFT | ||||
| 	CH_FRONT_RIGHT | ||||
| 	CH_BACK_CENTER | ||||
| 	CH_BACK_LEFT | ||||
| 	CH_BACK_RIGHT | ||||
| 	CH_SIDE_LEFT | ||||
| 	CH_SIDE_RIGHT | ||||
| 	CH_LOW_FREQ | ||||
| 	CH_NR | ||||
|  | ||||
| 	CH_MONO     = ChannelLayout(CH_FRONT_CENTER) | ||||
| 	CH_STEREO   = ChannelLayout(CH_FRONT_LEFT | CH_FRONT_RIGHT) | ||||
| 	CH_2_1      = ChannelLayout(CH_STEREO | CH_BACK_CENTER) | ||||
| 	CH_2POINT1  = ChannelLayout(CH_STEREO | CH_LOW_FREQ) | ||||
| 	CH_SURROUND = ChannelLayout(CH_STEREO | CH_FRONT_CENTER) | ||||
| 	CH_3POINT1  = ChannelLayout(CH_SURROUND | CH_LOW_FREQ) | ||||
| 	// TODO: add all channel_layout in ffmpeg | ||||
| ) | ||||
|  | ||||
| func (self ChannelLayout) Count() (n int) { | ||||
| 	for self != 0 { | ||||
| 		n++ | ||||
| 		self = (self - 1) & self | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Video/Audio codec type. can be H264/AAC/SPEEX/... | ||||
| type CodecType uint32 | ||||
|  | ||||
| var ( | ||||
| 	H264 = MakeVideoCodecType(avCodecTypeMagic + 1) | ||||
| 	AAC       = MakeAudioCodecType(avCodecTypeMagic + 1) | ||||
| 	PCM_MULAW = MakeAudioCodecType(avCodecTypeMagic + 2) | ||||
| 	PCM_ALAW  = MakeAudioCodecType(avCodecTypeMagic + 3) | ||||
| 	SPEEX = MakeAudioCodecType(avCodecTypeMagic + 4) | ||||
| 	NELLYMOSER = MakeAudioCodecType(avCodecTypeMagic + 5) | ||||
| ) | ||||
|  | ||||
| const codecTypeAudioBit = 0x1 | ||||
| const codecTypeOtherBits = 1 | ||||
|  | ||||
| func (self CodecType) String() string { | ||||
| 	switch self { | ||||
| 	case H264: | ||||
| 		return "H264" | ||||
| 	case AAC: | ||||
| 		return "AAC" | ||||
| 	case PCM_MULAW: | ||||
| 		return "PCM_MULAW" | ||||
| 	case PCM_ALAW: | ||||
| 		return "PCM_ALAW" | ||||
| 	case SPEEX: | ||||
| 		return "SPEEX" | ||||
| 	case NELLYMOSER: | ||||
| 		return "NELLYMOSER" | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (self CodecType) IsAudio() bool { | ||||
| 	return self&codecTypeAudioBit != 0 | ||||
| } | ||||
|  | ||||
| func (self CodecType) IsVideo() bool { | ||||
| 	return self&codecTypeAudioBit == 0 | ||||
| } | ||||
|  | ||||
| // Make a new audio codec type. | ||||
| func MakeAudioCodecType(base uint32) (c CodecType) { | ||||
| 	c = CodecType(base)<<codecTypeOtherBits | CodecType(codecTypeAudioBit) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Make a new video codec type. | ||||
| func MakeVideoCodecType(base uint32) (c CodecType) { | ||||
| 	c = CodecType(base) << codecTypeOtherBits | ||||
| 	return | ||||
| } | ||||
|  | ||||
| const avCodecTypeMagic = 233333 | ||||
|  | ||||
| // CodecData is some important bytes for initializing audio/video decoder, | ||||
| // can be converted to VideoCodecData or AudioCodecData using: | ||||
| // | ||||
| //     codecdata.(AudioCodecData) or codecdata.(VideoCodecData) | ||||
| //  | ||||
| // for H264, CodecData is AVCDecoderConfigure bytes, includes SPS/PPS. | ||||
| type CodecData interface { | ||||
| 	Type() CodecType // Video/Audio codec type | ||||
| } | ||||
|  | ||||
| type VideoCodecData interface { | ||||
| 	CodecData | ||||
| 	Width() int // Video width | ||||
| 	Height() int // Video height | ||||
| } | ||||
|  | ||||
| type AudioCodecData interface { | ||||
| 	CodecData | ||||
| 	SampleFormat() SampleFormat // audio sample format | ||||
| 	SampleRate() int // audio sample rate | ||||
| 	ChannelLayout() ChannelLayout // audio channel layout | ||||
| 	PacketDuration([]byte) (time.Duration, error) // get audio compressed packet duration | ||||
| } | ||||
|  | ||||
| type PacketWriter interface { | ||||
| 	WritePacket(Packet) error | ||||
| } | ||||
|  | ||||
| type PacketReader interface { | ||||
| 	ReadPacket() (Packet,error) | ||||
| } | ||||
|  | ||||
| // Muxer describes the steps of writing compressed audio/video packets into container formats like MP4/FLV/MPEG-TS. | ||||
| //  | ||||
| // Container formats, rtmp.Conn, and transcode.Muxer implements Muxer interface. | ||||
| type Muxer interface { | ||||
| 	WriteHeader([]CodecData) error // write the file header | ||||
| 	PacketWriter // write compressed audio/video packets | ||||
| 	WriteTrailer() error // finish writing file, this func can be called only once | ||||
| } | ||||
|  | ||||
| // Muxer with Close() method | ||||
| type MuxCloser interface { | ||||
| 	Muxer | ||||
| 	Close() error | ||||
| } | ||||
|  | ||||
| // Demuxer can read compressed audio/video packets from container formats like MP4/FLV/MPEG-TS. | ||||
| type Demuxer interface { | ||||
| 	PacketReader // read compressed audio/video packets | ||||
| 	Streams() ([]CodecData, error) // reads the file header, contains video/audio meta infomations | ||||
| } | ||||
|  | ||||
| // Demuxer with Close() method | ||||
| type DemuxCloser interface { | ||||
| 	Demuxer | ||||
| 	Close() error | ||||
| } | ||||
|  | ||||
| // Packet stores compressed audio/video data. | ||||
| type Packet struct { | ||||
| 	IsKeyFrame      bool // video packet is key frame | ||||
| 	Idx             int8 // stream index in container format | ||||
| 	CompositionTime time.Duration // packet presentation time minus decode time for H264 B-Frame | ||||
| 	Time time.Duration // packet decode time | ||||
| 	Data            []byte // packet data | ||||
| } | ||||
|  | ||||
| // Raw audio frame. | ||||
| type AudioFrame struct { | ||||
| 	SampleFormat  SampleFormat // audio sample format, e.g: S16,FLTP,... | ||||
| 	ChannelLayout ChannelLayout // audio channel layout, e.g: CH_MONO,CH_STEREO,... | ||||
| 	SampleCount   int // sample count in this frame | ||||
| 	SampleRate    int // sample rate | ||||
| 	Data          [][]byte // data array for planar format len(Data) > 1 | ||||
| } | ||||
|  | ||||
| func (self AudioFrame) Duration() time.Duration { | ||||
| 	return time.Second * time.Duration(self.SampleCount) / time.Duration(self.SampleRate) | ||||
| } | ||||
|  | ||||
| // Check this audio frame has same format as other audio frame. | ||||
| func (self AudioFrame) HasSameFormat(other AudioFrame) bool { | ||||
| 	if self.SampleRate != other.SampleRate { | ||||
| 		return false | ||||
| 	} | ||||
| 	if self.ChannelLayout != other.ChannelLayout { | ||||
| 		return false | ||||
| 	} | ||||
| 	if self.SampleFormat != other.SampleFormat { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // Split sample audio sample from this frame. | ||||
| func (self AudioFrame) Slice(start int, end int) (out AudioFrame) { | ||||
| 	if start > end { | ||||
| 		panic(fmt.Sprintf("av: AudioFrame split failed start=%d end=%d invalid", start, end)) | ||||
| 	} | ||||
| 	out = self | ||||
| 	out.Data = append([][]byte(nil), out.Data...) | ||||
| 	out.SampleCount = end - start | ||||
| 	size := self.SampleFormat.BytesPerSample() | ||||
| 	for i := range out.Data { | ||||
| 		out.Data[i] = out.Data[i][start*size : end*size] | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Concat two audio frames. | ||||
| func (self AudioFrame) Concat(in AudioFrame) (out AudioFrame) { | ||||
| 	out = self | ||||
| 	out.Data = append([][]byte(nil), out.Data...) | ||||
| 	out.SampleCount += in.SampleCount | ||||
| 	for i := range out.Data { | ||||
| 		out.Data[i] = append(out.Data[i], in.Data[i]...) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // AudioEncoder can encode raw audio frame into compressed audio packets. | ||||
| // cgo/ffmpeg inplements AudioEncoder, using ffmpeg.NewAudioEncoder to create it. | ||||
| type AudioEncoder interface { | ||||
| 	CodecData() (AudioCodecData, error) // encoder's codec data can put into container | ||||
| 	Encode(AudioFrame) ([][]byte, error) // encode raw audio frame into compressed pakcet(s) | ||||
| 	Close() // close encoder, free cgo contexts | ||||
| 	SetSampleRate(int) (error) // set encoder sample rate | ||||
| 	SetChannelLayout(ChannelLayout) (error) // set encoder channel layout | ||||
| 	SetSampleFormat(SampleFormat) (error) // set encoder sample format | ||||
| 	SetBitrate(int) (error) // set encoder bitrate | ||||
| 	SetOption(string,interface{}) (error) // encoder setopt, in ffmpeg is av_opt_set_dict() | ||||
| 	GetOption(string,interface{}) (error) // encoder getopt | ||||
| } | ||||
|  | ||||
| // AudioDecoder can decode compressed audio packets into raw audio frame. | ||||
| // use ffmpeg.NewAudioDecoder to create it. | ||||
| type AudioDecoder interface { | ||||
| 	Decode([]byte) (bool, AudioFrame, error) // decode one compressed audio packet | ||||
| 	Close() // close decode, free cgo contexts | ||||
| } | ||||
|  | ||||
| // AudioResampler can convert raw audio frames in different sample rate/format/channel layout. | ||||
| type AudioResampler interface { | ||||
| 	Resample(AudioFrame) (AudioFrame, error) // convert raw audio frames | ||||
| } | ||||
|  | ||||
							
								
								
									
										255
									
								
								av/avconv/avconv.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								av/avconv/avconv.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,255 @@ | ||||
| package avconv | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/deepch/vdk/av" | ||||
| 	"github.com/deepch/vdk/av/avutil" | ||||
| 	"github.com/deepch/vdk/av/pktque" | ||||
| 	"github.com/deepch/vdk/av/transcode" | ||||
| ) | ||||
|  | ||||
| var Debug bool | ||||
|  | ||||
| type Option struct { | ||||
| 	Transcode bool | ||||
| 	Args      []string | ||||
| } | ||||
|  | ||||
| type Options struct { | ||||
| 	OutputCodecTypes []av.CodecType | ||||
| } | ||||
|  | ||||
| type Demuxer struct { | ||||
| 	transdemux *transcode.Demuxer | ||||
| 	streams    []av.CodecData | ||||
| 	Options | ||||
| 	Demuxer av.Demuxer | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) Close() (err error) { | ||||
| 	if self.transdemux != nil { | ||||
| 		return self.transdemux.Close() | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) Streams() (streams []av.CodecData, err error) { | ||||
| 	if err = self.prepare(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	streams = self.streams | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) { | ||||
| 	if err = self.prepare(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return self.transdemux.ReadPacket() | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) prepare() (err error) { | ||||
| 	if self.transdemux != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 		var streams []av.CodecData | ||||
| 		if streams, err = self.Demuxer.Streams(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	*/ | ||||
|  | ||||
| 	supports := self.Options.OutputCodecTypes | ||||
|  | ||||
| 	transopts := transcode.Options{} | ||||
| 	transopts.FindAudioDecoderEncoder = func(codec av.AudioCodecData, i int) (ok bool, dec av.AudioDecoder, enc av.AudioEncoder, err error) { | ||||
| 		if len(supports) == 0 { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		support := false | ||||
| 		for _, typ := range supports { | ||||
| 			if typ == codec.Type() { | ||||
| 				support = true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if support { | ||||
| 			return | ||||
| 		} | ||||
| 		ok = true | ||||
|  | ||||
| 		var enctype av.CodecType | ||||
| 		for _, typ := range supports { | ||||
| 			if typ.IsAudio() { | ||||
| 				if enc, _ = avutil.DefaultHandlers.NewAudioEncoder(typ); enc != nil { | ||||
| 					enctype = typ | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		if enc == nil { | ||||
| 			err = fmt.Errorf("avconv: convert %s->%s failed", codec.Type(), enctype) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// TODO: support per stream option | ||||
| 		// enc.SetSampleRate ... | ||||
|  | ||||
| 		if dec, err = avutil.DefaultHandlers.NewAudioDecoder(codec); err != nil { | ||||
| 			err = fmt.Errorf("avconv: decode %s failed", codec.Type()) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	self.transdemux = &transcode.Demuxer{ | ||||
| 		Options: transopts, | ||||
| 		Demuxer: self.Demuxer, | ||||
| 	} | ||||
| 	if self.streams, err = self.transdemux.Streams(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func ConvertCmdline(args []string) (err error) { | ||||
| 	output := "" | ||||
| 	input := "" | ||||
| 	flagi := false | ||||
| 	flagv := false | ||||
| 	flagt := false | ||||
| 	flagre := false | ||||
| 	duration := time.Duration(0) | ||||
| 	options := Options{} | ||||
|  | ||||
| 	for _, arg := range args { | ||||
| 		switch arg { | ||||
| 		case "-i": | ||||
| 			flagi = true | ||||
|  | ||||
| 		case "-v": | ||||
| 			flagv = true | ||||
|  | ||||
| 		case "-t": | ||||
| 			flagt = true | ||||
|  | ||||
| 		case "-re": | ||||
| 			flagre = true | ||||
|  | ||||
| 		default: | ||||
| 			switch { | ||||
| 			case flagi: | ||||
| 				flagi = false | ||||
| 				input = arg | ||||
|  | ||||
| 			case flagt: | ||||
| 				flagt = false | ||||
| 				var f float64 | ||||
| 				fmt.Sscanf(arg, "%f", &f) | ||||
| 				duration = time.Duration(f * float64(time.Second)) | ||||
|  | ||||
| 			default: | ||||
| 				output = arg | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if input == "" { | ||||
| 		err = fmt.Errorf("avconv: input file not specified") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if output == "" { | ||||
| 		err = fmt.Errorf("avconv: output file not specified") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var demuxer av.DemuxCloser | ||||
| 	var muxer av.MuxCloser | ||||
|  | ||||
| 	if demuxer, err = avutil.Open(input); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer demuxer.Close() | ||||
|  | ||||
| 	var handler avutil.RegisterHandler | ||||
| 	if handler, muxer, err = avutil.DefaultHandlers.FindCreate(output); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer muxer.Close() | ||||
|  | ||||
| 	options.OutputCodecTypes = handler.CodecTypes | ||||
|  | ||||
| 	convdemux := &Demuxer{ | ||||
| 		Options: options, | ||||
| 		Demuxer: demuxer, | ||||
| 	} | ||||
| 	defer convdemux.Close() | ||||
|  | ||||
| 	var streams []av.CodecData | ||||
| 	if streams, err = demuxer.Streams(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var convstreams []av.CodecData | ||||
| 	if convstreams, err = convdemux.Streams(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if flagv { | ||||
| 		for _, stream := range streams { | ||||
| 			fmt.Print(stream.Type(), " ") | ||||
| 		} | ||||
| 		fmt.Print("-> ") | ||||
| 		for _, stream := range convstreams { | ||||
| 			fmt.Print(stream.Type(), " ") | ||||
| 		} | ||||
| 		fmt.Println() | ||||
| 	} | ||||
|  | ||||
| 	if err = muxer.WriteHeader(convstreams); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	filters := pktque.Filters{} | ||||
| 	if flagre { | ||||
| 		filters = append(filters, &pktque.Walltime{}) | ||||
| 	} | ||||
| 	filterdemux := &pktque.FilterDemuxer{ | ||||
| 		Demuxer: convdemux, | ||||
| 		Filter:  filters, | ||||
| 	} | ||||
|  | ||||
| 	for { | ||||
| 		var pkt av.Packet | ||||
| 		if pkt, err = filterdemux.ReadPacket(); err != nil { | ||||
| 			if err == io.EOF { | ||||
| 				err = nil | ||||
| 				break | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
| 		if flagv { | ||||
| 			fmt.Println(pkt.Idx, pkt.Time, len(pkt.Data), pkt.IsKeyFrame) | ||||
| 		} | ||||
| 		if duration != 0 && pkt.Time > duration { | ||||
| 			break | ||||
| 		} | ||||
| 		if err = muxer.WritePacket(pkt); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err = muxer.WriteTrailer(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										312
									
								
								av/avutil/avutil.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								av/avutil/avutil.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,312 @@ | ||||
| package avutil | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/deepch/vdk/av" | ||||
| ) | ||||
|  | ||||
| type HandlerDemuxer struct { | ||||
| 	av.Demuxer | ||||
| 	r io.ReadCloser | ||||
| } | ||||
|  | ||||
| func (self *HandlerDemuxer) Close() error { | ||||
| 	return self.r.Close() | ||||
| } | ||||
|  | ||||
| type HandlerMuxer struct { | ||||
| 	av.Muxer | ||||
| 	w     io.WriteCloser | ||||
| 	stage int | ||||
| } | ||||
|  | ||||
| func (self *HandlerMuxer) WriteHeader(streams []av.CodecData) (err error) { | ||||
| 	if self.stage == 0 { | ||||
| 		if err = self.Muxer.WriteHeader(streams); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		self.stage++ | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *HandlerMuxer) WriteTrailer() (err error) { | ||||
| 	if self.stage == 1 { | ||||
| 		self.stage++ | ||||
| 		if err = self.Muxer.WriteTrailer(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *HandlerMuxer) Close() (err error) { | ||||
| 	if err = self.WriteTrailer(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return self.w.Close() | ||||
| } | ||||
|  | ||||
| type RegisterHandler struct { | ||||
| 	Ext           string | ||||
| 	ReaderDemuxer func(io.Reader) av.Demuxer | ||||
| 	WriterMuxer   func(io.Writer) av.Muxer | ||||
| 	UrlMuxer      func(string) (bool, av.MuxCloser, error) | ||||
| 	UrlDemuxer    func(string) (bool, av.DemuxCloser, error) | ||||
| 	UrlReader     func(string) (bool, io.ReadCloser, error) | ||||
| 	Probe         func([]byte) bool | ||||
| 	AudioEncoder  func(av.CodecType) (av.AudioEncoder, error) | ||||
| 	AudioDecoder  func(av.AudioCodecData) (av.AudioDecoder, error) | ||||
| 	ServerDemuxer func(string) (bool, av.DemuxCloser, error) | ||||
| 	ServerMuxer   func(string) (bool, av.MuxCloser, error) | ||||
| 	CodecTypes    []av.CodecType | ||||
| } | ||||
|  | ||||
| type Handlers struct { | ||||
| 	handlers []RegisterHandler | ||||
| } | ||||
|  | ||||
| func (self *Handlers) Add(fn func(*RegisterHandler)) { | ||||
| 	handler := &RegisterHandler{} | ||||
| 	fn(handler) | ||||
| 	self.handlers = append(self.handlers, *handler) | ||||
| } | ||||
|  | ||||
| func (self *Handlers) openUrl(u *url.URL, uri string) (r io.ReadCloser, err error) { | ||||
| 	if u != nil && u.Scheme != "" { | ||||
| 		for _, handler := range self.handlers { | ||||
| 			if handler.UrlReader != nil { | ||||
| 				var ok bool | ||||
| 				if ok, r, err = handler.UrlReader(uri); ok { | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		err = fmt.Errorf("avutil: openUrl %s failed", uri) | ||||
| 	} else { | ||||
| 		r, err = os.Open(uri) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Handlers) createUrl(u *url.URL, uri string) (w io.WriteCloser, err error) { | ||||
| 	w, err = os.Create(uri) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Handlers) NewAudioEncoder(typ av.CodecType) (enc av.AudioEncoder, err error) { | ||||
| 	for _, handler := range self.handlers { | ||||
| 		if handler.AudioEncoder != nil { | ||||
| 			if enc, _ = handler.AudioEncoder(typ); enc != nil { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	err = fmt.Errorf("avutil: encoder", typ, "not found") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Handlers) NewAudioDecoder(codec av.AudioCodecData) (dec av.AudioDecoder, err error) { | ||||
| 	for _, handler := range self.handlers { | ||||
| 		if handler.AudioDecoder != nil { | ||||
| 			if dec, _ = handler.AudioDecoder(codec); dec != nil { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	err = fmt.Errorf("avutil: decoder", codec.Type(), "not found") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Handlers) Open(uri string) (demuxer av.DemuxCloser, err error) { | ||||
| 	listen := false | ||||
| 	if strings.HasPrefix(uri, "listen:") { | ||||
| 		uri = uri[len("listen:"):] | ||||
| 		listen = true | ||||
| 	} | ||||
|  | ||||
| 	for _, handler := range self.handlers { | ||||
| 		if listen { | ||||
| 			if handler.ServerDemuxer != nil { | ||||
| 				var ok bool | ||||
| 				if ok, demuxer, err = handler.ServerDemuxer(uri); ok { | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			if handler.UrlDemuxer != nil { | ||||
| 				var ok bool | ||||
| 				if ok, demuxer, err = handler.UrlDemuxer(uri); ok { | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var r io.ReadCloser | ||||
| 	var ext string | ||||
| 	var u *url.URL | ||||
| 	if u, _ = url.Parse(uri); u != nil && u.Scheme != "" { | ||||
| 		ext = path.Ext(u.Path) | ||||
| 	} else { | ||||
| 		ext = path.Ext(uri) | ||||
| 	} | ||||
|  | ||||
| 	if ext != "" { | ||||
| 		for _, handler := range self.handlers { | ||||
| 			if handler.Ext == ext { | ||||
| 				if handler.ReaderDemuxer != nil { | ||||
| 					if r, err = self.openUrl(u, uri); err != nil { | ||||
| 						return | ||||
| 					} | ||||
| 					demuxer = &HandlerDemuxer{ | ||||
| 						Demuxer: handler.ReaderDemuxer(r), | ||||
| 						r:       r, | ||||
| 					} | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var probebuf [1024]byte | ||||
| 	if r, err = self.openUrl(u, uri); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if _, err = io.ReadFull(r, probebuf[:]); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, handler := range self.handlers { | ||||
| 		if handler.Probe != nil && handler.Probe(probebuf[:]) && handler.ReaderDemuxer != nil { | ||||
| 			var _r io.Reader | ||||
| 			if rs, ok := r.(io.ReadSeeker); ok { | ||||
| 				if _, err = rs.Seek(0, 0); err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 				_r = rs | ||||
| 			} else { | ||||
| 				_r = io.MultiReader(bytes.NewReader(probebuf[:]), r) | ||||
| 			} | ||||
| 			demuxer = &HandlerDemuxer{ | ||||
| 				Demuxer: handler.ReaderDemuxer(_r), | ||||
| 				r:       r, | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	r.Close() | ||||
| 	err = fmt.Errorf("avutil: open %s failed", uri) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Handlers) Create(uri string) (muxer av.MuxCloser, err error) { | ||||
| 	_, muxer, err = self.FindCreate(uri) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Handlers) FindCreate(uri string) (handler RegisterHandler, muxer av.MuxCloser, err error) { | ||||
| 	listen := false | ||||
| 	if strings.HasPrefix(uri, "listen:") { | ||||
| 		uri = uri[len("listen:"):] | ||||
| 		listen = true | ||||
| 	} | ||||
|  | ||||
| 	for _, handler = range self.handlers { | ||||
| 		if listen { | ||||
| 			if handler.ServerMuxer != nil { | ||||
| 				var ok bool | ||||
| 				if ok, muxer, err = handler.ServerMuxer(uri); ok { | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			if handler.UrlMuxer != nil { | ||||
| 				var ok bool | ||||
| 				if ok, muxer, err = handler.UrlMuxer(uri); ok { | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var ext string | ||||
| 	var u *url.URL | ||||
| 	if u, _ = url.Parse(uri); u != nil && u.Scheme != "" { | ||||
| 		ext = path.Ext(u.Path) | ||||
| 	} else { | ||||
| 		ext = path.Ext(uri) | ||||
| 	} | ||||
|  | ||||
| 	if ext != "" { | ||||
| 		for _, handler = range self.handlers { | ||||
| 			if handler.Ext == ext && handler.WriterMuxer != nil { | ||||
| 				var w io.WriteCloser | ||||
| 				if w, err = self.createUrl(u, uri); err != nil { | ||||
| 					return | ||||
| 				} | ||||
| 				muxer = &HandlerMuxer{ | ||||
| 					Muxer: handler.WriterMuxer(w), | ||||
| 					w:     w, | ||||
| 				} | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err = fmt.Errorf("avutil: create muxer %s failed", uri) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| var DefaultHandlers = &Handlers{} | ||||
|  | ||||
| func Open(url string) (demuxer av.DemuxCloser, err error) { | ||||
| 	return DefaultHandlers.Open(url) | ||||
| } | ||||
|  | ||||
| func Create(url string) (muxer av.MuxCloser, err error) { | ||||
| 	return DefaultHandlers.Create(url) | ||||
| } | ||||
|  | ||||
| func CopyPackets(dst av.PacketWriter, src av.PacketReader) (err error) { | ||||
| 	for { | ||||
| 		var pkt av.Packet | ||||
| 		if pkt, err = src.ReadPacket(); err != nil { | ||||
| 			if err == io.EOF { | ||||
| 				break | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
| 		if err = dst.WritePacket(pkt); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func CopyFile(dst av.Muxer, src av.Demuxer) (err error) { | ||||
| 	var streams []av.CodecData | ||||
| 	if streams, err = src.Streams(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = dst.WriteHeader(streams); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = CopyPackets(dst, src); err != nil { | ||||
| 		if err != io.EOF { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if err = dst.WriteTrailer(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										73
									
								
								av/pktque/buf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								av/pktque/buf.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| package pktque | ||||
|  | ||||
| import ( | ||||
| 	"github.com/deepch/vdk/av" | ||||
| ) | ||||
|  | ||||
| type Buf struct { | ||||
| 	Head, Tail BufPos | ||||
| 	pkts       []av.Packet | ||||
| 	Size       int | ||||
| 	Count      int | ||||
| } | ||||
|  | ||||
| func NewBuf() *Buf { | ||||
| 	return &Buf{ | ||||
| 		pkts: make([]av.Packet, 64), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *Buf) Pop() av.Packet { | ||||
| 	if self.Count == 0 { | ||||
| 		panic("pktque.Buf: Pop() when count == 0") | ||||
| 	} | ||||
|  | ||||
| 	i := int(self.Head) & (len(self.pkts) - 1) | ||||
| 	pkt := self.pkts[i] | ||||
| 	self.pkts[i] = av.Packet{} | ||||
| 	self.Size -= len(pkt.Data) | ||||
| 	self.Head++ | ||||
| 	self.Count-- | ||||
|  | ||||
| 	return pkt | ||||
| } | ||||
|  | ||||
| func (self *Buf) grow() { | ||||
| 	newpkts := make([]av.Packet, len(self.pkts)*2) | ||||
| 	for i := self.Head; i.LT(self.Tail); i++ { | ||||
| 		newpkts[int(i)&(len(newpkts)-1)] = self.pkts[int(i)&(len(self.pkts)-1)] | ||||
| 	} | ||||
| 	self.pkts = newpkts | ||||
| } | ||||
|  | ||||
| func (self *Buf) Push(pkt av.Packet) { | ||||
| 	if self.Count == len(self.pkts) { | ||||
| 		self.grow() | ||||
| 	} | ||||
| 	self.pkts[int(self.Tail)&(len(self.pkts)-1)] = pkt | ||||
| 	self.Tail++ | ||||
| 	self.Count++ | ||||
| 	self.Size += len(pkt.Data) | ||||
| } | ||||
|  | ||||
| func (self *Buf) Get(pos BufPos) av.Packet { | ||||
| 	return self.pkts[int(pos)&(len(self.pkts)-1)] | ||||
| } | ||||
|  | ||||
| func (self *Buf) IsValidPos(pos BufPos) bool { | ||||
| 	return pos.GE(self.Head) && pos.LT(self.Tail) | ||||
| } | ||||
|  | ||||
| type BufPos int | ||||
|  | ||||
| func (self BufPos) LT(pos BufPos) bool { | ||||
| 	return self-pos < 0 | ||||
| } | ||||
|  | ||||
| func (self BufPos) GE(pos BufPos) bool { | ||||
| 	return self-pos >= 0 | ||||
| } | ||||
|  | ||||
| func (self BufPos) GT(pos BufPos) bool { | ||||
| 	return self-pos > 0 | ||||
| } | ||||
							
								
								
									
										190
									
								
								av/pktque/filters.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								av/pktque/filters.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| // Package pktque provides packet Filter interface and structures used by other components. | ||||
| package pktque | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/deepch/vdk/av" | ||||
| ) | ||||
|  | ||||
| type Filter interface { | ||||
| 	// Change packet time or drop packet | ||||
| 	ModifyPacket(pkt *av.Packet, streams []av.CodecData, videoidx int, audioidx int) (drop bool, err error) | ||||
| } | ||||
|  | ||||
| // Combine multiple Filters into one, ModifyPacket will be called in order. | ||||
| type Filters []Filter | ||||
|  | ||||
| func (self Filters) ModifyPacket(pkt *av.Packet, streams []av.CodecData, videoidx int, audioidx int) (drop bool, err error) { | ||||
| 	for _, filter := range self { | ||||
| 		if drop, err = filter.ModifyPacket(pkt, streams, videoidx, audioidx); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		if drop { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Wrap origin Demuxer and Filter into a new Demuxer, when read this Demuxer filters will be called. | ||||
| type FilterDemuxer struct { | ||||
| 	av.Demuxer | ||||
| 	Filter   Filter | ||||
| 	streams  []av.CodecData | ||||
| 	videoidx int | ||||
| 	audioidx int | ||||
| } | ||||
|  | ||||
| func (self FilterDemuxer) ReadPacket() (pkt av.Packet, err error) { | ||||
| 	if self.streams == nil { | ||||
| 		if self.streams, err = self.Demuxer.Streams(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		for i, stream := range self.streams { | ||||
| 			if stream.Type().IsVideo() { | ||||
| 				self.videoidx = i | ||||
| 			} else if stream.Type().IsAudio() { | ||||
| 				self.audioidx = i | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for { | ||||
| 		if pkt, err = self.Demuxer.ReadPacket(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		var drop bool | ||||
| 		if drop, err = self.Filter.ModifyPacket(&pkt, self.streams, self.videoidx, self.audioidx); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		if !drop { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Drop packets until first video key frame arrived. | ||||
| type WaitKeyFrame struct { | ||||
| 	ok bool | ||||
| } | ||||
|  | ||||
| func (self *WaitKeyFrame) ModifyPacket(pkt *av.Packet, streams []av.CodecData, videoidx int, audioidx int) (drop bool, err error) { | ||||
| 	if !self.ok && pkt.Idx == int8(videoidx) && pkt.IsKeyFrame { | ||||
| 		self.ok = true | ||||
| 	} | ||||
| 	drop = !self.ok | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Fix incorrect packet timestamps. | ||||
| type FixTime struct { | ||||
| 	zerobase      time.Duration | ||||
| 	incrbase      time.Duration | ||||
| 	lasttime      time.Duration | ||||
| 	StartFromZero bool // make timestamp start from zero | ||||
| 	MakeIncrement bool // force timestamp increment | ||||
| } | ||||
|  | ||||
| func (self *FixTime) ModifyPacket(pkt *av.Packet, streams []av.CodecData, videoidx int, audioidx int) (drop bool, err error) { | ||||
| 	if self.StartFromZero { | ||||
| 		if self.zerobase == 0 { | ||||
| 			self.zerobase = pkt.Time | ||||
| 		} | ||||
| 		pkt.Time -= self.zerobase | ||||
| 	} | ||||
|  | ||||
| 	if self.MakeIncrement { | ||||
| 		pkt.Time -= self.incrbase | ||||
| 		if self.lasttime == 0 { | ||||
| 			self.lasttime = pkt.Time | ||||
| 		} | ||||
| 		if pkt.Time < self.lasttime || pkt.Time > self.lasttime+time.Millisecond*500 { | ||||
| 			self.incrbase += pkt.Time - self.lasttime | ||||
| 			pkt.Time = self.lasttime | ||||
| 		} | ||||
| 		self.lasttime = pkt.Time | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Drop incorrect packets to make A/V sync. | ||||
| type AVSync struct { | ||||
| 	MaxTimeDiff time.Duration | ||||
| 	time        []time.Duration | ||||
| } | ||||
|  | ||||
| func (self *AVSync) ModifyPacket(pkt *av.Packet, streams []av.CodecData, videoidx int, audioidx int) (drop bool, err error) { | ||||
| 	if self.time == nil { | ||||
| 		self.time = make([]time.Duration, len(streams)) | ||||
| 		if self.MaxTimeDiff == 0 { | ||||
| 			self.MaxTimeDiff = time.Millisecond * 500 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	start, end, correctable, correcttime := self.check(int(pkt.Idx)) | ||||
| 	if pkt.Time >= start && pkt.Time < end { | ||||
| 		self.time[pkt.Idx] = pkt.Time | ||||
| 	} else { | ||||
| 		if correctable { | ||||
| 			pkt.Time = correcttime | ||||
| 			for i := range self.time { | ||||
| 				self.time[i] = correcttime | ||||
| 			} | ||||
| 		} else { | ||||
| 			drop = true | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *AVSync) check(i int) (start time.Duration, end time.Duration, correctable bool, correcttime time.Duration) { | ||||
| 	minidx := -1 | ||||
| 	maxidx := -1 | ||||
| 	for j := range self.time { | ||||
| 		if minidx == -1 || self.time[j] < self.time[minidx] { | ||||
| 			minidx = j | ||||
| 		} | ||||
| 		if maxidx == -1 || self.time[j] > self.time[maxidx] { | ||||
| 			maxidx = j | ||||
| 		} | ||||
| 	} | ||||
| 	allthesame := self.time[minidx] == self.time[maxidx] | ||||
|  | ||||
| 	if i == maxidx { | ||||
| 		if allthesame { | ||||
| 			correctable = true | ||||
| 		} else { | ||||
| 			correctable = false | ||||
| 		} | ||||
| 	} else { | ||||
| 		correctable = true | ||||
| 	} | ||||
|  | ||||
| 	start = self.time[minidx] | ||||
| 	end = start + self.MaxTimeDiff | ||||
| 	correcttime = start + time.Millisecond*40 | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Make packets reading speed as same as walltime, effect like ffmpeg -re option. | ||||
| type Walltime struct { | ||||
| 	firsttime time.Time | ||||
| } | ||||
|  | ||||
| func (self *Walltime) ModifyPacket(pkt *av.Packet, streams []av.CodecData, videoidx int, audioidx int) (drop bool, err error) { | ||||
| 	if pkt.Idx == 0 { | ||||
| 		if self.firsttime.IsZero() { | ||||
| 			self.firsttime = time.Now() | ||||
| 		} | ||||
| 		pkttime := self.firsttime.Add(pkt.Time) | ||||
| 		delta := pkttime.Sub(time.Now()) | ||||
| 		if delta > 0 { | ||||
| 			time.Sleep(delta) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										61
									
								
								av/pktque/timeline.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								av/pktque/timeline.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| package pktque | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| /* | ||||
| pop                                   push | ||||
|  | ||||
|      seg                 seg        seg | ||||
|   |--------|         |---------|   |---| | ||||
|      20ms                40ms       5ms | ||||
| ----------------- time --------------------> | ||||
| headtm                               tailtm | ||||
| */ | ||||
|  | ||||
| type tlSeg struct { | ||||
| 	tm, dur time.Duration | ||||
| } | ||||
|  | ||||
| type Timeline struct { | ||||
| 	segs []tlSeg | ||||
| 	headtm time.Duration | ||||
| } | ||||
|  | ||||
| func (self *Timeline) Push(tm time.Duration, dur time.Duration) { | ||||
| 	if len(self.segs) > 0 { | ||||
| 		tail := self.segs[len(self.segs)-1] | ||||
| 		diff := tm-(tail.tm+tail.dur) | ||||
| 		if diff < 0 { | ||||
| 			tm -= diff | ||||
| 		} | ||||
| 	} | ||||
| 	self.segs = append(self.segs, tlSeg{tm, dur}) | ||||
| } | ||||
|  | ||||
| func (self *Timeline) Pop(dur time.Duration) (tm time.Duration) { | ||||
| 	if len(self.segs) == 0 { | ||||
| 		return self.headtm | ||||
| 	} | ||||
|  | ||||
| 	tm = self.segs[0].tm | ||||
| 	for dur > 0 && len(self.segs) > 0 { | ||||
| 		seg := &self.segs[0] | ||||
| 		sub := dur | ||||
| 		if seg.dur < sub { | ||||
| 			sub = seg.dur | ||||
| 		} | ||||
| 		seg.dur -= sub | ||||
| 		dur -= sub | ||||
| 		seg.tm += sub | ||||
| 		self.headtm += sub | ||||
| 		if seg.dur == 0 { | ||||
| 			copy(self.segs[0:], self.segs[1:]) | ||||
| 			self.segs = self.segs[:len(self.segs)-1] | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
							
								
								
									
										218
									
								
								av/pubsub/queue.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								av/pubsub/queue.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,218 @@ | ||||
| // Packege pubsub implements publisher-subscribers model used in multi-channel streaming. | ||||
| package pubsub | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/deepch/vdk/av" | ||||
| 	"github.com/deepch/vdk/av/pktque" | ||||
| ) | ||||
|  | ||||
| //        time | ||||
| // -----------------> | ||||
| // | ||||
| // V-A-V-V-A-V-V-A-V-V | ||||
| // |                 | | ||||
| // 0        5        10 | ||||
| // head             tail | ||||
| // oldest          latest | ||||
| // | ||||
|  | ||||
| // One publisher and multiple subscribers thread-safe packet buffer queue. | ||||
| type Queue struct { | ||||
| 	buf                      *pktque.Buf | ||||
| 	head, tail               int | ||||
| 	lock                     *sync.RWMutex | ||||
| 	cond                     *sync.Cond | ||||
| 	curgopcount, maxgopcount int | ||||
| 	streams                  []av.CodecData | ||||
| 	videoidx                 int | ||||
| 	closed                   bool | ||||
| } | ||||
|  | ||||
| func NewQueue() *Queue { | ||||
| 	q := &Queue{} | ||||
| 	q.buf = pktque.NewBuf() | ||||
| 	q.maxgopcount = 2 | ||||
| 	q.lock = &sync.RWMutex{} | ||||
| 	q.cond = sync.NewCond(q.lock.RLocker()) | ||||
| 	q.videoidx = -1 | ||||
| 	return q | ||||
| } | ||||
|  | ||||
| func (self *Queue) SetMaxGopCount(n int) { | ||||
| 	self.lock.Lock() | ||||
| 	self.maxgopcount = n | ||||
| 	self.lock.Unlock() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Queue) WriteHeader(streams []av.CodecData) error { | ||||
| 	self.lock.Lock() | ||||
|  | ||||
| 	self.streams = streams | ||||
| 	for i, stream := range streams { | ||||
| 		if stream.Type().IsVideo() { | ||||
| 			self.videoidx = i | ||||
| 		} | ||||
| 	} | ||||
| 	self.cond.Broadcast() | ||||
|  | ||||
| 	self.lock.Unlock() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (self *Queue) WriteTrailer() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // After Close() called, all QueueCursor's ReadPacket will return io.EOF. | ||||
| func (self *Queue) Close() (err error) { | ||||
| 	self.lock.Lock() | ||||
|  | ||||
| 	self.closed = true | ||||
| 	self.cond.Broadcast() | ||||
|  | ||||
| 	self.lock.Unlock() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Put packet into buffer, old packets will be discared. | ||||
| func (self *Queue) WritePacket(pkt av.Packet) (err error) { | ||||
| 	self.lock.Lock() | ||||
|  | ||||
| 	self.buf.Push(pkt) | ||||
| 	if pkt.Idx == int8(self.videoidx) && pkt.IsKeyFrame { | ||||
| 		self.curgopcount++ | ||||
| 	} | ||||
|  | ||||
| 	for self.curgopcount >= self.maxgopcount && self.buf.Count > 1 { | ||||
| 		pkt := self.buf.Pop() | ||||
| 		if pkt.Idx == int8(self.videoidx) && pkt.IsKeyFrame { | ||||
| 			self.curgopcount-- | ||||
| 		} | ||||
| 		if self.curgopcount < self.maxgopcount { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	//println("shrink", self.curgopcount, self.maxgopcount, self.buf.Head, self.buf.Tail, "count", self.buf.Count, "size", self.buf.Size) | ||||
|  | ||||
| 	self.cond.Broadcast() | ||||
|  | ||||
| 	self.lock.Unlock() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| type QueueCursor struct { | ||||
| 	que    *Queue | ||||
| 	pos    pktque.BufPos | ||||
| 	gotpos bool | ||||
| 	init   func(buf *pktque.Buf, videoidx int) pktque.BufPos | ||||
| } | ||||
|  | ||||
| func (self *Queue) newCursor() *QueueCursor { | ||||
| 	return &QueueCursor{ | ||||
| 		que: self, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Create cursor position at latest packet. | ||||
| func (self *Queue) Latest() *QueueCursor { | ||||
| 	cursor := self.newCursor() | ||||
| 	cursor.init = func(buf *pktque.Buf, videoidx int) pktque.BufPos { | ||||
| 		return buf.Tail | ||||
| 	} | ||||
| 	return cursor | ||||
| } | ||||
|  | ||||
| // Create cursor position at oldest buffered packet. | ||||
| func (self *Queue) Oldest() *QueueCursor { | ||||
| 	cursor := self.newCursor() | ||||
| 	cursor.init = func(buf *pktque.Buf, videoidx int) pktque.BufPos { | ||||
| 		return buf.Head | ||||
| 	} | ||||
| 	return cursor | ||||
| } | ||||
|  | ||||
| // Create cursor position at specific time in buffered packets. | ||||
| func (self *Queue) DelayedTime(dur time.Duration) *QueueCursor { | ||||
| 	cursor := self.newCursor() | ||||
| 	cursor.init = func(buf *pktque.Buf, videoidx int) pktque.BufPos { | ||||
| 		i := buf.Tail - 1 | ||||
| 		if buf.IsValidPos(i) { | ||||
| 			end := buf.Get(i) | ||||
| 			for buf.IsValidPos(i) { | ||||
| 				if end.Time-buf.Get(i).Time > dur { | ||||
| 					break | ||||
| 				} | ||||
| 				i-- | ||||
| 			} | ||||
| 		} | ||||
| 		return i | ||||
| 	} | ||||
| 	return cursor | ||||
| } | ||||
|  | ||||
| // Create cursor position at specific delayed GOP count in buffered packets. | ||||
| func (self *Queue) DelayedGopCount(n int) *QueueCursor { | ||||
| 	cursor := self.newCursor() | ||||
| 	cursor.init = func(buf *pktque.Buf, videoidx int) pktque.BufPos { | ||||
| 		i := buf.Tail - 1 | ||||
| 		if videoidx != -1 { | ||||
| 			for gop := 0; buf.IsValidPos(i) && gop < n; i-- { | ||||
| 				pkt := buf.Get(i) | ||||
| 				if pkt.Idx == int8(self.videoidx) && pkt.IsKeyFrame { | ||||
| 					gop++ | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		return i | ||||
| 	} | ||||
| 	return cursor | ||||
| } | ||||
|  | ||||
| func (self *QueueCursor) Streams() (streams []av.CodecData, err error) { | ||||
| 	self.que.cond.L.Lock() | ||||
| 	for self.que.streams == nil && !self.que.closed { | ||||
| 		self.que.cond.Wait() | ||||
| 	} | ||||
| 	if self.que.streams != nil { | ||||
| 		streams = self.que.streams | ||||
| 	} else { | ||||
| 		err = io.EOF | ||||
| 	} | ||||
| 	self.que.cond.L.Unlock() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // ReadPacket will not consume packets in Queue, it's just a cursor. | ||||
| func (self *QueueCursor) ReadPacket() (pkt av.Packet, err error) { | ||||
| 	self.que.cond.L.Lock() | ||||
| 	buf := self.que.buf | ||||
| 	if !self.gotpos { | ||||
| 		self.pos = self.init(buf, self.que.videoidx) | ||||
| 		self.gotpos = true | ||||
| 	} | ||||
| 	for { | ||||
| 		if self.pos.LT(buf.Head) { | ||||
| 			self.pos = buf.Head | ||||
| 		} else if self.pos.GT(buf.Tail) { | ||||
| 			self.pos = buf.Tail | ||||
| 		} | ||||
| 		if buf.IsValidPos(self.pos) { | ||||
| 			pkt = buf.Get(self.pos) | ||||
| 			self.pos++ | ||||
| 			break | ||||
| 		} | ||||
| 		if self.que.closed { | ||||
| 			err = io.EOF | ||||
| 			break | ||||
| 		} | ||||
| 		self.que.cond.Wait() | ||||
| 	} | ||||
| 	self.que.cond.L.Unlock() | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										247
									
								
								av/transcode/transcode.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								av/transcode/transcode.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,247 @@ | ||||
| // Package transcoder implements Transcoder based on Muxer/Demuxer and AudioEncoder/AudioDecoder interface. | ||||
| package transcode | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/deepch/vdk/av" | ||||
| 	"github.com/deepch/vdk/av/pktque" | ||||
| ) | ||||
|  | ||||
| var Debug bool | ||||
|  | ||||
| type tStream struct { | ||||
| 	codec              av.CodecData | ||||
| 	timeline           *pktque.Timeline | ||||
| 	aencodec, adecodec av.AudioCodecData | ||||
| 	aenc               av.AudioEncoder | ||||
| 	adec               av.AudioDecoder | ||||
| } | ||||
|  | ||||
| type Options struct { | ||||
| 	// check if transcode is needed, and create the AudioDecoder and AudioEncoder. | ||||
| 	FindAudioDecoderEncoder func(codec av.AudioCodecData, i int) ( | ||||
| 		need bool, dec av.AudioDecoder, enc av.AudioEncoder, err error, | ||||
| 	) | ||||
| } | ||||
|  | ||||
| type Transcoder struct { | ||||
| 	streams []*tStream | ||||
| } | ||||
|  | ||||
| func NewTranscoder(streams []av.CodecData, options Options) (_self *Transcoder, err error) { | ||||
| 	self := &Transcoder{} | ||||
| 	self.streams = []*tStream{} | ||||
|  | ||||
| 	for i, stream := range streams { | ||||
| 		ts := &tStream{codec: stream} | ||||
| 		if stream.Type().IsAudio() { | ||||
| 			if options.FindAudioDecoderEncoder != nil { | ||||
| 				var ok bool | ||||
| 				var enc av.AudioEncoder | ||||
| 				var dec av.AudioDecoder | ||||
| 				ok, dec, enc, err = options.FindAudioDecoderEncoder(stream.(av.AudioCodecData), i) | ||||
| 				if ok { | ||||
| 					if err != nil { | ||||
| 						return | ||||
| 					} | ||||
| 					ts.timeline = &pktque.Timeline{} | ||||
| 					if ts.codec, err = enc.CodecData(); err != nil { | ||||
| 						return | ||||
| 					} | ||||
| 					ts.aencodec = ts.codec.(av.AudioCodecData) | ||||
| 					ts.adecodec = stream.(av.AudioCodecData) | ||||
| 					ts.aenc = enc | ||||
| 					ts.adec = dec | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		self.streams = append(self.streams, ts) | ||||
| 	} | ||||
|  | ||||
| 	_self = self | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *tStream) audioDecodeAndEncode(inpkt av.Packet) (outpkts []av.Packet, err error) { | ||||
| 	var dur time.Duration | ||||
| 	var frame av.AudioFrame | ||||
| 	var ok bool | ||||
| 	if ok, frame, err = self.adec.Decode(inpkt.Data); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if !ok { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if dur, err = self.adecodec.PacketDuration(inpkt.Data); err != nil { | ||||
| 		err = fmt.Errorf("transcode: PacketDuration() failed for input stream #%d", inpkt.Idx) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if Debug { | ||||
| 		fmt.Println("transcode: push", inpkt.Time, dur) | ||||
| 	} | ||||
| 	self.timeline.Push(inpkt.Time, dur) | ||||
|  | ||||
| 	var _outpkts [][]byte | ||||
| 	if _outpkts, err = self.aenc.Encode(frame); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	for _, _outpkt := range _outpkts { | ||||
| 		if dur, err = self.aencodec.PacketDuration(_outpkt); err != nil { | ||||
| 			err = fmt.Errorf("transcode: PacketDuration() failed for output stream #%d", inpkt.Idx) | ||||
| 			return | ||||
| 		} | ||||
| 		outpkt := av.Packet{Idx: inpkt.Idx, Data: _outpkt} | ||||
| 		outpkt.Time = self.timeline.Pop(dur) | ||||
|  | ||||
| 		if Debug { | ||||
| 			fmt.Println("transcode: pop", outpkt.Time, dur) | ||||
| 		} | ||||
|  | ||||
| 		outpkts = append(outpkts, outpkt) | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Do the transcode. | ||||
| // | ||||
| // In audio transcoding one Packet may transcode into many Packets | ||||
| // packet time will be adjusted automatically. | ||||
| func (self *Transcoder) Do(pkt av.Packet) (out []av.Packet, err error) { | ||||
| 	stream := self.streams[pkt.Idx] | ||||
| 	if stream.aenc != nil && stream.adec != nil { | ||||
| 		if out, err = stream.audioDecodeAndEncode(pkt); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} else { | ||||
| 		out = append(out, pkt) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Get CodecDatas after transcoding. | ||||
| func (self *Transcoder) Streams() (streams []av.CodecData, err error) { | ||||
| 	for _, stream := range self.streams { | ||||
| 		streams = append(streams, stream.codec) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Close transcoder, close related encoder and decoders. | ||||
| func (self *Transcoder) Close() (err error) { | ||||
| 	for _, stream := range self.streams { | ||||
| 		if stream.aenc != nil { | ||||
| 			stream.aenc.Close() | ||||
| 			stream.aenc = nil | ||||
| 		} | ||||
| 		if stream.adec != nil { | ||||
| 			stream.adec.Close() | ||||
| 			stream.adec = nil | ||||
| 		} | ||||
| 	} | ||||
| 	self.streams = nil | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Wrap transcoder and origin Muxer into new Muxer. | ||||
| // Write to new Muxer will do transcoding automatically. | ||||
| type Muxer struct { | ||||
| 	av.Muxer   // origin Muxer | ||||
| 	Options    // transcode options | ||||
| 	transcoder *Transcoder | ||||
| } | ||||
|  | ||||
| func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) { | ||||
| 	if self.transcoder, err = NewTranscoder(streams, self.Options); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	var newstreams []av.CodecData | ||||
| 	if newstreams, err = self.transcoder.Streams(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = self.Muxer.WriteHeader(newstreams); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Muxer) WritePacket(pkt av.Packet) (err error) { | ||||
| 	var outpkts []av.Packet | ||||
| 	if outpkts, err = self.transcoder.Do(pkt); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	for _, pkt := range outpkts { | ||||
| 		if err = self.Muxer.WritePacket(pkt); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Muxer) Close() (err error) { | ||||
| 	if self.transcoder != nil { | ||||
| 		return self.transcoder.Close() | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Wrap transcoder and origin Demuxer into new Demuxer. | ||||
| // Read this Demuxer will do transcoding automatically. | ||||
| type Demuxer struct { | ||||
| 	av.Demuxer | ||||
| 	Options | ||||
| 	transcoder *Transcoder | ||||
| 	outpkts    []av.Packet | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) prepare() (err error) { | ||||
| 	if self.transcoder == nil { | ||||
| 		var streams []av.CodecData | ||||
| 		if streams, err = self.Demuxer.Streams(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		if self.transcoder, err = NewTranscoder(streams, self.Options); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) { | ||||
| 	if err = self.prepare(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	for { | ||||
| 		if len(self.outpkts) > 0 { | ||||
| 			pkt = self.outpkts[0] | ||||
| 			self.outpkts = self.outpkts[1:] | ||||
| 			return | ||||
| 		} | ||||
| 		var rpkt av.Packet | ||||
| 		if rpkt, err = self.Demuxer.ReadPacket(); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		if self.outpkts, err = self.transcoder.Do(rpkt); err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) Streams() (streams []av.CodecData, err error) { | ||||
| 	if err = self.prepare(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return self.transcoder.Streams() | ||||
| } | ||||
|  | ||||
| func (self *Demuxer) Close() (err error) { | ||||
| 	if self.transcoder != nil { | ||||
| 		return self.transcoder.Close() | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Andrey Semochkin
					Andrey Semochkin