Skip to content

Commit bc5b6f0

Browse files
authored
Merge pull request #31 from mutablelogic/ffmpeg61
Changes to remove framerate
2 parents 0957e23 + c142e8e commit bc5b6f0

30 files changed

+951
-168
lines changed

cmd/examples/capture/context.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"os"
6+
"os/signal"
7+
)
8+
9+
///////////////////////////////////////////////////////////////////////////////
10+
// PUBLIC METHODS
11+
12+
// ContextForSignal returns a context object which is cancelled when a signal
13+
// is received. It returns nil if no signal parameter is provided
14+
func ContextForSignal(signals ...os.Signal) context.Context {
15+
if len(signals) == 0 {
16+
return nil
17+
}
18+
19+
ch := make(chan os.Signal, 1)
20+
ctx, cancel := context.WithCancel(context.Background())
21+
22+
// Send message on channel when signal received
23+
signal.Notify(ch, signals...)
24+
25+
// When any signal received, call cancel
26+
go func() {
27+
<-ch
28+
cancel()
29+
}()
30+
31+
// Return success
32+
return ctx
33+
}

cmd/examples/capture/main.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"image/jpeg"
6+
"log"
7+
"os"
8+
"regexp"
9+
"syscall"
10+
11+
// Packages
12+
media "github.com/mutablelogic/go-media"
13+
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
14+
)
15+
16+
var (
17+
reDeviceNamePath = regexp.MustCompile(`^([a-z][a-zA-Z0-9]+)\:(.*)$`)
18+
)
19+
20+
func main() {
21+
if len(os.Args) != 2 {
22+
log.Fatal("Usage: capture device:path")
23+
}
24+
25+
// Get the format associated with the input file
26+
device := reDeviceNamePath.FindStringSubmatch(os.Args[1])
27+
if device == nil {
28+
log.Fatal("Invalid device name, use device:path")
29+
}
30+
31+
// Create a media manager
32+
manager, err := ffmpeg.NewManager(ffmpeg.OptLog(false, nil))
33+
if err != nil {
34+
log.Fatal(err)
35+
}
36+
37+
// Find device
38+
devices := manager.Formats(media.DEVICE, device[1])
39+
if len(devices) == 0 {
40+
log.Fatalf("No devices found for %v", device[1])
41+
}
42+
if len(devices) > 1 {
43+
log.Fatalf("Multiple devices found: %q", devices)
44+
}
45+
46+
// Open device
47+
media, err := manager.Open(device[2], devices[0])
48+
if err != nil {
49+
log.Fatal(err)
50+
}
51+
defer media.Close()
52+
53+
// Tmpdir
54+
tmpdir, err := os.MkdirTemp("", "capture")
55+
if err != nil {
56+
log.Fatal(err)
57+
}
58+
59+
// Frame function
60+
frameFunc := func(stream int, frame *ffmpeg.Frame) error {
61+
w, err := os.Create(fmt.Sprintf("%v/frame-%v.jpg", tmpdir, frame.Ts()))
62+
if err != nil {
63+
return err
64+
}
65+
defer w.Close()
66+
67+
image, err := frame.Image()
68+
if err != nil {
69+
return err
70+
}
71+
72+
if err := jpeg.Encode(w, image, nil); err != nil {
73+
return err
74+
}
75+
76+
fmt.Println("Written", w.Name())
77+
78+
return nil
79+
}
80+
81+
// Map function
82+
mapFunc := func(_ int, in *ffmpeg.Par) (*ffmpeg.Par, error) {
83+
fmt.Println("Input", in)
84+
return ffmpeg.VideoPar("yuv420p", in.WidthHeight(), in.FrameRate()), nil
85+
}
86+
87+
// Receive frames
88+
if err := media.(*ffmpeg.Reader).Decode(
89+
ContextForSignal(os.Interrupt, syscall.SIGQUIT),
90+
mapFunc,
91+
frameFunc,
92+
); err != nil {
93+
log.Fatal(err)
94+
}
95+
}

cmd/examples/encode/context.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"os"
6+
"os/signal"
7+
)
8+
9+
///////////////////////////////////////////////////////////////////////////////
10+
// PUBLIC METHODS
11+
12+
// ContextForSignal returns a context object which is cancelled when a signal
13+
// is received. It returns nil if no signal parameter is provided
14+
func ContextForSignal(signals ...os.Signal) context.Context {
15+
if len(signals) == 0 {
16+
return nil
17+
}
18+
19+
ch := make(chan os.Signal, 1)
20+
ctx, cancel := context.WithCancel(context.Background())
21+
22+
// Send message on channel when signal received
23+
signal.Notify(ch, signals...)
24+
25+
// When any signal received, call cancel
26+
go func() {
27+
<-ch
28+
cancel()
29+
}()
30+
31+
// Return success
32+
return ctx
33+
}

cmd/examples/encode/main.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package main
22

