mirror of
https://github.com/asticode/go-astiav.git
synced 2025-09-26 20:21:15 +08:00
288 lines
7.5 KiB
Go
288 lines
7.5 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
|
|
"github.com/asticode/go-astiav"
|
|
)
|
|
|
|
var (
|
|
input = flag.String("i", "", "the input path")
|
|
)
|
|
|
|
var (
|
|
af *astiav.AudioFifo
|
|
decodedFrame *astiav.Frame
|
|
finalFrame *astiav.Frame
|
|
resampledFrame *astiav.Frame
|
|
src *astiav.SoftwareResampleContext
|
|
)
|
|
|
|
func main() {
|
|
// Handle ffmpeg logs
|
|
astiav.SetLogLevel(astiav.LogLevelDebug)
|
|
astiav.SetLogCallback(func(c astiav.Classer, l astiav.LogLevel, fmt, msg string) {
|
|
var cs string
|
|
if c != nil {
|
|
if cl := c.Class(); cl != nil {
|
|
cs = " - class: " + cl.String()
|
|
}
|
|
}
|
|
log.Printf("ffmpeg log: %s%s - level: %d\n", strings.TrimSpace(msg), cs, l)
|
|
})
|
|
|
|
// Parse flags
|
|
flag.Parse()
|
|
|
|
// Usage
|
|
if *input == "" {
|
|
log.Println("Usage: <binary path> -i <input path>")
|
|
return
|
|
}
|
|
|
|
// Allocate input format context
|
|
inputFormatContext := astiav.AllocFormatContext()
|
|
if inputFormatContext == nil {
|
|
log.Fatal(errors.New("main: input format context is nil"))
|
|
}
|
|
defer inputFormatContext.Free()
|
|
|
|
// Open input
|
|
if err := inputFormatContext.OpenInput(*input, nil, nil); err != nil {
|
|
log.Fatal(fmt.Errorf("main: opening input failed: %w", err))
|
|
}
|
|
defer inputFormatContext.CloseInput()
|
|
|
|
// Find stream info
|
|
if err := inputFormatContext.FindStreamInfo(nil); err != nil {
|
|
log.Fatal(fmt.Errorf("main: finding stream info failed: %w", err))
|
|
}
|
|
|
|
// Loop through streams
|
|
var s *astiav.Stream
|
|
var cc *astiav.CodecContext
|
|
for _, is := range inputFormatContext.Streams() {
|
|
// Only process audio
|
|
if is.CodecParameters().MediaType() != astiav.MediaTypeAudio {
|
|
continue
|
|
}
|
|
|
|
// Store stream
|
|
s = is
|
|
|
|
// Find decoder
|
|
c := astiav.FindDecoder(is.CodecParameters().CodecID())
|
|
if c == nil {
|
|
log.Fatal(errors.New("main: codec is nil"))
|
|
}
|
|
|
|
// Allocate codec context
|
|
if cc = astiav.AllocCodecContext(c); cc == nil {
|
|
log.Fatal(errors.New("main: codec context is nil"))
|
|
}
|
|
defer cc.Free()
|
|
|
|
// Update codec context
|
|
if err := is.CodecParameters().ToCodecContext(cc); err != nil {
|
|
log.Fatal(fmt.Errorf("main: updating codec context failed: %w", err))
|
|
}
|
|
|
|
// Open codec context
|
|
if err := cc.Open(c, nil); err != nil {
|
|
log.Fatal(fmt.Errorf("main: opening codec context failed: %w", err))
|
|
}
|
|
break
|
|
}
|
|
|
|
// No stream
|
|
if s == nil {
|
|
log.Fatal("main: no audio stream found")
|
|
}
|
|
|
|
// Alloc resample context
|
|
src = astiav.AllocSoftwareResampleContext()
|
|
defer src.Free()
|
|
|
|
// Allocate packet
|
|
pkt := astiav.AllocPacket()
|
|
defer pkt.Free()
|
|
|
|
// Allocate decoded frame
|
|
decodedFrame = astiav.AllocFrame()
|
|
defer decodedFrame.Free()
|
|
|
|
// Allocate resampled frame
|
|
resampledFrame = astiav.AllocFrame()
|
|
defer resampledFrame.Free()
|
|
|
|
// For the resampled frame we need to setup mandatory information
|
|
resampledFrame.SetChannelLayout(astiav.ChannelLayoutStereo)
|
|
resampledFrame.SetSampleFormat(astiav.SampleFormatFltp)
|
|
resampledFrame.SetSampleRate(24000)
|
|
|
|
// Do this only if you want to make sure the resampled frame's number of samples doesn't get
|
|
// bigger than a custom value ("200" in our case)
|
|
resampledFrame.SetNbSamples(200)
|
|
const align = 0
|
|
if err := resampledFrame.AllocBuffer(align); err != nil {
|
|
log.Fatal(fmt.Errorf("main: allocating buffer failed: %w", err))
|
|
}
|
|
|
|
// For the sake of the example we use an audio FIFO to ensure final frames have an exact constant
|
|
// number of samples except for the last one. However this is optional and it depends on your use case
|
|
finalFrame = astiav.AllocFrame()
|
|
defer finalFrame.Free()
|
|
finalFrame.SetChannelLayout(resampledFrame.ChannelLayout())
|
|
finalFrame.SetNbSamples(resampledFrame.NbSamples())
|
|
finalFrame.SetSampleFormat(resampledFrame.SampleFormat())
|
|
finalFrame.SetSampleRate(resampledFrame.SampleRate())
|
|
if err := finalFrame.AllocBuffer(align); err != nil {
|
|
log.Fatal(fmt.Errorf("main: allocating buffer failed: %w", err))
|
|
}
|
|
af = astiav.AllocAudioFifo(finalFrame.SampleFormat(), finalFrame.ChannelLayout().Channels(), finalFrame.NbSamples())
|
|
defer af.Free()
|
|
|
|
// Loop
|
|
for {
|
|
// We use a closure to ease unreferencing the packet
|
|
if stop := func() bool {
|
|
// Read frame
|
|
if err := inputFormatContext.ReadFrame(pkt); err != nil {
|
|
if errors.Is(err, astiav.ErrEof) {
|
|
return true
|
|
}
|
|
log.Fatal(fmt.Errorf("main: reading frame failed: %w", err))
|
|
}
|
|
|
|
// Make sure to unreference the packet
|
|
defer pkt.Unref()
|
|
|
|
// Invalid stream
|
|
if pkt.StreamIndex() != s.Index() {
|
|
return false
|
|
}
|
|
|
|
// Send packet
|
|
if err := cc.SendPacket(pkt); err != nil {
|
|
log.Fatal(fmt.Errorf("main: sending packet failed: %w", err))
|
|
}
|
|
|
|
// Loop
|
|
for {
|
|
// We use a closure to ease unreferencing the frame
|
|
if stop := func() bool {
|
|
// Receive frame
|
|
if err := cc.ReceiveFrame(decodedFrame); err != nil {
|
|
if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) {
|
|
return true
|
|
}
|
|
log.Fatal(fmt.Errorf("main: receiving frame failed: %w", err))
|
|
}
|
|
|
|
// Make sure to unreference the frame
|
|
defer decodedFrame.Unref()
|
|
|
|
// Log
|
|
log.Printf("new decoded frame: nb samples: %d", decodedFrame.NbSamples())
|
|
|
|
// Resample decoded frame
|
|
if err := src.ConvertFrame(decodedFrame, resampledFrame); err != nil {
|
|
log.Fatal(fmt.Errorf("main: resampling decoded frame failed: %w", err))
|
|
}
|
|
|
|
// Something was resampled
|
|
if nbSamples := resampledFrame.NbSamples(); nbSamples > 0 {
|
|
// Log
|
|
log.Printf("new resampled frame: nb samples: %d", nbSamples)
|
|
|
|
// Add resampled frame to audio fifo
|
|
if err := addResampledFrameToAudioFIFO(false); err != nil {
|
|
log.Fatal(fmt.Errorf("main: adding resampled frame to audio fifo failed: %w", err))
|
|
}
|
|
|
|
// Flush software resample context
|
|
if err := flushSoftwareResampleContext(false); err != nil {
|
|
log.Fatal(fmt.Errorf("main: flushing software resample context failed: %w", err))
|
|
}
|
|
}
|
|
return false
|
|
}(); stop {
|
|
break
|
|
}
|
|
}
|
|
return false
|
|
}(); stop {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Flush software resample context
|
|
if err := flushSoftwareResampleContext(true); err != nil {
|
|
log.Fatal(fmt.Errorf("main: flushing software resample context failed: %w", err))
|
|
}
|
|
|
|
// Success
|
|
log.Println("success")
|
|
}
|
|
|
|
func flushSoftwareResampleContext(finalFlush bool) error {
|
|
// Loop
|
|
for {
|
|
// We're making the final flush or there's enough data to flush the resampler
|
|
if finalFlush || src.Delay(int64(resampledFrame.SampleRate())) >= int64(resampledFrame.NbSamples()) {
|
|
// Flush resampler
|
|
if err := src.ConvertFrame(nil, resampledFrame); err != nil {
|
|
log.Fatal(fmt.Errorf("main: flushing resampler failed: %w", err))
|
|
}
|
|
|
|
// Nothing was resampled
|
|
if resampledFrame.NbSamples() == 0 {
|
|
break
|
|
}
|
|
|
|
// Log
|
|
log.Printf("new resampled frame: nb samples: %d", resampledFrame.NbSamples())
|
|
|
|
// Add resampled frame to audio fifo
|
|
if err := addResampledFrameToAudioFIFO(finalFlush); err != nil {
|
|
log.Fatal(fmt.Errorf("main: adding resampled frame to audio fifo failed: %w", err))
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func addResampledFrameToAudioFIFO(flush bool) error {
|
|
// Write
|
|
if resampledFrame.NbSamples() > 0 {
|
|
if _, err := af.Write(resampledFrame); err != nil {
|
|
return fmt.Errorf("main: writing failed: %w", err)
|
|
}
|
|
}
|
|
|
|
// Loop
|
|
for {
|
|
// We're flushing or there's enough data to read
|
|
if (flush && af.Size() > 0) || (!flush && af.Size() >= finalFrame.NbSamples()) {
|
|
// Read
|
|
n, err := af.Read(finalFrame)
|
|
if err != nil {
|
|
return fmt.Errorf("main: reading failed: %w", err)
|
|
}
|
|
finalFrame.SetNbSamples(n)
|
|
|
|
// Log
|
|
log.Printf("new final frame: nb samples: %d", finalFrame.NbSamples())
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
return nil
|
|
}
|