diff --git a/pkg/encoder.go b/pkg/encoder.go index a5ae8be..f556205 100644 --- a/pkg/encoder.go +++ b/pkg/encoder.go @@ -19,7 +19,7 @@ type Encoder struct { codec *astiav.Codec encoderContext *astiav.CodecContext codecFlags *astiav.Dictionary - encoderSettings encoderCodecSetting + encoderSettings codecSettings bandwidthChan chan int64 sps []byte pps []byte @@ -27,11 +27,10 @@ type Encoder struct { func CreateEncoder(ctx context.Context, codecID astiav.CodecID, filter *Filter, options ...EncoderOption) (*Encoder, error) { encoder := &Encoder{ - buffer: buffer.CreateChannelBuffer(ctx, DefaultVideoFPS*3, internal.CreatePacketPool()), - filter: filter, - codecFlags: astiav.NewDictionary(), - encoderSettings: EncoderCodecNoSetting, - ctx: ctx, + buffer: buffer.CreateChannelBuffer(ctx, DefaultVideoFPS*3, internal.CreatePacketPool()), + filter: filter, + codecFlags: astiav.NewDictionary(), + ctx: ctx, } encoder.codec = astiav.FindEncoder(codecID) @@ -45,7 +44,7 @@ func CreateEncoder(ctx context.Context, codecID astiav.CodecID, filter *Filter, } } - if encoder.encoderSettings == EncoderCodecNoSetting { + if encoder.encoderSettings == nil { return nil, ErrorCodecNoSetting } @@ -55,7 +54,7 @@ func CreateEncoder(ctx context.Context, codecID astiav.CodecID, filter *Filter, return nil, err } - encoder.findParameterSets(encoder.encoderContext.ExtraData()) + //encoder.findParameterSets(encoder.encoderContext.ExtraData()) return encoder, nil } @@ -85,7 +84,7 @@ loop1: select { case <-encoder.ctx.Done(): return - case bitrate := <-encoder.bandwidthChan: + case bitrate := <-encoder.bandwidthChan: // TODO: MIGHT NEED A MUTEX FOR THIS ONE CASE encoder.encoderContext.SetBitRate(bitrate) case frame = <-encoder.filter.WaitForFrame(): if err = encoder.encoderContext.SendFrame(frame); err != nil { @@ -138,45 +137,45 @@ func (encoder *Encoder) SetBitrateChannel(channel chan int64) { encoder.bandwidthChan = channel } -func (encoder *Encoder) findParameterSets(extraData []byte) { - if len(extraData) > 0 { - // Find first start code (0x00000001) - for i := 0; i < len(extraData)-4; i++ { - if extraData[i] == 0 && extraData[i+1] == 0 && extraData[i+2] == 0 && extraData[i+3] == 1 { - // Skip start code to get NAL type - nalType := extraData[i+4] & 0x1F - - // Find next start code or end - nextStart := len(extraData) - for j := i + 4; j < len(extraData)-4; j++ { - if extraData[j] == 0 && extraData[j+1] == 0 && extraData[j+2] == 0 && extraData[j+3] == 1 { - nextStart = j - break - } - } - - if nalType == 7 { // SPS - encoder.sps = make([]byte, nextStart-i) - copy(encoder.sps, extraData[i:nextStart]) - } else if nalType == 8 { // PPS - encoder.pps = make([]byte, len(extraData)-i) - copy(encoder.pps, extraData[i:]) - } - - i = nextStart - 1 - } - } - } - -} - -func (encoder *Encoder) GetSPS() []byte { - return encoder.sps -} - -func (encoder *Encoder) GetPPS() []byte { - return encoder.pps -} +//func (encoder *Encoder) findParameterSets(extraData []byte) { +// if len(extraData) > 0 { +// // Find first start code (0x00000001) +// for i := 0; i < len(extraData)-4; i++ { +// if extraData[i] == 0 && extraData[i+1] == 0 && extraData[i+2] == 0 && extraData[i+3] == 1 { +// // Skip start code to get NAL type +// nalType := extraData[i+4] & 0x1F +// +// // Find next start code or end +// nextStart := len(extraData) +// for j := i + 4; j < len(extraData)-4; j++ { +// if extraData[j] == 0 && extraData[j+1] == 0 && extraData[j+2] == 0 && extraData[j+3] == 1 { +// nextStart = j +// break +// } +// } +// +// if nalType == 7 { // SPS +// encoder.sps = make([]byte, nextStart-i) +// copy(encoder.sps, extraData[i:nextStart]) +// } else if nalType == 8 { // PPS +// encoder.pps = make([]byte, len(extraData)-i) +// copy(encoder.pps, extraData[i:]) +// } +// +// i = nextStart - 1 +// } +// } +// } +// +//} +// +//func (encoder *Encoder) GetSPS() []byte { +// return encoder.sps +//} +// +//func (encoder *Encoder) GetPPS() []byte { +// return encoder.pps +//} func (encoder *Encoder) close() { if encoder.encoderContext != nil { diff --git a/pkg/encoder_options.go b/pkg/encoder_options.go index 976bb5b..80c2093 100644 --- a/pkg/encoder_options.go +++ b/pkg/encoder_options.go @@ -1,14 +1,205 @@ package transcode +import "reflect" + type ( - encoderCodecSetting string - EncoderOption = func(*Encoder) error + EncoderOption = func(*Encoder) error ) -const ( - EncoderCodecNoSetting encoderCodecSetting = "None" - EncoderCodecDefaultSetting encoderCodecSetting = "default" - EncoderCodecHighQualitySetting encoderCodecSetting = "high-quality" - EncoderCodecLowLatencySetting encoderCodecSetting = "low-latency" - EncoderCodecLowBandwidthSetting encoderCodecSetting = "low-bandwidth" -) +type codecSettings interface { + ForEach(func(string, string) error) error +} + +type X264Settings struct { + Preset string `x264:"preset"` + Tune string `x264:"tune"` + Refs string `x264:"refs"` + Profile string `x264:"profile"` + Level string `x264:"level"` + Qmin string `x264:"qmin"` + Qmax string `x264:"qmax"` + BFrames string `x264:"bframes"` + BAdapt string `x264:"b-adapt"` + NGOP string `x264:"keyint"` + NGOPMin string `x264:"min-keyint"` + Scenecut string `x264:"scenecut"` + InfraRefresh string `x264:"intra-refresh"` + LookAhead string `x264:"rc-lookahead"` + SlicedThreads string `x264:"sliced-threads"` + SyncLookAhead string `x264:"sync-lookahead"` + ForceIDR string `x264:"force-idr"` + AQMode string `x264:"aq-mode"` + AQStrength string `x264:"aq-strength"` + MBTree string `x264:"mbtree"` + Bitrate string `x264:"bitrate"` + VBVMaxBitrate string `x264:"vbv-maxrate"` + VBVBuffer string `x264:"vbv-bufsize"` + RateTol string `x264:"ratetol"` + Threads string `x264:"threads"` + AnnexB string `x264:"annexb"` + Aud string `x264:"aud"` +} + +func (s X264Settings) ForEach(fn func(key, value string) error) error { + t := reflect.TypeOf(s) + v := reflect.ValueOf(s) + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + tag := field.Tag.Get("x264") + if tag != "" { + if err := fn(tag, v.Field(i).String()); err != nil { + return err + } + } + } + return nil +} + +var DefaultX264Settings = X264Settings{ + Preset: "medium", + Tune: "film", + Refs: "6", + Profile: "high", + Level: "auto", + Qmin: "18", + Qmax: "28", + BFrames: "3", + BAdapt: "1", + NGOP: "250", + NGOPMin: "25", + Scenecut: "40", + InfraRefresh: "0", + LookAhead: "40", + SlicedThreads: "0", + SyncLookAhead: "1", + ForceIDR: "0", + AQMode: "1", + AQStrength: "1.0", + MBTree: "1", + Bitrate: "4000", + VBVMaxBitrate: "5000", + VBVBuffer: "8000", + RateTol: "1", + Threads: "0", + AnnexB: "1", + Aud: "0", +} + +var LowBandwidthX264Settings = X264Settings{ + Preset: "veryfast", + Tune: "fastdecode", + Refs: "2", + Profile: "baseline", + Level: "4.1", + Qmin: "23", + Qmax: "35", + BFrames: "0", + BAdapt: "0", + NGOP: "60", + NGOPMin: "30", + Scenecut: "30", + InfraRefresh: "0", + LookAhead: "20", + SlicedThreads: "1", + SyncLookAhead: "0", + ForceIDR: "0", + AQMode: "0", + AQStrength: "1.2", + MBTree: "0", + Bitrate: "1500", + VBVMaxBitrate: "1800", + VBVBuffer: "3000", + RateTol: "0.25", + Threads: "0", + AnnexB: "1", + Aud: "0", +} + +var LowLatencyX264Settings = X264Settings{ + Preset: "ultrafast", + Tune: "zerolatency", + Refs: "1", + Profile: "baseline", + Level: "4.1", + Qmin: "20", + Qmax: "32", + BFrames: "0", + BAdapt: "0", + NGOP: "30", + NGOPMin: "15", + Scenecut: "0", + InfraRefresh: "1", + LookAhead: "10", + SlicedThreads: "1", + SyncLookAhead: "0", + ForceIDR: "1", + AQMode: "0", + AQStrength: "0", + MBTree: "0", + Bitrate: "2500", + VBVMaxBitrate: "3000", + VBVBuffer: "5000", + RateTol: "0.5", + Threads: "0", + AnnexB: "1", + Aud: "1", +} + +var HighQualityX264Settings = X264Settings{ + Preset: "slow", + Tune: "film", + Refs: "8", + Profile: "high", + Level: "5.1", + Qmin: "15", + Qmax: "24", + BFrames: "5", + BAdapt: "2", + NGOP: "250", + NGOPMin: "30", + Scenecut: "80", + InfraRefresh: "0", + LookAhead: "60", + SlicedThreads: "0", + SyncLookAhead: "1", + ForceIDR: "0", + AQMode: "0", + AQStrength: "1.3", + MBTree: "1", + Bitrate: "15000", + VBVMaxBitrate: "20000", + VBVBuffer: "30000", + RateTol: "2.0", + Threads: "0", + AnnexB: "1", + Aud: "0", +} + +func WithX264DefaultOptions(encoder *Encoder) error { + encoder.encoderSettings = DefaultX264Settings + return encoder.encoderSettings.ForEach(func(key, value string) error { + return encoder.codecFlags.Set(key, value, 0) + }) +} + +func WithX264HighQualityOptions(encoder *Encoder) error { + encoder.encoderSettings = HighQualityX264Settings + return encoder.encoderSettings.ForEach(func(key, value string) error { + return encoder.codecFlags.Set(key, value, 0) + }) +} + +func WithX264LowLatencyOptions(encoder *Encoder) error { + encoder.encoderSettings = LowLatencyX264Settings + return encoder.encoderSettings.ForEach(func(key, value string) error { + return encoder.codecFlags.Set(key, value, 0) + }) +} + +func WithX264LowBandwidthOptions(encoder *Encoder) error { + encoder.encoderSettings = LowBandwidthX264Settings + return encoder.encoderSettings.ForEach(func(key, value string) error { + return encoder.codecFlags.Set(key, value, 0) + }) +}