Skip to content

Commit

Permalink
Merge pull request #32 from mutablelogic/ffmpeg61
Browse files Browse the repository at this point in the history
Ffmpeg61
  • Loading branch information
djthorpe authored Jul 29, 2024
2 parents bc5b6f0 + d14921d commit b02b78a
Show file tree
Hide file tree
Showing 26 changed files with 1,156 additions and 344 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ BUILD_DIR := "build"
CMD_DIR := $(filter-out cmd/ffmpeg/README.md, $(wildcard cmd/ffmpeg/*))
BUILD_TAG := ${DOCKER_REGISTRY}/go-media-${OS}-${ARCH}:${VERSION}

all: clean cli cmds
all: clean cmds

cmds: $(CMD_DIR)

Expand Down
38 changes: 35 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,46 @@ you are interested in, please see below "Contributing & Distribution" below.

## Requirements

In order to build the examples, you'll need the library and header files for [FFmpeg 6](https://ffmpeg.org/download.html) installed.The `chromaprint` library is also required for fingerprinting audio files. On Macintosh with [homebrew](http://bew.sh/), for example:
In order to build the examples, you'll need the library and header files
for [FFmpeg 6](https://ffmpeg.org/download.html) installed.The `chromaprint` library is also
required for fingerprinting audio files and SDL2 for the video player.

### MacOS

On Macintosh with [homebrew](http://bew.sh/), for example:

```bash
brew install ffmpeg@6 chromaprint make
brew link ffmpeg@6
```

### Debian

If you're using Debian you may not be able to get the ffmpeg 6 unless you first of all add the debi-multimedia repository.
You can do this by adding the following line to your `/etc/apt/sources.list` file:

```bash
# Run commands as privileged user
echo "deb https://www.deb-multimedia.org $(lsb_release -sc) main" >> /etc/apt/sources.list
apt update -y -oAcquire::AllowInsecureRepositories=true
apt install -y --force-yes deb-multimedia-keyring
```

Then you can proceed to install the ffmpeg 6 and the other dependencies:

```bash
# Run commands as privileged user
apt install -y libavcodec-dev libavdevice-dev libavfilter-dev libavutil-dev libswscale-dev libswresample-dev
apt install -y libchromaprint-dev
apt install -y libsdl2-dev
```

### Docker Container

TODO

## Examples

There are some examples in the `cmd` folder of the main repository on how to use
the package. The various make targets are:

Expand All @@ -43,8 +77,6 @@ cd go-media
DOCKER_REGISTRY=ghcr.io/mutablelogic make docker
```

## Examples

There are a variety of types of object needed as part of media processing.
All examples require a `Manager` to be created, which is used to enumerate all supported formats
and open media files and byte streams.
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
33 changes: 33 additions & 0 deletions cmd/examples/sdlplayer/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"context"
"os"
"os/signal"
)

///////////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS

// ContextForSignal returns a context object which is cancelled when a signal
// is received. It returns nil if no signal parameter is provided
func ContextForSignal(signals ...os.Signal) context.Context {
if len(signals) == 0 {
return nil
}

ch := make(chan os.Signal, 1)
ctx, cancel := context.WithCancel(context.Background())

// Send message on channel when signal received
signal.Notify(ch, signals...)

// When any signal received, call cancel
go func() {
<-ch
cancel()
}()

// Return success
return ctx
}
36 changes: 36 additions & 0 deletions cmd/examples/sdlplayer/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* This example demonstrates how to play audio and video files using SDL2. */
package main

import (
"errors"
"fmt"
"os"
"syscall"
)

func main() {
// Bail out when we receive a signal
ctx := ContextForSignal(os.Interrupt, syscall.SIGQUIT)

// Create a player object
player := NewPlayer()
defer player.Close()

// Open the file
var result error
if len(os.Args) == 2 {
result = player.OpenUrl(os.Args[1])
} else {
result = errors.New("usage: sdlplayer <filename>")
}
if result != nil {
fmt.Fprintln(os.Stderr, result)
os.Exit(-1)
}

// Play
if err := player.Play(ctx); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(-1)
}
}
212 changes: 212 additions & 0 deletions cmd/examples/sdlplayer/player.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package main

import (

// Packages
"context"
"errors"
"fmt"
"os"
"sync"
"unsafe"

"github.com/mutablelogic/go-media/pkg/ffmpeg"
"github.com/mutablelogic/go-media/pkg/sdl"

// Namespace imports
. "github.com/mutablelogic/go-media"
)

type Player struct {
input *ffmpeg.Reader
ctx *ffmpeg.Context
audio *ffmpeg.Par
video *ffmpeg.Par
videoevent uint32
audioevent uint32
}

func NewPlayer() *Player {
return &Player{}
}

func (p *Player) Close() error {
var result error

// Close resources
if p.ctx != nil {
result = errors.Join(result, p.ctx.Close())
}
if p.input != nil {
result = errors.Join(result, p.input.Close())
}

// Return any errors
return result
}

func (p *Player) OpenUrl(url string) error {
input, err := ffmpeg.Open(url)
if err != nil {
return err
}
p.input = input

// Map input streams - find best audio and video streams
ctx, err := p.input.Map(func(stream int, par *ffmpeg.Par) (*ffmpeg.Par, error) {
if stream == p.input.BestStream(VIDEO) {
p.video = par
return par, nil
} else if stream == p.input.BestStream(AUDIO) {
p.audio = par
return par, nil
} else {
return nil, nil
}
})
if err != nil {
return err
} else {
p.ctx = ctx
p.input = input
}
return nil
}

func (p *Player) Type() Type {
t := NONE
if p.video != nil {
t |= VIDEO
}
if p.audio != nil {
t |= AUDIO
}
return t
}

// Return media title
func (p *Player) Title() string {
title := p.input.Metadata("title")
if len(title) > 0 {
return title[0].Value()
}
return fmt.Sprint(p.Type())
}

func (p *Player) Play(ctx context.Context) error {
var window *sdl.Window
var audio *sdl.Audio

// Create a new SDL context
sdl, err := sdl.New(p.Type())
if err != nil {
return err
}
defer sdl.Close()

// Create a window for video
if p.video != nil {
if w, err := sdl.NewVideo(p.Title(), p.video); err != nil {
return err
} else {
window = w
}
defer window.Close()

// Register a method to push video rendering
p.videoevent = sdl.Register(func(frame unsafe.Pointer) {
var result error
frame_ := (*ffmpeg.Frame)(frame)
if err := window.RenderFrame(frame_); err != nil {
result = errors.Join(result, err)
}
if err := window.Flush(); err != nil {
result = errors.Join(result, err)
}
if err := frame_.Close(); err != nil {
result = errors.Join(result, err)
}
if result != nil {
fmt.Fprintln(os.Stderr, result)
}
/*
// Pause to present the frame at the correct PTS
if pts != ffmpeg.TS_UNDEFINED && pts < frame.Ts() {
pause := frame.Ts() - pts
if pause > 0 {
sdl.Delay(uint32(pause * 1000))
}
}
// Set current timestamp
pts = frame.Ts()
// Render the frame, release the frame resources
if err := w.RenderFrame(frame); err != nil {
log.Print(err)
} else if err := w.Flush(); err != nil {
log.Print(err)
} else if err := frame.Close(); err != nil {
log.Print(err)
}
*/
})
}

// Create audio
if p.audio != nil {
if a, err := sdl.NewAudio(p.audio); err != nil {
return err
} else {
audio = a
}
defer audio.Close()

// Register a method to push audio rendering
p.audioevent = sdl.Register(func(frame unsafe.Pointer) {
//frame_ := (*ffmpeg.Frame)(frame)
//fmt.Println("TODO: Audio", frame_)
})
}

// Start go routine to decode the audio and video frames
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
if err := p.decode(ctx, sdl); err != nil {
fmt.Fprintln(os.Stderr, err)
}
}()

// Run loop with events for audio and video
var result error
if err := sdl.Run(ctx); err != nil {
result = err
}

// Wait for go routines to finish
wg.Wait()

// Return any errors
return result
}

