Skip to content

Commit

Permalink
Merge pull request #31 from mutablelogic/ffmpeg61
Browse files Browse the repository at this point in the history
Changes to remove framerate
  • Loading branch information
djthorpe authored Jul 6, 2024
2 parents 0957e23 + c142e8e commit bc5b6f0
Show file tree
Hide file tree
Showing 30 changed files with 951 additions and 168 deletions.
33 changes: 33 additions & 0 deletions cmd/examples/capture/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
}
95 changes: 95 additions & 0 deletions cmd/examples/capture/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package main

import (
"fmt"
"image/jpeg"
"log"
"os"
"regexp"
"syscall"

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

var (
reDeviceNamePath = regexp.MustCompile(`^([a-z][a-zA-Z0-9]+)\:(.*)$`)
)

func main() {
if len(os.Args) != 2 {
log.Fatal("Usage: capture device:path")
}

// Get the format associated with the input file
device := reDeviceNamePath.FindStringSubmatch(os.Args[1])
if device == nil {
log.Fatal("Invalid device name, use device:path")
}

// Create a media manager
manager, err := ffmpeg.NewManager(ffmpeg.OptLog(false, nil))
if err != nil {
log.Fatal(err)
}

// Find device
devices := manager.Formats(media.DEVICE, device[1])
if len(devices) == 0 {
log.Fatalf("No devices found for %v", device[1])
}
if len(devices) > 1 {
log.Fatalf("Multiple devices found: %q", devices)
}

// Open device
media, err := manager.Open(device[2], devices[0])
if err != nil {
log.Fatal(err)
}
defer media.Close()

// Tmpdir
tmpdir, err := os.MkdirTemp("", "capture")
if err != nil {
log.Fatal(err)
}

// Frame function
frameFunc := func(stream int, frame *ffmpeg.Frame) error {
w, err := os.Create(fmt.Sprintf("%v/frame-%v.jpg", tmpdir, frame.Ts()))
if err != nil {
return err
}
defer w.Close()

image, err := frame.Image()
if err != nil {
return err
}

if err := jpeg.Encode(w, image, nil); err != nil {
return err
}

fmt.Println("Written", w.Name())

return nil
}

// Map function
mapFunc := func(_ int, in *ffmpeg.Par) (*ffmpeg.Par, error) {
fmt.Println("Input", in)
return ffmpeg.VideoPar("yuv420p", in.WidthHeight(), in.FrameRate()), nil
}

// Receive frames
if err := media.(*ffmpeg.Reader).Decode(
ContextForSignal(os.Interrupt, syscall.SIGQUIT),
mapFunc,
frameFunc,
); err != nil {
log.Fatal(err)
}
}
33 changes: 33 additions & 0 deletions cmd/examples/encode/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
}
19 changes: 15 additions & 4 deletions cmd/examples/encode/main.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package main

