Skip to content

Commit 80efb24

Browse files
committed
Updated
1 parent 80bf31d commit 80efb24

File tree

11 files changed

+202
-80
lines changed

11 files changed

+202
-80
lines changed

pkg/ffmpeg/opts.go

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -50,33 +50,11 @@ func OptOutputFormat(name string) Opt {
5050
}
5151
}
5252

53-
// New audio stream with parameters
54-
func OptAudioStream(stream int, par *Par) Opt {
53+
// New stream with parameters
54+
func OptStream(stream int, par *Par) Opt {
5555
return func(o *opts) error {
56-
if par == nil || par.CodecType() != ffmpeg.AVMEDIA_TYPE_AUDIO {
57-
return ErrBadParameter.With("invalid audio parameters")
58-
}
59-
if stream == 0 {
60-
stream = len(o.streams) + 1
61-
}
62-
if _, exists := o.streams[stream]; exists {
63-
return ErrDuplicateEntry.Withf("stream %v", stream)
64-
}
65-
if stream < 0 {
66-
return ErrBadParameter.Withf("invalid stream %v", stream)
67-
}
68-
o.streams[stream] = par
69-
70-
// Return success
71-
return nil
72-
}
73-
}
74-
75-
// New video stream with parameters
76-
func OptVideoStream(stream int, par *Par) Opt {
77-
return func(o *opts) error {
78-
if par == nil || par.CodecType() != ffmpeg.AVMEDIA_TYPE_VIDEO {
79-
return ErrBadParameter.With("invalid video parameters")
56+
if par == nil {
57+
return ErrBadParameter.With("invalid parameters")
8058
}
8159
if stream == 0 {
8260
stream = len(o.streams) + 1

pkg/ffmpeg/par.go

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package ffmpeg
22

33
import (
44
"encoding/json"
5-
"fmt"
65
"slices"
76

7+
// Packages
88
ff "github.com/mutablelogic/go-media/sys/ffmpeg61"
99

1010
// Namespace imports
@@ -51,7 +51,7 @@ func NewAudioPar(samplefmt string, channellayout string, samplerate int) (*Par,
5151
return par, nil
5252
}
5353

54-
func NewVideoPar(pixfmt string, size string) (*Par, error) {
54+
func NewVideoPar(pixfmt string, size string, framerate float64) (*Par, error) {
5555
par := new(Par)
5656
par.SetCodecType(ff.AVMEDIA_TYPE_VIDEO)
5757

@@ -70,6 +70,13 @@ func NewVideoPar(pixfmt string, size string) (*Par, error) {
7070
par.SetHeight(h)
7171
}
7272

73+
// Frame rate
74+
if framerate <= 0 {
75+
return nil, ErrBadParameter.Withf("negative or zero framerate %v", framerate)
76+
} else {
77+
par.SetFramerate(ff.AVUtil_rational_d2q(framerate, 1<<24))
78+
}
79+
7380
// Set default sample aspect ratio
7481
par.SetSampleAspectRatio(ff.AVUtil_rational(1, 1))
7582

@@ -85,8 +92,8 @@ func AudioPar(samplefmt string, channellayout string, samplerate int) *Par {
8592
}
8693
}
8794

88-
func VideoPar(pixfmt string, size string) *Par {
89-
if par, err := NewVideoPar(pixfmt, size); err != nil {
95+
func VideoPar(pixfmt string, size string, framerate float64) *Par {
96+
if par, err := NewVideoPar(pixfmt, size, framerate); err != nil {
9097
panic(err)
9198
} else {
9299
return par
@@ -199,11 +206,61 @@ func (ctx *Par) validateAudioCodec(codec *ff.AVCodecContext) error {
199206
}
200207

201208
func (ctx *Par) copyVideoCodec(codec *ff.AVCodecContext) error {
202-
fmt.Println("TODO: copyVideoCodec")
209+
codec.SetPixFmt(ctx.PixelFormat())
210+
codec.SetWidth(ctx.Width())
211+
codec.SetHeight(ctx.Height())
212+
codec.SetSampleAspectRatio(ctx.SampleAspectRatio())
213+
codec.SetFramerate(ctx.Framerate())
214+
codec.SetTimeBase(ff.AVUtil_rational_invert(ctx.Framerate()))
203215
return nil
204216
}
205217

206218
func (ctx *Par) validateVideoCodec(codec *ff.AVCodecContext) error {
207-
fmt.Println("TODO: validateVideoCodec")
219+
pixelformats := codec.Codec().PixelFormats()
220+
framerates := codec.Codec().SupportedFramerates()
221+
222+
// First we set params from the codec which are not already set
223+
if ctx.PixelFormat() == ff.AV_PIX_FMT_NONE {
224+
if len(pixelformats) > 0 {
225+
ctx.SetPixelFormat(pixelformats[0])
226+
}
227+
}
228+
if ctx.Framerate().Num() == 0 || ctx.Framerate().Den() == 0 {
229+
if len(framerates) > 0 {
230+
ctx.SetFramerate(framerates[0])
231+
}
232+
}
233+
234+
// Then we check to make sure the parameters are compatible with
235+
// the codec
236+
if len(pixelformats) > 0 {
237+
if !slices.Contains(pixelformats, ctx.PixelFormat()) {
238+
return ErrBadParameter.Withf("unsupported pixel format %v", ctx.PixelFormat())
239+
}
240+
} else if ctx.PixelFormat() == ff.AV_PIX_FMT_NONE {
241+
return ErrBadParameter.With("pixel format not set")
242+
}
243+
if ctx.Width() == 0 || ctx.Height() == 0 {
244+
return ErrBadParameter.Withf("invalid width %v or height %v", ctx.Width(), ctx.Height())
245+
}
246+
if ctx.SampleAspectRatio().Num() == 0 || ctx.SampleAspectRatio().Den() == 0 {
247+
ctx.SetSampleAspectRatio(ff.AVUtil_rational(1, 1))
248+
}
249+
if ctx.Framerate().Num() == 0 || ctx.Framerate().Den() == 0 {
250+
return ErrBadParameter.With("framerate not set")
251+
} else if len(framerates) > 0 {
252+
valid := false
253+
for _, fr := range framerates {
254+
if ff.AVUtil_rational_equal(fr, ctx.Framerate()) {
255+
valid = true
256+
break
257+
}
258+
}
259+
if !valid {
260+
return ErrBadParameter.Withf("unsupported framerate %v", ctx.Framerate())
261+
}
262+
}
263+
264+
// Return success
208265
return nil
209266
}

pkg/ffmpeg/par_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func Test_par_001(t *testing.T) {
2020
func Test_par_002(t *testing.T) {
2121
assert := assert.New(t)
2222

23-
par, err := ffmpeg.NewVideoPar("yuv420p", "1280x720")
23+
par, err := ffmpeg.NewVideoPar("yuv420p", "1280x720", 25)
2424
if !assert.NoError(err) {
2525
t.FailNow()
2626
}

pkg/ffmpeg/rescaler_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func Test_rescaler_001(t *testing.T) {
1717
assert := assert.New(t)
1818

1919
// Create an image generator
20-
image, err := generator.NewYUV420P("vga", 25)
20+
image, err := generator.NewYUV420P(25, ffmpeg.VideoPar("yuv420p", "1280x720", 25))
2121
if !assert.NoError(err) {
2222
t.FailNow()
2323
}
@@ -53,7 +53,7 @@ func Test_rescaler_002(t *testing.T) {
5353
assert := assert.New(t)
5454

5555
// Create an image generator
56-
image, err := generator.NewYUV420P("vga", 25)
56+
image, err := generator.NewYUV420P(25, ffmpeg.VideoPar("yuva420p", "1280x720", 25))
5757
if !assert.NoError(err) {
5858
t.FailNow()
5959
}
@@ -120,7 +120,5 @@ func Test_rescaler_002(t *testing.T) {
120120
t.FailNow()
121121
}
122122
t.Logf("Wrote %s", tmpfile)
123-
124123
}
125-
126124
}

pkg/ffmpeg/writer.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,20 @@ func NewWriter(w io.Writer, opt ...Opt) (*Writer, error) {
8484
}
8585
}
8686

87-
// Check output
87+
// Try once more to get the output format
8888
var filename string
89+
if options.oformat == nil {
90+
if w_, ok := w.(*os.File); ok {
91+
filename = w_.Name()
92+
if err := OptOutputFormat(filename)(options); err != nil {
93+
return nil, err
94+
}
95+
}
96+
}
97+
98+
// Bail out
8999
if options.oformat == nil {
90100
return nil, ErrBadParameter.Withf("invalid output format")
91-
} else if w_, ok := w.(*os.File); ok {
92-
filename = w_.Name()
93101
}
94102

95103
// Allocate the AVIO context

pkg/ffmpeg/writer_test.go

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func Test_writer_001(t *testing.T) {
2626
writer, err := ffmpeg.NewWriter(w,
2727
ffmpeg.OptOutputFormat(w.Name()),
2828
ffmpeg.OptMetadata(ffmpeg.NewMetadata("title", t.Name())),
29-
ffmpeg.OptAudioStream(1, ffmpeg.AudioPar("fltp", "mono", 22050)),
29+
ffmpeg.OptStream(1, ffmpeg.AudioPar("fltp", "mono", 22050)),
3030
)
3131
if !assert.NoError(err) {
3232
t.FailNow()
@@ -40,8 +40,8 @@ func Test_writer_001(t *testing.T) {
4040
}
4141
defer audio.Close()
4242

43-
// Write frames
44-
duration := 1000 * time.Minute
43+
// Write 15 mins of frames
44+
duration := 60 * time.Minute
4545
assert.NoError(writer.Encode(func(stream int) (*ff.AVFrame, error) {
4646
frame := audio.Frame()
4747
if frame.Time() >= duration {
@@ -72,7 +72,7 @@ func Test_writer_002(t *testing.T) {
7272
// Create a writer with an audio stream
7373
writer, err := ffmpeg.Create(w.Name(),
7474
ffmpeg.OptMetadata(ffmpeg.NewMetadata("title", t.Name())),
75-
ffmpeg.OptAudioStream(1, ffmpeg.AudioPar("fltp", "mono", 22050)),
75+
ffmpeg.OptStream(1, ffmpeg.AudioPar("fltp", "mono", 22050)),
7676
)
7777
if !assert.NoError(err) {
7878
t.FailNow()
@@ -86,8 +86,8 @@ func Test_writer_002(t *testing.T) {
8686
}
8787
defer audio.Close()
8888

89-
// Write frames
90-
duration := 1000 * time.Minute
89+
// Write 15 mins of frames
90+
duration := 15 * time.Minute
9191
assert.NoError(writer.Encode(func(stream int) (*ff.AVFrame, error) {
9292
frame := audio.Frame()
9393
if frame.Time() >= duration {
@@ -104,3 +104,49 @@ func Test_writer_002(t *testing.T) {
104104
}))
105105
t.Log("Written to", w.Name())
106106
}
107+
108+
func Test_writer_003(t *testing.T) {
109+
assert := assert.New(t)
110+
111+
// Write to a file
112+
w, err := os.CreateTemp("", t.Name()+"_*.ts")
113+
if !assert.NoError(err) {
114+
t.FailNow()
115+
}
116+
defer w.Close()
117+
118+
// Create a writer with an audio stream
119+
writer, err := ffmpeg.NewWriter(w,
120+
ffmpeg.OptMetadata(ffmpeg.NewMetadata("title", t.Name())),
121+
ffmpeg.OptStream(1, ffmpeg.VideoPar("yuv420p", "1280x720", 25)),
122+
)
123+
if !assert.NoError(err) {
124+
t.FailNow()
125+
}
126+
defer writer.Close()
127+
128+
// Make an video generator
129+
video, err := generator.NewYUV420P(25, writer.Stream(1).Par())
130+
if !assert.NoError(err) {
131+
t.FailNow()
132+
}
133+
defer video.Close()
134+
135+
// Write 1 min of frames
136+
duration := time.Minute
137+
assert.NoError(writer.Encode(func(stream int) (*ff.AVFrame, error) {
138+
frame := video.Frame()
139+
if frame.Time() >= duration {
140+
return nil, io.EOF
141+
} else {
142+
t.Log("Frame", frame.Time().Truncate(time.Millisecond))
143+
return frame.(*ffmpeg.Frame).AVFrame(), nil
144+
}
145+
}, func(packet *ff.AVPacket, timebase *ff.AVRational) error {
146+
if packet != nil {
147+
t.Log("Packet", packet)
148+
}
149+
return writer.Write(packet)
150+
}))
151+
t.Log("Written to", w.Name())
152+
}

pkg/generator/yuv420p.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,18 @@ var _ Generator = (*yuv420p)(nil)
2424

2525
// Create a new video generator which generates YUV420P frames
2626
// of the specified size and framerate (in frames per second)
27-
func NewYUV420P(size string, framerate int) (*yuv420p, error) {
27+
func NewYUV420P(framerate int, par *ffmpeg.Par) (*yuv420p, error) {
2828
yuv420p := new(yuv420p)
2929

3030
// Check parameters
3131
if framerate <= 0 {
3232
return nil, errors.New("invalid framerate")
3333
}
34-
w, h, err := ff.AVUtil_parse_video_size(size)
35-
if err != nil {
36-
return nil, err
34+
// Check parameters
35+
if par.CodecType() != ff.AVMEDIA_TYPE_VIDEO {
36+
return nil, errors.New("invalid codec type")
37+
} else if par.PixelFormat() != ff.AV_PIX_FMT_YUV420P {
38+
return nil, errors.New("invalid pixel format, only yuv420p is supported")
3739
}
3840

3941
// Create a frame
@@ -42,10 +44,10 @@ func NewYUV420P(size string, framerate int) (*yuv420p, error) {
4244
return nil, errors.New("failed to allocate frame")
4345
}
4446

45-
frame.SetPixFmt(ff.AV_PIX_FMT_YUV420P)
46-
frame.SetWidth(w)
47-
frame.SetHeight(h)
48-
frame.SetSampleAspectRatio(ff.AVUtil_rational(1, 1))
47+
frame.SetPixFmt(par.PixelFormat())
48+
frame.SetWidth(par.Width())
49+
frame.SetHeight(par.Height())
50+
frame.SetSampleAspectRatio(par.SampleAspectRatio())
4951
frame.SetTimeBase(ff.AVUtil_rational(1, framerate))
5052
frame.SetPts(ff.AV_NOPTS_VALUE)
5153

pkg/generator/yuv420p_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import (
77
"path/filepath"
88
"testing"
99

10+
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
1011
"github.com/mutablelogic/go-media/pkg/generator"
1112
"github.com/stretchr/testify/assert"
1213
)
1314

1415
func Test_yuv420p_001(t *testing.T) {
1516
assert := assert.New(t)
16-
image, err := generator.NewYUV420P("1024x768", 25)
17+
image, err := generator.NewYUV420P(25, ffmpeg.VideoPar("yuva420p", "1280x720"))
1718
if !assert.NoError(err) {
1819
t.FailNow()
1920
}
@@ -24,7 +25,7 @@ func Test_yuv420p_001(t *testing.T) {
2425

2526
func Test_yuv420p_002(t *testing.T) {
2627
assert := assert.New(t)
27-
image, err := generator.NewYUV420P("vga", 25)
28+
image, err := generator.NewYUV420P(25, ffmpeg.VideoPar("yuva420p", "1280x720"))
2829
if !assert.NoError(err) {
2930
t.FailNow()
3031
}
@@ -38,7 +39,7 @@ func Test_yuv420p_002(t *testing.T) {
3839

3940
func Test_yuv420p_003(t *testing.T) {
4041
assert := assert.New(t)
41-
image, err := generator.NewYUV420P("vga", 25)
42+
image, err := generator.NewYUV420P(25, ffmpeg.VideoPar("yuva420p", "1280x720"))
4243
if !assert.NoError(err) {
4344
t.FailNow()
4445
}

0 commit comments

Comments
 (0)