diff --git a/go.mod b/go.mod index 0957e2f..84b720a 100644 --- a/go.mod +++ b/go.mod @@ -1,29 +1,16 @@ -module harshabose/transcode/v1 +module github.com/harshabose/simple_webrtc_comm/transcode go 1.23 require ( - github.com/asticode/go-astiav v0.33.1 // indirect - github.com/asticode/go-astikit v0.42.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/pion/datachannel v1.5.10 // indirect - github.com/pion/dtls/v3 v3.0.4 // indirect - github.com/pion/ice/v4 v4.0.6 // indirect - github.com/pion/interceptor v0.1.37 // indirect - github.com/pion/logging v0.2.3 // indirect - github.com/pion/mdns/v2 v2.0.7 // indirect - github.com/pion/randutil v0.1.0 // indirect - github.com/pion/rtcp v1.2.15 // indirect - github.com/pion/rtp v1.8.11 // indirect - github.com/pion/sctp v1.8.35 // indirect - github.com/pion/sdp/v3 v3.0.10 // indirect - github.com/pion/srtp/v3 v3.0.4 // indirect - github.com/pion/stun/v3 v3.0.0 // indirect - github.com/pion/transport/v3 v3.0.7 // indirect - github.com/pion/turn/v4 v4.0.0 // indirect - github.com/pion/webrtc/v4 v4.0.10 // indirect - github.com/wlynxg/anet v0.0.5 // indirect - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect + github.com/asticode/go-astiav v0.33.1 + github.com/pion/rtp v1.8.11 + github.com/pion/webrtc/v4 v4.0.10 + github.com/asticode/go-astikit v0.52.0 // indirect + github.com/pion/randutil v0.1.0 // indirect + github.com/harshabose/tools/buffer v0.0.0 +) + +replace ( + github.com/harshabose/tools/buffer => ../tools/buffer ) diff --git a/go.sum b/go.sum index 702edf6..42982f5 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,20 @@ github.com/asticode/go-astiav v0.33.1 h1:fll7jDP1LCosVTpqJNze0z0TAokuyFj+Jjls+g1 github.com/asticode/go-astiav v0.33.1/go.mod h1:K7D8UC6GeQt85FUxk2KVwYxHnotrxuEnp5evkkudc2s= github.com/asticode/go-astikit v0.42.0 h1:pnir/2KLUSr0527Tv908iAH6EGYYrYta132vvjXsH5w= github.com/asticode/go-astikit v0.42.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= +github.com/asticode/go-astikit v0.52.0 h1:kTl2XjgiVQhUl1H7kim7NhmTtCMwVBbPrXKqhQhbk8Y= +github.com/asticode/go-astikit v0.52.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U= @@ -36,6 +48,12 @@ github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM= github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA= github.com/pion/webrtc/v4 v4.0.10 h1:Hq/JLjhqLxi+NmCtE8lnRPDr8H4LcNvwg8OxVcdv56Q= github.com/pion/webrtc/v4 v4.0.10/go.mod h1:ViHLVaNpiuvaH8pdiuQxuA9awuE6KVzAXx3vVWilOck= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sclevine/agouti v3.0.0+incompatible h1:8IBJS6PWz3uTlMP3YBIR5f+KAldcGuOeFkFbUWfBgK4= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= @@ -44,3 +62,11 @@ golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/errors.go b/internal/errors.go deleted file mode 100644 index f1f0a7a..0000000 --- a/internal/errors.go +++ /dev/null @@ -1,8 +0,0 @@ -package internal - -import "errors" - -var ( - ErrorElementUnallocated = errors.New("encountered nil in the buffer. this should not happen. check usage") - ErrorChannelBufferClose = errors.New("channel buffer has be closed. cannot perform this operation") -) diff --git a/internal/frame_pool.go b/internal/frame_pool.go index 80dfe2c..c641f39 100644 --- a/internal/frame_pool.go +++ b/internal/frame_pool.go @@ -4,12 +4,23 @@ import ( "sync" "github.com/asticode/go-astiav" + "github.com/harshabose/tools/buffer/pkg" ) type framePool struct { pool sync.Pool } +func CreateFramePool() buffer.Pool[astiav.Frame] { + return &framePool{ + pool: sync.Pool{ + New: func() any { + return astiav.AllocFrame() + }, + }, + } +} + func (pool *framePool) Get() *astiav.Frame { frame, ok := pool.pool.Get().(*astiav.Frame) diff --git a/internal/limit_buffer.go b/internal/limit_buffer.go deleted file mode 100644 index 2106e7e..0000000 --- a/internal/limit_buffer.go +++ /dev/null @@ -1,106 +0,0 @@ -package internal - -import ( - "context" - "fmt" -) - -type limitBuffer[T any] struct { - pool Pool[T] - bufferChannel chan *T - inputBuffer chan *T - ctx context.Context -} - -func (buffer *limitBuffer[T]) Push(ctx context.Context, element *T) error { - select { - case buffer.inputBuffer <- element: - // WARN: LACKS CHECKS FOR CLOSED CHANNEL - return nil - case <-ctx.Done(): - return ctx.Err() - } -} - -func (buffer *limitBuffer[T]) Pop(ctx context.Context) (*T, error) { - select { - case <-ctx.Done(): - return nil, ctx.Err() - case data, ok := <-buffer.bufferChannel: - if !ok { - return nil, ErrorChannelBufferClose - } - if data == nil { - return nil, ErrorElementUnallocated - } - return data, nil - } -} - -func (buffer *limitBuffer[T]) Generate() *T { - return buffer.pool.Get() -} - -func (buffer *limitBuffer[T]) PutBack(element *T) { - if buffer.pool != nil { - buffer.pool.Put(element) - } -} - -func (buffer *limitBuffer[T]) GetChannel() chan *T { - return buffer.bufferChannel -} - -func (buffer *limitBuffer[T]) Size() int { - return len(buffer.bufferChannel) -} - -func (buffer *limitBuffer[T]) loop() { - defer buffer.close() -loop: - for { - select { - case <-buffer.ctx.Done(): - return - case element, ok := <-buffer.inputBuffer: - if !ok || element == nil { - continue loop - } - select { - case buffer.bufferChannel <- element: // SUCCESSFULLY BUFFERED - continue loop - default: - select { - case oldElement := <-buffer.bufferChannel: - buffer.PutBack(oldElement) - select { - case buffer.bufferChannel <- element: - continue loop - default: - fmt.Println("unexpected buffer state. skipping the element..") - buffer.PutBack(element) - } - } - } - } - } -} - -func (buffer *limitBuffer[T]) close() { -loop: - for { - select { - case element := <-buffer.bufferChannel: - if buffer.pool != nil { - buffer.pool.Put(element) - } - default: - close(buffer.bufferChannel) - close(buffer.inputBuffer) - break loop - } - } - if buffer.pool != nil { - buffer.pool.Release() - } -} diff --git a/internal/package.go b/internal/package.go deleted file mode 100644 index f2fb094..0000000 --- a/internal/package.go +++ /dev/null @@ -1,82 +0,0 @@ -package internal - -import ( - "context" - "sync" - - "github.com/asticode/go-astiav" - "github.com/pion/rtp" - "github.com/pion/webrtc/v4/pkg/media" -) - -type Pool[T any] interface { - Get() *T - Put(*T) - Release() -} - -type Buffer[T any] interface { - Push(context.Context, *T) error - Pop(ctx context.Context) (*T, error) - Size() int -} - -type BufferWithGenerator[T any] interface { - Push(context.Context, *T) error - Pop(ctx context.Context) (*T, error) - Size() int - Generate() *T - PutBack(*T) - GetChannel() chan *T -} - -func CreateFramePool() Pool[astiav.Frame] { - return &framePool{ - pool: sync.Pool{ - New: func() any { - return astiav.AllocFrame() - }, - }, - } -} - -func CreateSamplePool() Pool[media.Sample] { - return &samplePool{ - pool: sync.Pool{ - New: func() any { - return &media.Sample{} - }, - }, - } -} - -func CreateRTPPool() Pool[rtp.Packet] { - return &rtpPool{ - pool: sync.Pool{ - New: func() any { - return &rtp.Packet{} - }, - }, - } -} - -func CreatePacketPool() Pool[astiav.Packet] { - return &packetPool{ - pool: sync.Pool{ - New: func() any { - return astiav.AllocPacket() - }, - }, - } -} - -func CreateChannelBuffer[T any](ctx context.Context, size int, pool Pool[T]) BufferWithGenerator[T] { - buffer := &limitBuffer[T]{ - pool: pool, - bufferChannel: make(chan *T, size), - inputBuffer: make(chan *T), - ctx: ctx, - } - go buffer.loop() - return buffer -} diff --git a/internal/packet_pool.go b/internal/packet_pool.go index ff4e78f..bc47053 100644 --- a/internal/packet_pool.go +++ b/internal/packet_pool.go @@ -4,12 +4,23 @@ import ( "sync" "github.com/asticode/go-astiav" + "github.com/harshabose/tools/buffer/pkg" ) type packetPool struct { pool sync.Pool } +func CreatePacketPool() buffer.Pool[astiav.Packet] { + return &packetPool{ + pool: sync.Pool{ + New: func() any { + return astiav.AllocPacket() + }, + }, + } +} + func (pool *packetPool) Get() *astiav.Packet { packet, ok := pool.pool.Get().(*astiav.Packet) diff --git a/internal/rtp_pool.go b/internal/rtp_pool.go index dace164..2076251 100644 --- a/internal/rtp_pool.go +++ b/internal/rtp_pool.go @@ -3,6 +3,7 @@ package internal import ( "sync" + "github.com/harshabose/tools/buffer/pkg" "github.com/pion/rtp" ) @@ -10,6 +11,16 @@ type rtpPool struct { pool sync.Pool } +func CreateRTPPool() buffer.Pool[rtp.Packet] { + return &rtpPool{ + pool: sync.Pool{ + New: func() any { + return &rtp.Packet{} + }, + }, + } +} + func (pool *rtpPool) Get() *rtp.Packet { packet, ok := pool.pool.Get().(*rtp.Packet) diff --git a/internal/sample_pool.go b/internal/sample_pool.go deleted file mode 100644 index e8dc001..0000000 --- a/internal/sample_pool.go +++ /dev/null @@ -1,41 +0,0 @@ -package internal - -import ( - "sync" - - "github.com/pion/webrtc/v4/pkg/media" -) - -type samplePool struct { - pool sync.Pool -} - -func (pool *samplePool) Get() *media.Sample { - packet, ok := pool.pool.Get().(*media.Sample) - - if packet == nil || !ok { - return &media.Sample{} - } - return packet -} - -func (pool *samplePool) Put(sample *media.Sample) { - if sample == nil { - return - } - pool.pool.Put(sample) -} - -func (pool *samplePool) Release() { - for { - sample, ok := pool.pool.Get().(*media.Sample) - if !ok { - continue - } - if sample == nil { - break - } - - sample = nil - } -} diff --git a/internal/timer_buffer.go b/internal/timer_buffer.go deleted file mode 100644 index 5bf0569..0000000 --- a/internal/timer_buffer.go +++ /dev/null @@ -1 +0,0 @@ -package internal diff --git a/main.go b/main.go deleted file mode 100644 index 46a652c..0000000 --- a/main.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - "fmt" -) - -// TIP

To run your code, right-click the code and select Run.

Alternatively, click -// the icon in the gutter and select the Run menu item from here.

- -func main() { - // TIP

Press when your caret is at the underlined text - // to see how GoLand suggests fixing the warning.

Alternatively, if available, click the lightbulb to view possible fixes.

- s := "gopher" - fmt.Printf("Hello and welcome, %s!\n", s) - - for i := 1; i <= 5; i++ { - // TIP

To start your debugging session, right-click your code in the editor and select the Debug option.

We have set one breakpoint - // for you, but you can always add more by pressing .

- fmt.Println("i =", 100/i) - } -} diff --git a/pkg/constants.go b/pkg/constants.go index 9c47cb0..3f79a48 100644 --- a/pkg/constants.go +++ b/pkg/constants.go @@ -1,13 +1,22 @@ -package pkg +package transcode import "github.com/asticode/go-astiav" const ( DefaultVideoPayloadType = 96 DefaultVideoFPS = int(25) + DefaultVideoTimeBase = int(9000) DefaultVideoClockRate = int(90000) DefaultVideoHeight = int(1080) DefaultVideoWidth = int(1920) DefaultVideoPixFormat = astiav.PixelFormatYuv420P DefaultVideoEncoderCodec = astiav.CodecIDH264 ) + +const ( + DefaultAudioPayloadType = 111 + DefaultAudioSampleRate = int(48000) + DefaultAudioFrameSize = int(960) + DefaultAudioSampleFormat = astiav.SampleFormatS16 + DefaultAudioEncoderCodec = astiav.CodecIDOpus +) diff --git a/pkg/decoder.go b/pkg/decoder.go index 0039cbf..7e422c0 100644 --- a/pkg/decoder.go +++ b/pkg/decoder.go @@ -1,4 +1,4 @@ -package pkg +package transcode import ( "context" @@ -7,22 +7,23 @@ import ( "time" "github.com/asticode/go-astiav" + "github.com/harshabose/tools/buffer/pkg" - "harshabose/transcode/v1/internal" + "github.com/harshabose/simple_webrtc_comm/transcode/internal" ) type Decoder struct { demuxer *Demuxer decoderContext *astiav.CodecContext codec *astiav.Codec - buffer internal.BufferWithGenerator[astiav.Frame] + buffer buffer.BufferWithGenerator[astiav.Frame] ctx context.Context } func CreateDecoder(ctx context.Context, demuxer *Demuxer, options ...DecoderOption) (*Decoder, error) { decoder := &Decoder{ demuxer: demuxer, - buffer: internal.CreateChannelBuffer(ctx, DefaultVideoFPS*3, internal.CreateFramePool()), + buffer: buffer.CreateChannelBuffer(ctx, DefaultVideoFPS*3, internal.CreateFramePool()), ctx: ctx, } diff --git a/pkg/decoder_options.go b/pkg/decoder_options.go index 9161787..8078fc2 100644 --- a/pkg/decoder_options.go +++ b/pkg/decoder_options.go @@ -1,4 +1,4 @@ -package pkg +package transcode import "github.com/asticode/go-astiav" @@ -27,3 +27,26 @@ func VideoSetDecoderContext(codecParameters *astiav.CodecParameters, videoStream return nil } } + +func AudioSetDecoderContext(codecParameters *astiav.CodecParameters, stream *astiav.Stream, formatContext *astiav.FormatContext) func(*Decoder) error { + return func(decoder *Decoder) error { + var ( + err error + ) + + if decoder.codec = astiav.FindDecoder(codecParameters.CodecID()); decoder.codec == nil { + return ErrorNoCodecFound + } + + if decoder.decoderContext = astiav.AllocCodecContext(decoder.codec); decoder.decoderContext == nil { + return ErrorAllocateCodecContext + } + + if err = stream.CodecParameters().ToCodecContext(decoder.decoderContext); err != nil { + return ErrorFillCodecContext + } + + decoder.decoderContext.SetTimeBase(stream.TimeBase()) + return nil + } +} diff --git a/pkg/demuxer.go b/pkg/demuxer.go index ea747c1..9ef0af1 100644 --- a/pkg/demuxer.go +++ b/pkg/demuxer.go @@ -1,20 +1,22 @@ -package pkg +package transcode import ( "context" "time" "github.com/asticode/go-astiav" + "github.com/harshabose/tools/buffer/pkg" - "harshabose/transcode/v1/internal" + "github.com/harshabose/simple_webrtc_comm/transcode/internal" ) type Demuxer struct { formatContext *astiav.FormatContext inputOptions *astiav.Dictionary + inputFormat *astiav.InputFormat stream *astiav.Stream codecParameters *astiav.CodecParameters - buffer internal.BufferWithGenerator[astiav.Packet] + buffer buffer.BufferWithGenerator[astiav.Packet] ctx context.Context } @@ -23,7 +25,7 @@ func CreateDemuxer(ctx context.Context, containerAddress string, options ...Demu demuxer := &Demuxer{ formatContext: astiav.AllocFormatContext(), inputOptions: astiav.NewDictionary(), - buffer: internal.CreateChannelBuffer(ctx, DefaultVideoFPS*3, internal.CreatePacketPool()), + buffer: buffer.CreateChannelBuffer(ctx, DefaultVideoFPS*3, internal.CreatePacketPool()), ctx: ctx, } @@ -37,7 +39,7 @@ func CreateDemuxer(ctx context.Context, containerAddress string, options ...Demu } } - if err := demuxer.formatContext.OpenInput(containerAddress, nil, nil); err != nil { + if err := demuxer.formatContext.OpenInput(containerAddress, demuxer.inputFormat, demuxer.inputOptions); err != nil { return nil, ErrorOpenInputContainer } diff --git a/pkg/demuxer_options.go b/pkg/demuxer_options.go index f69dca6..88dcaa2 100644 --- a/pkg/demuxer_options.go +++ b/pkg/demuxer_options.go @@ -1,4 +1,6 @@ -package pkg +package transcode + +import "github.com/asticode/go-astiav" type DemuxerOption = func(*Demuxer) error @@ -23,3 +25,13 @@ func WithRTSPInputOption(demuxer *Demuxer) error { return nil } + +func WithAlsaInputFormatOption(demuxer *Demuxer) error { + demuxer.inputFormat = astiav.FindInputFormat("alsa") + return nil +} + +func WithAvFoundationInputFormatOption(demuxer *Demuxer) error { + demuxer.inputFormat = astiav.FindInputFormat("avfoundation") + return nil +} diff --git a/pkg/encoder.go b/pkg/encoder.go index d7a5ca4..ed1aae4 100644 --- a/pkg/encoder.go +++ b/pkg/encoder.go @@ -1,4 +1,4 @@ -package pkg +package transcode import ( "context" @@ -7,16 +7,18 @@ import ( "github.com/asticode/go-astiav" - "harshabose/transcode/v1/internal" + "github.com/harshabose/tools/buffer/pkg" + + "github.com/harshabose/simple_webrtc_comm/transcode/internal" ) type Encoder struct { - buffer internal.BufferWithGenerator[astiav.Packet] + buffer buffer.BufferWithGenerator[astiav.Packet] filter *Filter ctx context.Context codec *astiav.Codec encoderContext *astiav.CodecContext - h264options *astiav.Dictionary + codecFlags *astiav.Dictionary encoderSettings encoderCodecSetting sps []byte pps []byte @@ -24,9 +26,9 @@ type Encoder struct { func CreateEncoder(ctx context.Context, codecID astiav.CodecID, filter *Filter, options ...EncoderOption) (*Encoder, error) { encoder := &Encoder{ - buffer: internal.CreateChannelBuffer(ctx, DefaultVideoFPS*3, internal.CreatePacketPool()), + buffer: buffer.CreateChannelBuffer(ctx, DefaultVideoFPS*3, internal.CreatePacketPool()), filter: filter, - h264options: astiav.NewDictionary(), + codecFlags: astiav.NewDictionary(), encoderSettings: EncoderCodecNoSetting, ctx: ctx, } @@ -48,7 +50,7 @@ func CreateEncoder(ctx context.Context, codecID astiav.CodecID, filter *Filter, encoder.encoderContext.SetFlags(astiav.NewCodecContextFlags(astiav.CodecContextFlagGlobalHeader)) - if err := encoder.encoderContext.Open(encoder.codec, encoder.h264options); err != nil { + if err := encoder.encoderContext.Open(encoder.codec, encoder.codecFlags); err != nil { return nil, err } @@ -61,6 +63,14 @@ func (encoder *Encoder) Start() { go encoder.loop() } +func (encoder *Encoder) GetFPS() int { // TODO: THIS NEEDS TO BE ABSTRACTED + return DefaultVideoFPS +} + +func (encoder *Encoder) GetVideoTimeBase() int { // TODO: THIS NEEDS TO BE ABSTRACTED + return DefaultVideoClockRate +} + func (encoder *Encoder) loop() { var ( frame *astiav.Frame diff --git a/pkg/encoder_options.go b/pkg/encoder_options.go index fcb321e..976bb5b 100644 --- a/pkg/encoder_options.go +++ b/pkg/encoder_options.go @@ -1,4 +1,4 @@ -package pkg +package transcode type ( encoderCodecSetting string diff --git a/pkg/errors.go b/pkg/errors.go index 07535de..63d614d 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -1,4 +1,4 @@ -package pkg +package transcode import "errors" diff --git a/pkg/filter.go b/pkg/filter.go index 7d38aef..cbfb3ea 100644 --- a/pkg/filter.go +++ b/pkg/filter.go @@ -1,4 +1,4 @@ -package pkg +package transcode import ( "context" @@ -6,14 +6,15 @@ import ( "time" "github.com/asticode/go-astiav" + "github.com/harshabose/tools/buffer/pkg" - "harshabose/transcode/v1/internal" + "github.com/harshabose/simple_webrtc_comm/transcode/internal" ) type Filter struct { content string decoder *Decoder - buffer internal.BufferWithGenerator[astiav.Frame] + buffer buffer.BufferWithGenerator[astiav.Frame] graph *astiav.FilterGraph input *astiav.FilterInOut output *astiav.FilterInOut @@ -33,7 +34,7 @@ func CreateFilter(ctx context.Context, decoder *Decoder, filterConfig *Config, o filter = &Filter{ graph: astiav.AllocFilterGraph(), decoder: decoder, - buffer: internal.CreateChannelBuffer(ctx, DefaultVideoFPS*3, internal.CreateFramePool()), + buffer: buffer.CreateChannelBuffer(ctx, DefaultVideoFPS*3, internal.CreateFramePool()), input: astiav.AllocFilterInOut(), output: astiav.AllocFilterInOut(), srcContextParams: astiav.AllocBuffersrcFilterContextParameters(), diff --git a/pkg/filter_options.go b/pkg/filter_options.go index 60c5080..df76cc7 100644 --- a/pkg/filter_options.go +++ b/pkg/filter_options.go @@ -1,4 +1,4 @@ -package pkg +package transcode import ( "fmt" @@ -23,6 +23,8 @@ type Config struct { const ( videoBufferFilterName Name = "buffer" videoBufferSinkFilterName Name = "buffersink" + audioBufferFilterName Name = "abuffer" + audioBufferSinkFilterName Name = "abuffersink" ) var ( @@ -30,6 +32,10 @@ var ( Source: videoBufferFilterName, Sink: videoBufferSinkFilterName, } + AudioFilters = &Config{ + Source: audioBufferFilterName, + Sink: audioBufferSinkFilterName, + } ) func VideoSetFilterContextParameters(codecContext *astiav.CodecContext) func(*Filter) error { @@ -71,3 +77,104 @@ func videoFPSFilterContent(filter *Filter) error { filter.content += fmt.Sprintf("fps=%d,", DefaultVideoFPS) return nil } + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +func AudioSetFilterContextParameters(codecContext *astiav.CodecContext) func(*Filter) error { + return func(filter *Filter) error { + filter.srcContextParams.SetChannelLayout(codecContext.ChannelLayout()) + filter.srcContextParams.SetSampleFormat(codecContext.SampleFormat()) + filter.srcContextParams.SetSampleRate(codecContext.SampleRate()) + filter.srcContextParams.SetTimeBase(codecContext.TimeBase()) + + return nil + } +} + +func WithDefaultAudioFilterContentOptions(filter *Filter) error { + if err := audioSampleFormatChannelLayoutContent(filter); err != nil { + return err + } + if err := audioSampleRateContent(filter); err != nil { + return err + } + if err := audioFrameSizeContent(filter); err != nil { + return err + } + return nil +} + +func audioSampleFormatChannelLayoutContent(filter *Filter) error { + filter.content += buildAudioFormatContent(DefaultAudioSampleFormat, DefaultAudioChannelLayout) + "," + return nil +} + +func buildAudioFormatContent(sampleFormat astiav.SampleFormat, channelLayout astiav.ChannelLayout) string { + return fmt.Sprintf("aformat=sample_fmts=%s:channel_layouts=%s", sampleFormat.String(), channelLayout.String()) +} + +func audioSampleRateContent(filter *Filter) error { + filter.content += fmt.Sprintf("aresample=%d,", DefaultAudioSampleRate) + return nil +} + +func audioFrameSizeContent(filter *Filter) error { + filter.content += fmt.Sprintf("asetnsamples=%d,", DefaultAudioFrameSize) + return nil +} + +func audioCompressionContent(filter *Filter) error { + // NOTE: DYNAMIC RANGE COMPRESSION TO HANDLE SUDDEN VOLUME CHANGES + // Possible values 'acompressor=threshold=-12dB:ratio=2:attack=0.05:release=0.2" // MOST POPULAR VALUES + filter.content += fmt.Sprintf("acompressor=threshold=%ddB:ratio=%d:attack=%d:release=%d,") + return nil +} + +func audioHighPassContent(filter *Filter) error { + // NOTE: HIGH-PASS FILTER TO REMOVE WIND NOISE AND TURBULENCE + // NOTE: 120HZ CUTOFF MIGHT PRESERVE VOICE WHILE REMOVING LOW RUMBLE; BUT MORE TESTING IS NEEDED + filter.content += fmt.Sprintf("highpass=f=%d,") + return nil +} + +func audioNotchFilterContent(filter *Filter) error { + // NOTE: NOTCH FILTER CAN BE USED TO TARGET SPECIFIC PROPELLER NOISE AND REMOVE THEM + // NOTE: THIS MIGHT BE UNIQUE TO DRONE AND POWER LEVELS. I AM NOT SURE HOW TO USE IT TOO. + filter.content += "afftfilt=real='re*cos(0)':imag='im*cos(0):win_size=1024:fixed=true'," + return nil +} + +// WARN: DO NOT USE FOR NOW +func audioNeuralNetworkDenoiserContent(filter *Filter) error { + // NOTE: A RECURRENT NEURAL NETWORK MIGHT BE THE BEST SOLUTION HERE BUT I AM NOT SURE HOW TO BUILD IT + filter.content += "arnndn=m=," + return nil +} + +func audioEqualiser(filter *Filter) error { + // NOTE: EQUALISER CAN BE USED TO ENHANCE SPEECH BANDWIDTH (300 - 3kHz). MORE RESEARCH NEEDS TO DONE + filter.content += fmt.Sprintf("equalizer=f=%d:t=h:width=%d:g=%d,") + + return nil +} + +func audioSilenceGateContent(filter *Filter) error { + // NOTE: IF EVERYTHING WORKS, WE SHOULD HAVE LIGHT NOISE WHICH CAN BE CONSIDERED AS SILENCE. THIS GATE REMOVES SILENCE + // NOTE: POSSIBLE VALUES 'agate=threshold=-30dB:range=-30dB:attack=0.01:release=0.1" // MOST POPULAR; MORE TESTING IS NEEDED + filter.content += fmt.Sprintf("agate=threshold=%ddB:range=%ddB:attack=%d:release=%d,") + return nil +} + +func audioLoudnessNormaliseContent(filter *Filter) error { + // NOTE: NORMALISES THE FINAL AUDIO. MUST BE CALLED AT THE END + // NOTE: POSSIBLE VALUES "loudnorm=I=-16:TP=-1.5:LRA=11" // MOST POPULAR + filter.content += fmt.Sprintf("loudnorm=I=%d:TP=%d:LRA=%d") + return nil +} + +// WARN: DO NOT USE FOR NOW +func audioNoiseReductionContent(filter *Filter) error { + // NOTE: anlmdn IS A NOISE REDUCTION FILTER. THIS MIGHT EFFECT THE QUALITY SIGNIFICANTLY - USE CAREFULLY + filter.content += fmt.Sprintf("anlmdn=s=%d,") + return nil +}