import (
"context"
"errors"
"fmt"
"io"
"log"
"os"
"syscall"

// Packages
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
Expand All @@ -13,9 +16,14 @@ import (

// This example encodes an audio an video stream to a file
func main() {
// Check we have a filename
if len(os.Args) != 2 {
log.Fatal("Usage: encode filename")
}

// Create a new file with an audio and video stream
file, err := ffmpeg.Create(os.Args[1],
ffmpeg.OptStream(1, ffmpeg.VideoPar("yuv420p", "1280x720", 30)),
ffmpeg.OptStream(1, ffmpeg.VideoPar("yuv420p", "1280x720", 25, ffmpeg.NewMetadata("crf", 2))),
ffmpeg.OptStream(2, ffmpeg.AudioPar("fltp", "mono", 22050)),
)
if err != nil {
Expand All @@ -39,10 +47,13 @@ func main() {
}
defer audio.Close()

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

// Write 90 seconds, passing video and audio frames to the encoder
// and returning io.EOF when the duration is reached
duration := float64(90)
err = file.Encode(func(stream int) (*ffmpeg.Frame, error) {
err = file.Encode(ctx, func(stream int) (*ffmpeg.Frame, error) {
var frame *ffmpeg.Frame
switch stream {
case 1:
Expand All @@ -51,12 +62,12 @@ func main() {
frame = audio.Frame()
}
if frame != nil && frame.Ts() < duration {
fmt.Print(".")
fmt.Println(stream, frame.Ts())
return frame, nil
}
return nil, io.EOF
}, nil)
if err != nil {
if err != nil && !errors.Is(err, context.Canceled) {
log.Fatal(err)
}
fmt.Print("\n")
Expand Down
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
module github.com/mutablelogic/go-media

go 1.22

toolchain go1.22.4
go 1.20

require (
github.com/alecthomas/kong v0.9.0
Expand Down
46 changes: 31 additions & 15 deletions manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ functions to determine capabilities and manage media files and devices.
*/
package media

import "io"

// Manager represents a manager for media formats and devices.
// Create a new manager object using the NewManager function.
//
Expand All @@ -26,7 +28,7 @@ type Manager interface {
// Open a media file or device for reading, from a path or url.
// If a format is specified, then the format will be used to open
// the file. Close the media object when done.
//Open(string, Format, ...string) (Media, error)
Open(string, Format, ...string) (Media, error)

// Open a media stream for reading. If a format is
// specified, then the format will be used to open the file. Close the
Expand All @@ -47,11 +49,6 @@ type Manager interface {
// of the caller to also close the writer when done.
//Write(io.Writer, Format, []Metadata, ...Parameters) (Media, error)

// Return supported devices for a given format.
// Not all devices may be supported on all platforms or listed
// if the device does not support enumeration.
//Devices(Format) []Device

// Return audio parameters for encoding
// ChannelLayout, SampleFormat, Samplerate
//AudioParameters(string, string, int) (Parameters, error)
Expand All @@ -68,15 +65,11 @@ type Manager interface {
// Codec name, Profile name, Framerate (fps) and VideoParameters
//VideoCodecParameters(string, string, float64, VideoParameters) (Parameters, error)

// Return supported input formats which match any filter, which can be
// a name, extension (with preceeding period) or mimetype. The MediaType
// can be NONE (for any) or combinations of DEVICE and STREAM.
//InputFormats(Type, ...string) []Format

// Return supported output formats which match any filter, which can be
// a name, extension (with preceeding period) or mimetype. The MediaType
// can be NONE (for any) or combinations of DEVICE and STREAM.
//OutputFormats(Type, ...string) []Format
// Return supported input and output container formats which match any filter,
// which can be a name, extension (with preceeding period) or mimetype. The Type
// can be a combination of DEVICE, INPUT, OUTPUT or ANY to select the right kind of
// format
Formats(Type, ...string) []Format

// Return all supported sample formats
SampleFormats() []Metadata
Expand Down Expand Up @@ -107,3 +100,26 @@ type Manager interface {
// Log info messages with arguments
Infof(string, ...any)
}

// A container format for a media file or stream
type Format interface {
// The type of the format, which can be combinations of
// INPUT, OUTPUT, DEVICE, AUDIO, VIDEO and SUBTITLE
Type() Type

// The unique name that the format can be referenced as
Name() string

// Description of the format
Description() string
}

// A container format for a media file, reader, device or
// network stream
type Media interface {
io.Closer

// The type of the format, which can be combinations of
// INPUT, OUTPUT, DEVICE
Type() Type
}
11 changes: 7 additions & 4 deletions pkg/ffmpeg/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (

// Packages
ff "github.com/mutablelogic/go-media/sys/ffmpeg61"

// Namespace imports
. "github.com/djthorpe/go-errors"
)

////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -35,7 +38,7 @@ func NewDecoder(stream *ff.AVStream, dest *Par, force bool) (*Decoder, error) {
// Create a frame for decoder output - before resize/resample
frame := ff.AVUtil_frame_alloc()
if frame == nil {
return nil, errors.New("failed to allocate frame")
return nil, ErrInternalAppError.With("failed to allocate frame")
}

// Create a codec context for the decoder
Expand Down Expand Up @@ -114,12 +117,12 @@ func (d *Decoder) Close() error {
// correct timebase, etc set
func (d *Decoder) decode(packet *ff.AVPacket, fn DecoderFrameFn) error {
if fn == nil {
return errors.New("DecoderFrameFn is nil")
return ErrBadParameter.With("DecoderFrameFn is nil")
}

// Submit the packet to the decoder (nil packet will flush the decoder)
if err := ff.AVCodec_send_packet(d.codec, packet); err != nil {
return err
return ErrInternalAppError.With("AVCodec_send_packet:", err)
}

// get all the available frames from the decoder
Expand All @@ -136,7 +139,7 @@ func (d *Decoder) decode(packet *ff.AVPacket, fn DecoderFrameFn) error {
// Finished decoding packet or EOF
break
} else if err != nil {
return err
return ErrInternalAppError.With("AVCodec_receive_frame:", err)
}

// Obtain the output frame. If a new frame is returned, it is
Expand Down
Loading

0 comments on commit bc5b6f0

Please sign in to comment.