-
I'm trying the examples of transcoding, but, when I transcode mp4 to webm, I always encounter problems, I can't solve it, please help me. Thank you.🫡 The s.filterGraph.Parse method always fails This is my code: package ffmpeg
import (
"errors"
"flag"
"fmt"
"log"
"strings"
"github.com/asticode/go-astiav"
"github.com/asticode/go-astikit"
)
var (
c = astikit.NewCloser()
inputFormatContext *astiav.FormatContext
outputFormatContext *astiav.FormatContext
streams = make(map[int]*stream) // Indexed by input stream index
)
type stream struct {
buffersinkContext *astiav.BuffersinkFilterContext
buffersrcContext *astiav.BuffersrcFilterContext
decCodec *astiav.Codec
decCodecContext *astiav.CodecContext
decFrame *astiav.Frame
encCodec *astiav.Codec
encCodecContext *astiav.CodecContext
encPkt *astiav.Packet
filterFrame *astiav.Frame
filterGraph *astiav.FilterGraph
inputStream *astiav.Stream
outputStream *astiav.Stream
}
func TranscodingMp4ToWebM(inputPath, outputPath string) {
// 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()
input := flag.String("i", inputPath, "the input path")
output := flag.String("o", outputPath, "the output path")
// Usage
if *input == "" || *output == "" {
log.Println("Usage: <binary path> -i <input path> -o <output path>")
return
}
// We use an astikit.Closer to free all resources properly
defer c.Close()
// Open input file
if err := openInputFile(input); err != nil {
log.Fatal(fmt.Errorf("main: opening input file failed: %w", err))
}
// Open output file
if err := openOutputFile(output); err != nil {
log.Fatal(fmt.Errorf("main: opening output file failed: %w", err))
}
// Init filters
if err := initFilters(); err != nil {
log.Fatal(fmt.Errorf("main: initializing filters failed: %w", err))
}
// Allocate packet
pkt := astiav.AllocPacket()
c.Add(pkt.Free)
// Loop through packets
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
}
// Update packet
pkt.RescaleTs(s.inputStream.TimeBase(), s.decCodecContext.TimeBase())
// 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(s.decFrame); 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 s.decFrame.Unref()
// Filter, encode and write frame
if err := filterEncodeWriteFrame(s.decFrame, s); err != nil {
log.Fatal(fmt.Errorf("main: filtering, encoding and writing frame failed: %w", err))
}
return false
}(); stop {
break
}
}
return false
}(); stop {
break
}
}
// Loop through streams
for _, s := range streams {
// Flush filter
if err := filterEncodeWriteFrame(nil, s); err != nil {
log.Fatal(fmt.Errorf("main: filtering, encoding and writing frame failed: %w", err))
}
// Flush encoder
if err := encodeWriteFrame(nil, s); err != nil {
log.Fatal(fmt.Errorf("main: encoding and writing frame failed: %w", err))
}
}
// Write trailer
if err := outputFormatContext.WriteTrailer(); err != nil {
log.Fatal(fmt.Errorf("main: writing trailer failed: %w", err))
}
// Success
log.Println("success")
}
func openInputFile(input *string) (err error) {
// Allocate input format context
if inputFormatContext = astiav.AllocFormatContext(); inputFormatContext == nil {
err = errors.New("main: input format context is nil")
return
}
c.Add(inputFormatContext.Free)
// Open input
if err = inputFormatContext.OpenInput(*input, nil, nil); err != nil {
err = fmt.Errorf("main: opening input failed: %w", err)
return
}
c.Add(inputFormatContext.CloseInput)
// Find stream info
if err = inputFormatContext.FindStreamInfo(nil); err != nil {
err = fmt.Errorf("main: finding stream info failed: %w", err)
return
}
// Loop through streams
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 {
err = errors.New("main: codec is nil")
return
}
// Allocate codec context
if s.decCodecContext = astiav.AllocCodecContext(s.decCodec); s.decCodecContext == nil {
err = errors.New("main: codec context is nil")
return
}
c.Add(s.decCodecContext.Free)
// Update codec context
if err = is.CodecParameters().ToCodecContext(s.decCodecContext); err != nil {
err = fmt.Errorf("main: updating codec context failed: %w", err)
return
}
// Set framerate
if is.CodecParameters().MediaType() == astiav.MediaTypeVideo {
s.decCodecContext.SetFramerate(inputFormatContext.GuessFrameRate(is, nil))
}
// Open codec context
if err = s.decCodecContext.Open(s.decCodec, nil); err != nil {
err = fmt.Errorf("main: opening codec context failed: %w", err)
return
}
// Set time base
s.decCodecContext.SetTimeBase(is.TimeBase())
// Allocate frame
s.decFrame = astiav.AllocFrame()
c.Add(s.decFrame.Free)
// Store stream
streams[is.Index()] = s
}
return
}
func openOutputFile(output *string) (err error) {
// Allocate output format context
if outputFormatContext, err = astiav.AllocOutputFormatContext(nil, "", *output); err != nil {
err = fmt.Errorf("main: allocating output format context failed: %w", err)
return
} else if outputFormatContext == nil {
err = errors.New("main: output format context is nil")
return
}
c.Add(outputFormatContext.Free)
// Loop through streams
for _, is := range inputFormatContext.Streams() {
// Get stream
s, ok := streams[is.Index()]
if !ok {
continue
}
// Create output stream
if s.outputStream = outputFormatContext.NewStream(nil); s.outputStream == nil {
err = errors.New("main: output stream is nil")
return
}
// Get codec id
codecID := astiav.CodecIDVp9
if s.decCodecContext.MediaType() == astiav.MediaTypeAudio {
codecID = astiav.CodecIDOpus
}
// Find encoder
if s.encCodec = astiav.FindEncoder(codecID); s.encCodec == nil {
err = errors.New("main: codec is nil")
return
}
// Allocate codec context
if s.encCodecContext = astiav.AllocCodecContext(s.encCodec); s.encCodecContext == nil {
err = errors.New("main: codec context is nil")
return
}
c.Add(s.encCodecContext.Free)
// Update codec context
if s.decCodecContext.MediaType() == astiav.MediaTypeAudio {
if v := s.encCodec.ChannelLayouts(); len(v) > 0 {
s.encCodecContext.SetChannelLayout(v[0])
} else {
s.encCodecContext.SetChannelLayout(s.decCodecContext.ChannelLayout())
}
s.encCodecContext.SetSampleRate(s.decCodecContext.SampleRate())
if v := s.encCodec.SampleFormats(); len(v) > 0 {
s.encCodecContext.SetSampleFormat(v[0])
} else {
s.encCodecContext.SetSampleFormat(s.decCodecContext.SampleFormat())
}
// s.encCodecContext.SetSampleFormat(astiav.SampleFormatS16)
s.encCodecContext.SetTimeBase(astiav.NewRational(1, s.encCodecContext.SampleRate()))
s.encCodecContext.SetBitRate(0)
} else {
// 强制使用 YUV420P
forcedPixFmt := astiav.PixelFormatYuv420P
isSupported := false
// 检查编码器是否真的支持 YUV420P
for _, pf := range s.encCodec.PixelFormats() {
if pf == forcedPixFmt {
isSupported = true
break
}
}
if isSupported {
log.Printf("Stream %d: Forcing encoder pixel format to %s", s.inputStream.Index(), forcedPixFmt.Name())
s.encCodecContext.SetPixelFormat(forcedPixFmt)
} else {
// 如果不支持 YUV420P (理论上 VP9 应该支持), 回退到原来的逻辑
log.Printf("Stream %d: Warning - Encoder doesn't support %s, using first available.", s.inputStream.Index(), forcedPixFmt.Name())
if v := s.encCodec.PixelFormats(); len(v) > 0 {
s.encCodecContext.SetPixelFormat(v[0])
} else {
// 如果编码器没有报告支持的格式,这是个问题
log.Printf("Stream %d: Error - Encoder reports no supported pixel formats!", s.inputStream.Index())
s.encCodecContext.SetPixelFormat(s.decCodecContext.PixelFormat()) // Fallback might still fail
}
}
decSar := s.decCodecContext.SampleAspectRatio()
if decSar.Num() <= 0 || decSar.Den() <= 0 {
log.Printf("[Warning] Stream %d: Invalid SAR %d/%d detected from decoder context. Defaulting to 1/1.", s.inputStream.Index(), decSar.Num(), decSar.Den())
decSar = astiav.NewRational(1, 1) // 设置默认的有效 SAR (方形像素)
}
fmt.Printf(" SAR (DecCtx - Original): %d/%d", decSar.Num(), decSar.Den())
s.encCodecContext.SetSampleAspectRatio(decSar)
s.encCodecContext.SetTimeBase(s.decCodecContext.TimeBase())
s.encCodecContext.SetWidth(s.decCodecContext.Width())
s.encCodecContext.SetHeight(s.decCodecContext.Height())
s.encCodecContext.SetBitRate(0)
}
// Update flags
if outputFormatContext.OutputFormat().Flags().Has(astiav.IOFormatFlagGlobalheader) {
s.encCodecContext.SetFlags(s.encCodecContext.Flags().Add(astiav.CodecContextFlagGlobalHeader))
}
// Open codec context
if err = s.encCodecContext.Open(s.encCodec, nil); err != nil {
err = fmt.Errorf("main: opening codec context failed: %w", err)
return
}
// Update codec parameters
if err = s.outputStream.CodecParameters().FromCodecContext(s.encCodecContext); err != nil {
err = fmt.Errorf("main: updating codec parameters failed: %w", err)
return
}
// Update stream
s.outputStream.SetTimeBase(s.encCodecContext.TimeBase())
}
// If this is a file, we need to use an io context
if !outputFormatContext.OutputFormat().Flags().Has(astiav.IOFormatFlagNofile) {
// Open io context
var ioContext *astiav.IOContext
if ioContext, err = astiav.OpenIOContext(*output, astiav.NewIOContextFlags(astiav.IOContextFlagWrite), nil, nil); err != nil {
err = fmt.Errorf("main: opening io context failed: %w", err)
return
}
c.AddWithError(ioContext.Close)
// Update output format context
outputFormatContext.SetPb(ioContext)
}
// Write header
if err = outputFormatContext.WriteHeader(nil); err != nil {
err = fmt.Errorf("main: writing header failed: %w", err)
return
}
return
}
func initFilters() (err error) {
// Loop through output streams
for _, s := range streams {
// Allocate graph
if s.filterGraph = astiav.AllocFilterGraph(); s.filterGraph == nil {
err = errors.New("main: graph is nil")
return
}
c.Add(s.filterGraph.Free)
// Allocate outputs
outputs := astiav.AllocFilterInOut()
if outputs == nil {
err = errors.New("main: outputs is nil")
return
}
c.Add(outputs.Free)
// Allocate inputs
inputs := astiav.AllocFilterInOut()
if inputs == nil {
err = errors.New("main: inputs is nil")
return
}
c.Add(inputs.Free)
// Create buffersrc context parameters
buffersrcContextParameters := astiav.AllocBuffersrcFilterContextParameters()
defer buffersrcContextParameters.Free()
// Switch on media type
var buffersrc, buffersink *astiav.Filter
var content string
switch s.decCodecContext.MediaType() {
case astiav.MediaTypeAudio:
buffersrc = astiav.FindFilterByName("abuffer")
buffersrcContextParameters.SetChannelLayout(s.decCodecContext.ChannelLayout())
buffersrcContextParameters.SetSampleFormat(s.decCodecContext.SampleFormat())
buffersrcContextParameters.SetSampleRate(s.decCodecContext.SampleRate())
buffersrcContextParameters.SetTimeBase(s.decCodecContext.TimeBase())
buffersink = astiav.FindFilterByName("abuffersink")
// 添加 sample_rates 参数
content = fmt.Sprintf("aformat=sample_fmts=%s:sample_rates=%d:channel_layouts=%s",
s.encCodecContext.SampleFormat().Name(),
s.encCodecContext.SampleRate(), // 使用编码器期望的采样率
s.encCodecContext.ChannelLayout().String())
default:
buffersrc = astiav.FindFilterByName("buffer")
buffersrcContextParameters.SetPixelFormat(s.decCodecContext.PixelFormat())
buffersrcContextParameters.SetTimeBase(s.decCodecContext.TimeBase())
buffersrcContextParameters.SetWidth(s.decCodecContext.Width())
buffersrcContextParameters.SetHeight(s.decCodecContext.Height())
buffersrcContextParameters.SetFramerate(s.decCodecContext.Framerate())
decSar := s.decCodecContext.SampleAspectRatio()
if decSar.Num() <= 0 || decSar.Den() <= 0 {
log.Printf("[Warning] Stream %d: Invalid SAR %d/%d detected from decoder context. Defaulting to 1/1.", s.inputStream.Index(), decSar.Num(), decSar.Den())
decSar = astiav.NewRational(1, 1) // 设置默认的有效 SAR (方形像素)
}
buffersrcContextParameters.SetSampleAspectRatio(decSar)
buffersink = astiav.FindFilterByName("buffersink")
content = fmt.Sprintf("format=pix_fmts=%s", s.encCodecContext.PixelFormat().Name())
// content = fmt.Sprintf("format=pix_fmts=%s", astiav.PixelFormatYuva420P)
}
// Check filters
if buffersrc == nil {
err = errors.New("main: buffersrc is nil")
return
}
if buffersink == nil {
err = errors.New("main: buffersink is nil")
return
}
// Create filter contexts
if s.buffersrcContext, err = s.filterGraph.NewBuffersrcFilterContext(buffersrc, "in"); err != nil {
err = fmt.Errorf("main: creating buffersrc context failed: %w", err)
return
}
if s.buffersinkContext, err = s.filterGraph.NewBuffersinkFilterContext(buffersink, "out"); err != nil {
err = fmt.Errorf("main: creating buffersink context failed: %w", err)
return
}
// Set buffersrc context parameters
if err = s.buffersrcContext.SetParameters(buffersrcContextParameters); err != nil {
err = fmt.Errorf("main: setting buffersrc context parameters failed: %w", err)
return
}
// 在 initFilters 中, default case (视频), Initialize 调用之前
fmt.Println("[调试] 视频流 %d 最终检查 (Initialize 之前):", s.inputStream.Index())
fmt.Println(" Width: %d", buffersrcContextParameters.Width()) // 从参数对象获取 Width
fmt.Println(" Height: %d", buffersrcContextParameters.Height()) // 从参数对象获取 Height
fmt.Println(" PixFmt: %s (%d)", buffersrcContextParameters.PixelFormat().Name(), buffersrcContextParameters.PixelFormat()) // 获取 PixFmt
fmt.Println(" TimeBase: %d/%d", buffersrcContextParameters.TimeBase().Num(), buffersrcContextParameters.TimeBase().Den()) // 获取 TimeBase
fmt.Println(" FrameRate: %d/%d", buffersrcContextParameters.Framerate().Num(), buffersrcContextParameters.Framerate().Den()) // 获取 FrameRate
fmt.Println(" SAR: %d/%d", buffersrcContextParameters.SampleAspectRatio().Num(), buffersrcContextParameters.SampleAspectRatio().Den()) //
// Initialize buffersrc context
if err = s.buffersrcContext.Initialize(nil); err != nil {
err = fmt.Errorf("main: initializing buffersrc context failed: %w", err)
return
}
// Update outputs
outputs.SetName("in")
outputs.SetFilterContext(s.buffersrcContext.FilterContext())
outputs.SetPadIdx(0)
outputs.SetNext(nil)
// Update inputs
inputs.SetName("out")
inputs.SetFilterContext(s.buffersinkContext.FilterContext())
inputs.SetPadIdx(0)
inputs.SetNext(nil)
// fmt.Println(content)
// content = "null"
fmt.Println("Test Parse>", content)
// Parse
if err = s.filterGraph.Parse(content, inputs, outputs); err != nil {
err = fmt.Errorf("main: parsing filter failed: %w", err)
return
}
fmt.Println("Test Parse>", s.decCodecContext.MediaType())
// Configure
if err = s.filterGraph.Configure(); err != nil {
err = fmt.Errorf("main: configuring filter failed: %w", err)
return
}
// Allocate frame
s.filterFrame = astiav.AllocFrame()
c.Add(s.filterFrame.Free)
// Allocate packet
s.encPkt = astiav.AllocPacket()
c.Add(s.encPkt.Free)
}
return
}
func filterEncodeWriteFrame(f *astiav.Frame, s *stream) (err error) {
// Add frame
if err = s.buffersrcContext.AddFrame(f, astiav.NewBuffersrcFlags(astiav.BuffersrcFlagKeepRef)); err != nil {
err = fmt.Errorf("main: adding frame failed: %w", err)
return
}
// Loop
for {
// We use a closure to unreference the frame
if stop, err := func() (bool, error) {
// Get frame
if err := s.buffersinkContext.GetFrame(s.filterFrame, astiav.NewBuffersinkFlags()); err != nil {
if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) {
return true, nil
}
return false, fmt.Errorf("main: getting frame failed: %w", err)
}
// Make sure to unreference the frame
defer s.filterFrame.Unref()
// Reset picture type
s.filterFrame.SetPictureType(astiav.PictureTypeNone)
// Encode and write frame
if err := encodeWriteFrame(s.filterFrame, s); err != nil {
return false, fmt.Errorf("main: encoding and writing frame failed: %w", err)
}
return false, nil
}(); err != nil {
return err
} else if stop {
break
}
}
return
}
func encodeWriteFrame(f *astiav.Frame, s *stream) (err error) {
// Send frame
if err = s.encCodecContext.SendFrame(f); err != nil {
err = fmt.Errorf("main: sending frame failed: %w", err)
return
}
// Loop
for {
// We use a closure to ease unreferencing the packet
if stop, err := func() (bool, error) {
// Receive packet
if err := s.encCodecContext.ReceivePacket(s.encPkt); err != nil {
if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) {
return true, nil
}
return false, fmt.Errorf("main: receiving packet failed: %w", err)
}
// Make sure to unreference packet
defer s.encPkt.Unref()
// Update pkt
s.encPkt.SetStreamIndex(s.outputStream.Index())
s.encPkt.RescaleTs(s.encCodecContext.TimeBase(), s.outputStream.TimeBase())
// Write frame
if err := outputFormatContext.WriteInterleavedFrame(s.encPkt); err != nil {
return false, fmt.Errorf("main: writing frame failed: %w", err)
}
return false, nil
}(); err != nil {
return err
} else if stop {
break
}
}
return
}
|
Beta Was this translation helpful? Give feedback.
Answered by
asticode
May 9, 2025
Replies: 2 comments 14 replies
-
|
Beta Was this translation helpful? Give feedback.
3 replies
-
Do you mean you've tried with something like |
Beta Was this translation helpful? Give feedback.
11 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You're a life savior! I was looking to fix this for so long 😱
I've pushed the fix in
master
(beware you still need to freecn
in your modified code): 74973f7Let me know whether you need a tag 👍