33
import (
4+
"context"
5+
"errors"
46
"fmt"
57
"io"
68
"log"
79
"os"
10+
"syscall"
811

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

1417
// This example encodes an audio an video stream to a file
1518
func main() {
19+
// Check we have a filename
20+
if len(os.Args) != 2 {
21+
log.Fatal("Usage: encode filename")
22+
}
23+
1624
// Create a new file with an audio and video stream
1725
file, err := ffmpeg.Create(os.Args[1],
18-
ffmpeg.OptStream(1, ffmpeg.VideoPar("yuv420p", "1280x720", 30)),
26+
ffmpeg.OptStream(1, ffmpeg.VideoPar("yuv420p", "1280x720", 25, ffmpeg.NewMetadata("crf", 2))),
1927
ffmpeg.OptStream(2, ffmpeg.AudioPar("fltp", "mono", 22050)),
2028
)
2129
if err != nil {
@@ -39,10 +47,13 @@ func main() {
3947
}
4048
defer audio.Close()
4149

50+
// Bail out when we receive a signal
51+
ctx := ContextForSignal(os.Interrupt, syscall.SIGQUIT)
52+
4253
// Write 90 seconds, passing video and audio frames to the encoder
4354
// and returning io.EOF when the duration is reached
4455
duration := float64(90)
45-
err = file.Encode(func(stream int) (*ffmpeg.Frame, error) {
56+
err = file.Encode(ctx, func(stream int) (*ffmpeg.Frame, error) {
4657
var frame *ffmpeg.Frame
4758
switch stream {
4859
case 1:
@@ -51,12 +62,12 @@ func main() {
5162
frame = audio.Frame()
5263
}
5364
if frame != nil && frame.Ts() < duration {
54-
fmt.Print(".")
65+
fmt.Println(stream, frame.Ts())
5566
return frame, nil
5667
}
5768
return nil, io.EOF
5869
}, nil)
59-
if err != nil {
70+
if err != nil && !errors.Is(err, context.Canceled) {
6071
log.Fatal(err)
6172
}
6273
fmt.Print("\n")

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
module github.com/mutablelogic/go-media
22

3-
go 1.22
4-
5-
toolchain go1.22.4
3+
go 1.20
64

75
require (
86
github.com/alecthomas/kong v0.9.0

manager.go

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ functions to determine capabilities and manage media files and devices.
55
*/
66
package media
77

8+
import "io"
9+
810
// Manager represents a manager for media formats and devices.
911
// Create a new manager object using the NewManager function.
1012
//
@@ -26,7 +28,7 @@ type Manager interface {
2628
// Open a media file or device for reading, from a path or url.
2729
// If a format is specified, then the format will be used to open
2830
// the file. Close the media object when done.
29-
//Open(string, Format, ...string) (Media, error)
31+
Open(string, Format, ...string) (Media, error)
3032

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

50-
// Return supported devices for a given format.
51-
// Not all devices may be supported on all platforms or listed
52-
// if the device does not support enumeration.
53-
//Devices(Format) []Device
54-
5552
// Return audio parameters for encoding
5653
// ChannelLayout, SampleFormat, Samplerate
5754
//AudioParameters(string, string, int) (Parameters, error)
@@ -68,15 +65,11 @@ type Manager interface {
6865
// Codec name, Profile name, Framerate (fps) and VideoParameters
6966
//VideoCodecParameters(string, string, float64, VideoParameters) (Parameters, error)
7067

71-
// Return supported input formats which match any filter, which can be
72-
// a name, extension (with preceeding period) or mimetype. The MediaType
73-
// can be NONE (for any) or combinations of DEVICE and STREAM.
74-
//InputFormats(Type, ...string) []Format
75-
76-
// Return supported output formats which match any filter, which can be
77-
// a name, extension (with preceeding period) or mimetype. The MediaType
78-
// can be NONE (for any) or combinations of DEVICE and STREAM.
79-
//OutputFormats(Type, ...string) []Format
68+
// Return supported input and output container formats which match any filter,
69+
// which can be a name, extension (with preceeding period) or mimetype. The Type
70+
// can be a combination of DEVICE, INPUT, OUTPUT or ANY to select the right kind of
71+
// format
72+
Formats(Type, ...string) []Format
8073

8174
// Return all supported sample formats
8275
SampleFormats() []Metadata
@@ -107,3 +100,26 @@ type Manager interface {
107100
// Log info messages with arguments
108101
Infof(string, ...any)
109102
}
103+
104+
// A container format for a media file or stream
105+
type Format interface {
106+
// The type of the format, which can be combinations of
107+
// INPUT, OUTPUT, DEVICE, AUDIO, VIDEO and SUBTITLE
108+
Type() Type
109+
110+
// The unique name that the format can be referenced as
111+
Name() string
112+
113+
// Description of the format
114+
Description() string
115+
}
116+
117+
// A container format for a media file, reader, device or
118+
// network stream
119+
type Media interface {
120+
io.Closer
121+
122+
// The type of the format, which can be combinations of
123+
// INPUT, OUTPUT, DEVICE
124+
Type() Type
125+
}

pkg/ffmpeg/decoder.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import (
88

99
// Packages
1010
ff "github.com/mutablelogic/go-media/sys/ffmpeg61"
11+
12+
// Namespace imports
13+
. "github.com/djthorpe/go-errors"
1114
)
1215

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

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

120123
// Submit the packet to the decoder (nil packet will flush the decoder)
121124
if err := ff.AVCodec_send_packet(d.codec, packet); err != nil {
122-
return err
125+
return ErrInternalAppError.With("AVCodec_send_packet:", err)
123126
}
124127

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

142145
// Obtain the output frame. If a new frame is returned, it is

0 commit comments

Comments
 (0)