Skip to content

Instantly share code, notes, and snippets.

@dbl0null
Last active December 8, 2022 07:16
Show Gist options
  • Save dbl0null/15dc217131e014a5deefcd7b3d68d94d to your computer and use it in GitHub Desktop.
Save dbl0null/15dc217131e014a5deefcd7b3d68d94d to your computer and use it in GitHub Desktop.
package transcoder
import (
"errors"
"fmt"
"log"
"time"
"github.com/dbl0null/rt/internal/cgo/ffmpeg"
"github.com/deepch/vdk/av"
"github.com/deepch/vdk/av/pktque"
)
type AudioTranscoder struct {
Ready bool
prepared bool
timeline pktque.Timeline
encoder *ffmpeg.AudioEncoder
decoder *ffmpeg.AudioDecoder
EncoderData av.AudioCodecData
decoderData av.AudioCodecData
debug bool
}
// Default transcoder errors
var (
ErrorTranscoderNotNeeded = errors.New("source audio codec already fit")
ErrorTranscoderUnsupported = errors.New("unsupported source audio codec")
ErrorTranscoderNoDecoder = errors.New("no audio decoder")
ErrorTranscoderNoEncoder = errors.New("no audio encoder")
ErrorTranscoderEncoderSetup = errors.New("audio encoder setup")
ErrorTranscoderEncoderCodecData = errors.New("audio encoder no codec data")
)
func (transcoder *AudioTranscoder) Setup(codecData av.CodecData) error {
switch codecData.Type() {
case av.PCM_ALAW, av.PCM_MULAW, av.PCM, av.AAC:
encoder, err := ffmpeg.NewAudioEncoderByCodecType(av.OPUS)
if err != nil {
log.Println(ErrorTranscoderNoEncoder, err)
return ErrorTranscoderNoEncoder
}
decoderCodecData := codecData.(av.AudioCodecData)
decoder, err := ffmpeg.NewAudioDecoder(decoderCodecData)
if err != nil {
transcoder.Close()
log.Println(ErrorTranscoderNoDecoder, err)
return ErrorTranscoderNoDecoder
}
_ = encoder.SetSampleRate(48000)
_ = encoder.SetChannelLayout(av.CH_STEREO)
_ = encoder.SetBitrate(48000)
if err = encoder.Setup(); err != nil {
transcoder.Close()
log.Println(ErrorTranscoderEncoderSetup, err)
return ErrorTranscoderEncoderSetup
}
transcoder.decoder = decoder
transcoder.encoder = encoder
transcoder.decoderData = decoderCodecData
transcoder.EncoderData, err = encoder.CodecData()
if err != nil {
transcoder.Close()
log.Println(ErrorTranscoderEncoderSetup, err)
return ErrorTranscoderEncoderCodecData
}
transcoder.Ready = true
case av.OPUS:
return ErrorTranscoderNotNeeded
default:
return ErrorTranscoderUnsupported
}
return nil
}
func MakeAudioTranscoder() AudioTranscoder {
return AudioTranscoder{
Ready: false,
prepared: false,
debug: false,
timeline: pktque.Timeline{},
}
}
func (transcoder *AudioTranscoder) Close() {
transcoder.Ready = false
if transcoder.encoder != nil {
transcoder.encoder.Close()
}
if transcoder.decoder != nil {
transcoder.decoder.Close()
}
}
func (transcoder *AudioTranscoder) Prepare(time time.Duration) {
// if !transcoder.prepared {
transcoder.timeline.Push(time, 100)
transcoder.prepared = true
// }
}
func (transcoder *AudioTranscoder) Do(packetAV *av.Packet) (outpkts []*av.Packet, err error) {
if !transcoder.Ready {
err = ErrorTranscoderNoDecoder
return
}
var dur time.Duration
var frame av.AudioFrame
var ok bool
if ok, frame, err = transcoder.decoder.Decode(packetAV.Data); err != nil {
return
}
if !ok {
return
}
if dur, err = transcoder.decoderData.PacketDuration(packetAV.Data); err != nil {
err = fmt.Errorf("transcode: PacketDuration() failed for input stream #%d", packetAV.Idx)
return
}
if transcoder.debug {
fmt.Println("transcode: push", packetAV.Time, dur)
}
transcoder.timeline.Push(packetAV.Time, dur)
var _outpkts [][]byte
if _outpkts, err = transcoder.encoder.Encode(frame); err != nil {
return
}
for _, _outpkt := range _outpkts {
if dur, err = transcoder.EncoderData.PacketDuration(_outpkt); err != nil {
err = fmt.Errorf("transcode: PacketDuration() failed for output stream #%d", packetAV.Idx)
return
}
outpkt := &av.Packet{Duration: dur, Idx: packetAV.Idx, Data: _outpkt}
outpkt.Time = transcoder.timeline.Pop(dur)
if transcoder.debug {
fmt.Println("transcode: pop", outpkt.Time, dur)
}
outpkts = append(outpkts, outpkt)
}
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment