Files

165 lines
3.8 KiB
Go

package main
import (
"errors"
"flag"
"fmt"
"log"
"strings"
"github.com/asticode/go-astiav"
)
var (
input = flag.String("i", "", "the input path")
)
type stream struct {
decCodec *astiav.Codec
decCodecContext *astiav.CodecContext
inputStream *astiav.Stream
}
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 packet
pkt := astiav.AllocPacket()
defer pkt.Free()
// Allocate frame
f := astiav.AllocFrame()
defer f.Free()
// 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
streams := make(map[int]*stream) // Indexed by input stream index
for _, is := range inputFormatContext.Streams() {
// Only process audio or video
if is.CodecParameters().MediaType() != astiav.MediaTypeAudio &&
is.CodecParameters().MediaType() != astiav.MediaTypeVideo {
continue
}
// Create stream
s := &stream{inputStream: is}
// Find decoder
if s.decCodec = astiav.FindDecoder(is.CodecParameters().CodecID()); s.decCodec == nil {
log.Fatal(errors.New("main: codec is nil"))
}
// Allocate codec context
if s.decCodecContext = astiav.AllocCodecContext(s.decCodec); s.decCodecContext == nil {
log.Fatal(errors.New("main: codec context is nil"))
}
defer s.decCodecContext.Free()
// Update codec context
if err := is.CodecParameters().ToCodecContext(s.decCodecContext); err != nil {
log.Fatal(fmt.Errorf("main: updating codec context failed: %w", err))
}
// Open codec context
if err := s.decCodecContext.Open(s.decCodec, nil); err != nil {
log.Fatal(fmt.Errorf("main: opening codec context failed: %w", err))
}
// Add stream
streams[is.Index()] = s
}
// 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()
// Get stream
s, ok := streams[pkt.StreamIndex()]
if !ok {
return false
}
// Send packet
if err := s.decCodecContext.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 := s.decCodecContext.ReceiveFrame(f); 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 f.Unref()
// Log
log.Printf("new %s frame: stream %d - pts: %d", s.inputStream.CodecParameters().MediaType(), pkt.StreamIndex(), f.Pts())
return false
}(); stop {
break
}
}
return false
}(); stop {
break
}
}
// Success
log.Println("success")
}