// Goroutine decoder
func (p *Player) decode(ctx context.Context, sdl *sdl.Context) error {
return p.input.DecodeWithContext(ctx, p.ctx, func(stream int, frame *ffmpeg.Frame) error {
if frame.Type().Is(VIDEO) {
if copy, err := frame.Copy(); err != nil {
fmt.Println("Unable to make a frame copy: ", err)
} else {
// TODO: Make a copy of the frame
sdl.Post(p.videoevent, unsafe.Pointer(copy))
}
}
if frame.Type().Is(AUDIO) {
// TODO: Make a copy of the frame
sdl.Post(p.audioevent, unsafe.Pointer(frame))
}
return nil
})
}
33 changes: 33 additions & 0 deletions cmd/examples/transcode/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"context"
"os"
"os/signal"
)

///////////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS

// ContextForSignal returns a context object which is cancelled when a signal
// is received. It returns nil if no signal parameter is provided
func ContextForSignal(signals ...os.Signal) context.Context {
if len(signals) == 0 {
return nil
}

ch := make(chan os.Signal, 1)
ctx, cancel := context.WithCancel(context.Background())

// Send message on channel when signal received
signal.Notify(ch, signals...)

// When any signal received, call cancel
go func() {
<-ch
cancel()
}()

// Return success
return ctx
}
Loading

0 comments on commit b02b78a

Please sign in to comment.