Skip to content

Documentation, Encode and Decode #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 60 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,74 @@

# go-media

This module provides an interface for media services, including:
This module provides an interface for media services, mostly based on bindings
for [FFmpeg](https://ffmpeg.org/). It is designed to be used in a pipeline
for processing media files, and is not a standalone application.

* Bindings in golang for [FFmpeg 7.1](https://ffmpeg.org/);
* Opening media files, devices and network sockets for reading and writing;
* Retrieving metadata and artwork from audio and video media;
* Re-multiplexing media files from one format to another;
* Fingerprinting audio files to identify music.
You'd want to use this module if you want to integrate media processing into an
existing pipeline, not necessarily to build a standalone application, where
you can already use a command like [FFmpeg](https://ffmpeg.org/) or
[GStreamer](https://gstreamer.freedesktop.org/).

## Current Status

This module is currently in development and subject to change. If there are any specific features
you are interested in, please see below "Contributing & Distribution" below.

## What do you want to do?

Here is some examples of how you might want to use this module:

| Use Case | Examples |
|----------|-----------------|
| Use low-level bindings in golang for [FFmpeg 7.1](https://ffmpeg.org/) | [here]() |
| Opening media files, devices and network sockets for reading and writing | [here]() |
| Retrieving metadata, artwork or thumbnails from audio and video media | [here]() |
| Re-multiplexing media files from one format to another | [here]() |
| Encoding and decoding audio, video and subtitle streams | [here]() |
| Resampling audio and resizing video streams | [here]() |
| Applying filters and effects to audio and video streams | [here]() |
| Fingerprinting audio files to identify music | [here]() |
| Creating an audio or video player | [here]() |

## Requirements

If you're building for docker, then you can simply run the following command. This creates a docker
image with all the dependencies installed.
There are two ways to satisfy the dependencies on FFmpeg:

```bash
DOCKER_REGISTRY=docker.io/user make docker
```
1. The module is based on [FFmpeg 7.1](https://ffmpeg.org/) and requires you to have installed the libraries
and headers for FFmpeg. You can install the libraries using your package manager.
2. The module can download the source code for FFmpeg and build static libraries and headers
for you. This is done using the `make` command.

However, it's more likely that you want to build the bindings. To do so, compile the FFmpeg libraries
first:
Either way, in order to integrate the module into your golang code, you need to have satisfied these
dependencies and use a specific set of flags to compile your code.

### Building FFmpeg

To build FFmpeg, you need to have a compiler, nasm, pkg-config and make.

#### Debian/Ubuntu

```bash
# Debian/Ubuntu
apt install libfreetype-dev libmp3lame-dev libopus-dev libvorbis-dev libvpx-dev libx264-dev libx265-dev libnuma-dev
# Required
apt install \
build-essential cmake nasm curl

# Optional
apt install \
libfreetype-dev libmp3lame-dev libopus-dev libvorbis-dev libvpx-dev \
libx264-dev libx265-dev libnuma-dev

# Make ffmpeg
git clone github.com/mutablelogic/go-media
cd go-media
make ffmpeg
```

#### Fedora

TODO

```bash
# Fedora
dnf install freetype-devel lame-devel opus-devel libvorbis-devel libvpx-devel x264-devel x265-devel numactl-devel
Expand All @@ -42,6 +77,11 @@ cd go-media
make ffmpeg
```


#### MacOS Homebrew

TODO

```bash
# Homebrew
brew install freetype lame opus libvorbis libvpx x264 x265
Expand All @@ -51,15 +91,18 @@ make ffmpeg
```

This will place the static libraries in the `build/install` folder which you can refer to when compiling your
golang code. For example, here's a typical compile or run command on a Mac:
golang code.

## Linking to FFmpeg

For example, here's a typical compile or run command on a Mac:

```bash
PKG_CONFIG_PATH="${PWD}/build/install/lib/pkgconfig" \
LD_LIBRARY_PATH="/opt/homebrew/lib" \
CGO_LDFLAGS_ALLOW="-(W|D).*" \
go build -o build/media ./cmd/media
```

### Demultiplexing

```go
Expand Down
33 changes: 0 additions & 33 deletions cmd/examples/encode/context.go

This file was deleted.

4 changes: 3 additions & 1 deletion cmd/examples/encode/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"log"
"os"
"os/signal"
"syscall"

// Packages
Expand Down Expand Up @@ -48,7 +49,8 @@ func main() {
defer audio.Close()

// Bail out when we receive a signal
ctx := ContextForSignal(os.Interrupt, syscall.SIGQUIT)
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGQUIT)
defer cancel()

// Write 90 seconds, passing video and audio frames to the encoder
// and returning io.EOF when the duration is reached
Expand Down
97 changes: 97 additions & 0 deletions cmd/media/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package main

import (
"fmt"
"io"
"time"

// Packages
media "github.com/mutablelogic/go-media"
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
generator "github.com/mutablelogic/go-media/pkg/generator"
server "github.com/mutablelogic/go-server"
)

///////////////////////////////////////////////////////////////////////////////
// TYPES

type EncodeCommands struct {
EncodeTest EncodeTest `cmd:"" group:"TRANSCODE" help:"Encode a test file"`
}

type EncodeTest struct {
Out string `arg:"" type:"path" help:"Output filename"`
Duration time.Duration `help:"Duration of the test file"`
}

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

func (cmd *EncodeTest) Run(app server.Cmd) error {
// Create a manager
manager, err := ffmpeg.NewManager()
if err != nil {
return err
}

// TODO: Guess the output format
formats := manager.Formats(media.OUTPUT, cmd.Out)
if len(formats) == 0 {
return media.ErrBadParameter.With("unable to guess the output format for %q", cmd.Out)
}
fmt.Println(formats)

// Streams
streams := []media.Par{
manager.MustVideoPar("yuv420p", 1280, 720, 25),
manager.MustAudioPar("fltp", "mono", 22050),
}

// Create a writer with two streams
writer, err := manager.Create(cmd.Out, nil, nil, streams...)
if err != nil {
return err
}
defer writer.Close()

// Make an video generator which can generate frames with the same parameters as the video stream
video, err := generator.NewEBU(writer.(*ffmpeg.Writer).Stream(1).Par())
if err != nil {
return err
}
defer video.Close()

// Make an audio generator which can generate a 1KHz tone
// at -5dB with the same parameters as the audio stream
audio, err := generator.NewSine(1000, -5, writer.(*ffmpeg.Writer).Stream(2).Par())
if err != nil {
return err
}
defer audio.Close()

// Write until CTRL+C or duration is reached
var ts uint
manager.Errorf("Press CTRL+C to stop encoding\n")
return manager.Encode(app.Context(), writer, func(stream int) (media.Frame, error) {
var frame *ffmpeg.Frame
switch stream {
case 1:
frame = video.Frame()
case 2:
frame = audio.Frame()
}

// Print the timestamp in seconds
if newts := uint(frame.Ts()); newts != ts {
ts = newts
manager.Errorf("Writing frame at %s\r", time.Duration(ts)*time.Second)
}

// Check for end of stream
if cmd.Duration == 0 || frame.Ts() < cmd.Duration.Seconds() {
return frame, nil
} else {
return frame, io.EOF
}
})
}
1 change: 1 addition & 0 deletions cmd/media/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type CLI struct {
CodecCommands
FingerprintCommands
VersionCommands
EncodeCommands
}

///////////////////////////////////////////////////////////////////////////////
Expand Down
37 changes: 28 additions & 9 deletions manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,19 @@ type Manager interface {
// of the caller to also close the writer when done.
//Write(io.Writer, Format, []Metadata, ...Par) (Media, error)

// Return audio parameters for encoding
// ChannelLayout, SampleFormat, Samplerate
//AudioPar(string, string, int) (Par, error)
// Return audio parameters for encoding with SampleFormat, ChannelLayout, Samplerate
AudioPar(string, string, uint) (Par, error)

// Return video parameters for encoding
// Width, Height, PixelFormat
//VideoPar(int, int, string) (Par, error)
// Return audio parameters for encoding with SampleFormat, ChannelLayout, Samplerate,
// panics on error
MustAudioPar(string, string, uint) Par

// Return video parameters for encoding with PixelFormat, Width, Height, Framerate
VideoPar(string, uint, uint, float64) (Par, error)

// Return video parameters for encoding with PixelFormat, Width, Height, Framerate,
// panics on error
MustVideoPar(string, uint, uint, float64) Par

// Return codec parameters for audio encoding
// Codec name and AudioParameters
Expand Down Expand Up @@ -102,7 +108,10 @@ type Manager interface {
// Decode an input stream, determining the streams to be decoded
// and the function to accept the decoded frames. If MapFunc is nil,
// all streams are passed through (demultiplexing).
Decode(context.Context, Media, MapFunc, FrameFunc) error
Decode(context.Context, Media, MapFunc, DecodeFrameFunc) error

// Encode an output stream
Encode(context.Context, Media, EncodeFrameFn) error
}

// MapFunc return parameters if a stream should be decoded,
Expand All @@ -113,10 +122,17 @@ type MapFunc func(int, Par) (Par, error)

// FrameFunc is a function which is called to send a frame after decoding. It should
// return nil to continue decoding or io.EOF to stop.
type FrameFunc func(int, Frame) error
type DecodeFrameFunc func(int, Frame) error

// EncodeFrameFn is a function which is called to receive a frame to encode. It should
// return nil to continue encoding or io.EOF to stop encoding.
type EncodeFrameFn func(int) (Frame, error)

// Parameters for a stream or frame
type Par interface{}
type Par interface {
// The type of the parameters, which can be AUDIO, VIDEO or SUBTITLE
Type() Type
}

// A frame of decoded data
type Frame interface{}
Expand All @@ -132,6 +148,9 @@ type Format interface {

// Description of the format
Description() string

// Return AUDIO, VIDEO or SUBTITLE codec parameters
CodecPar(Type) Par
}

// A container format for a media file, reader, device or
Expand Down
11 changes: 11 additions & 0 deletions pkg/ffmpeg/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,14 @@ func (f *Format) Description() string {
return f.metaFormat.Name
}
}

// Return AUDIO, VIDEO or SUBTITLE codec parameters
func (f *Format) CodecPar(t media.Type) media.Par {
switch {
case f.Output != nil:
// TODO
case f.Input != nil:
// TODO
}
return nil
}
Loading
Loading