Skip to content

Commit

Permalink
Added custom io muxing example
Browse files Browse the repository at this point in the history
  • Loading branch information
asticode committed Nov 14, 2024
1 parent 9ca4a5e commit be9bba8
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Examples are located in the [examples](examples) directory and mirror as much as
|---|---|---|
|BitStream Filtering|[see](examples/bit_stream_filtering/main.go)|X
|Custom IO Demuxing|[see](examples/custom_io_demuxing/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/avio_reading.c)
|Custom IO Muxing|[see](examples/custom_io_muxing/main.go)|X
|Demuxing/Decoding|[see](examples/demuxing_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/demuxing_decoding.c)
|Filtering|[see](examples/filtering/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n7.0/doc/examples/filtering_video.c)
|Frame data manipulation|[see](examples/frame_data_manipulation/main.go)|X
Expand Down
185 changes: 185 additions & 0 deletions examples/custom_io_muxing/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package main

import (
"errors"
"flag"
"fmt"
"log"
"os"
"strings"

"github.com/asticode/go-astiav"
)

var (
input = flag.String("i", "", "the input path")
output = flag.String("o", "", "the output path")
)

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 == "" || *output == "" {
log.Println("Usage: <binary path> -i <input path> -o <output path>")
return
}

// Allocate packet
pkt := astiav.AllocPacket()
defer pkt.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))
}

// Allocate output format context
outputFormatContext, err := astiav.AllocOutputFormatContext(nil, "mp4", "")
if err != nil {
log.Fatal(fmt.Errorf("main: allocating output format context failed: %w", err))
}
if outputFormatContext == nil {
log.Fatal(errors.New("main: output format context is nil"))
}
defer outputFormatContext.Free()

// Open file
f, err := os.Create(*output)
if err != nil {
log.Fatal(fmt.Errorf("main: creating %s failed: %w", *output, err))
}
defer f.Close()

// Allocate io context
ioContext, err := astiav.AllocIOContext(
4096,
true,
nil,
func(offset int64, whence int) (n int64, err error) {
return f.Seek(offset, whence)
},
func(b []byte) (n int, err error) {
return f.Write(b)
},
)
if err != nil {
log.Fatal(fmt.Errorf("main: allocating io context failed: %w", err))
}
defer ioContext.Free()

// Store io context
outputFormatContext.SetPb(ioContext)

// Loop through streams
inputStreams := make(map[int]*astiav.Stream) // Indexed by input stream index
outputStreams := make(map[int]*astiav.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
}

// Add input stream
inputStreams[is.Index()] = is

// Add stream to output format context
os := outputFormatContext.NewStream(nil)
if os == nil {
log.Fatal(errors.New("main: output stream is nil"))
}

// Copy codec parameters
if err = is.CodecParameters().Copy(os.CodecParameters()); err != nil {
log.Fatal(fmt.Errorf("main: copying codec parameters failed: %w", err))
}

// Reset codec tag
os.CodecParameters().SetCodecTag(0)

// Add output stream
outputStreams[is.Index()] = os
}

// Write header
if err = outputFormatContext.WriteHeader(nil); err != nil {
log.Fatal(fmt.Errorf("main: writing header failed: %w", err))
}

// Loop through packets
for {
// We use a closure to ease unreferencing 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 packet
defer pkt.Unref()

// Get input stream
inputStream, ok := inputStreams[pkt.StreamIndex()]
if !ok {
return false
}

// Get output stream
outputStream, ok := outputStreams[pkt.StreamIndex()]
if !ok {
return false
}

// Update packet
pkt.SetStreamIndex(outputStream.Index())
pkt.RescaleTs(inputStream.TimeBase(), outputStream.TimeBase())
pkt.SetPos(-1)

// Write frame
if err = outputFormatContext.WriteInterleavedFrame(pkt); err != nil {
log.Fatal(fmt.Errorf("main: writing interleaved frame failed: %w", err))
}
return false
}(); stop {
break
}
}

// Write trailer
if err = outputFormatContext.WriteTrailer(); err != nil {
log.Fatal(fmt.Errorf("main: writing trailer failed: %w", err))
}

// Success
log.Println("success")
}

0 comments on commit be9bba8

Please sign in to comment.