Skip to content

Commit

Permalink
Added bit stream filter example
Browse files Browse the repository at this point in the history
  • Loading branch information
asticode committed May 15, 2024
1 parent d884495 commit f43c80d
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Examples are located in the [examples](examples) directory and mirror as much as

|name|astiav|ffmpeg|
|---|---|---|
|BitStream Filtering|[see](examples/bit_stream_filtering/main.go)|X
|Demuxing/Decoding|[see](examples/demuxing_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/demuxing_decoding.c)
|Filtering|[see](examples/filtering/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/filtering_video.c)
|Hardware Decoding|[see](examples/hardware_decoding/main.go)|[see](https://github.com/FFmpeg/FFmpeg/blob/n5.1.2/doc/examples/hw_decode.c)
Expand Down
4 changes: 0 additions & 4 deletions bit_stream_filter_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,3 @@ func (bsfc *BitStreamFilterContext) SetTimeBaseIn(r Rational) {
func (bsfc *BitStreamFilterContext) CodecParametersIn() *CodecParameters {
return newCodecParametersFromC(bsfc.c.par_in)
}

func (bsfc *BitStreamFilterContext) SetCodecParametersIn(cp *CodecParameters) {
bsfc.c.par_in = cp.c
}
9 changes: 6 additions & 3 deletions bit_stream_filter_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ func TestBitStreamFilterContext(t *testing.T) {

cp1 := AllocCodecParameters()
require.NotNil(t, cp1)
defer cp1.Free()
cp1.SetCodecID(CodecIDH264)

bsfc.SetCodecParametersIn(cp1)
require.NoError(t, cp1.Copy(bsfc.CodecParametersIn()))
require.Equal(t, CodecIDH264, bsfc.CodecParametersIn().CodecID())

// TODO: add tests for send and receive packet flows
require.NoError(t, bsfc.Initialize())

// TODO Test SendPacket
// TODO Test ReceivePacket
}
170 changes: 170 additions & 0 deletions examples/bit_stream_filtering/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package main

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

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

var (
filter = flag.String("f", "null", "the bit stream filter name")
input = flag.String("i", "", "the input path")
)

type stream struct {
bitStreamFilterContext *astiav.BitStreamFilterContext
bitStreamPkt *astiav.Packet
}

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> -f <bit stream filter name>")
return
}

// Alloc packet
pkt := astiav.AllocPacket()
defer pkt.Free()

// Alloc 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{}

// Alloc packet
s.bitStreamPkt = astiav.AllocPacket()
defer s.bitStreamPkt.Free()

// Find bit stream filter
bsf := astiav.FindBitStreamFilterByName(*filter)
if bsf == nil {
log.Fatal(errors.New("main: bit stream filter is nil"))
}

// Alloc bit stream filter context
var err error
if s.bitStreamFilterContext, err = astiav.AllocBitStreamFilterContext(bsf); err != nil {
log.Fatal(fmt.Errorf("main: allocating bit stream filter context failed: %w", err))
}
defer s.bitStreamFilterContext.Free()

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

// Update time base
s.bitStreamFilterContext.SetTimeBaseIn(is.TimeBase())

// Initialize bit stream filter context
if err := s.bitStreamFilterContext.Initialize(); err != nil {
log.Fatal(fmt.Errorf("main: initializing bit stream filter context failed: %w", err))
}

// Add stream
streams[is.Index()] = s
}

// Loop through packets
for {
// Read frame
if err := inputFormatContext.ReadFrame(pkt); err != nil {
if errors.Is(err, astiav.ErrEof) {
break
}
log.Fatal(fmt.Errorf("main: reading frame failed: %w", err))
}

// Get stream
s, ok := streams[pkt.StreamIndex()]
if !ok {
continue
}

// Filter bit stream
if err := filterBitStream(pkt, s); err != nil {
log.Fatal(fmt.Errorf("main: filtering bit stream failed: %w", err))
}
}

// Loop through streams
for _, s := range streams {
// Flush bit stream filter
if err := filterBitStream(nil, s); err != nil {
log.Fatal(fmt.Errorf("main: filtering bit stream failed: %w", err))
}
}

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

func filterBitStream(pkt *astiav.Packet, s *stream) error {
// Send packet
if err := s.bitStreamFilterContext.SendPacket(pkt); err != nil && !errors.Is(err, astiav.ErrEagain) {
return fmt.Errorf("main: sending packet failed: %w", err)
}

// Loop
for {
// Receive packet
if err := s.bitStreamFilterContext.ReceivePacket(s.bitStreamPkt); err != nil {
if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) {
break
}
return fmt.Errorf("main: receiving packet failed: %w", err)
}

// Do something with packet
log.Printf("new filtered packet: stream %d - pts: %d", s.bitStreamPkt.StreamIndex(), s.bitStreamPkt.Pts())

// Unref packet
s.bitStreamPkt.Unref()
}
return nil
}

0 comments on commit f43c80d

Please sign in to comment.