Skip to content

Commit

Permalink
Merge pull request #24 from mutablelogic/ffmpeg61
Browse files Browse the repository at this point in the history
* General update
* Fixes tests on pull request
  • Loading branch information
djthorpe authored Jun 26, 2024
2 parents ad9b25a + 3b9c6db commit 74a2c76
Show file tree
Hide file tree
Showing 20 changed files with 840 additions and 138 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/on_pull_request_merge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
permissions:
actions: read
contents: read
Expand All @@ -28,7 +28,7 @@ jobs:
uses: github/codeql-action/analyze@v3
test:
name: Test
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
matrix:
go-version: [ '1.21', '1.22' ]
Expand All @@ -42,4 +42,5 @@ jobs:
- name: Run tests
run: |
sudo apt install -y libavcodec-dev libavdevice-dev libavfilter-dev libavutil-dev libswscale-dev libswresample-dev libchromaprint-dev
make test
make container-test
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ test: go-dep
@${GO} test ./pkg/...
@${GO} test .

container-test: go-dep
@echo Test
@${GO} mod tidy
@${GO} test --tags=container ./sys/ffmpeg61
@${GO} test --tags=container ./sys/chromaprint
@${GO} test --tags=container ./pkg/...
@${GO} test --tags=container .



cli: go-dep mkdir
@echo Build media tool
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

This module provides an interface for media services, including:

* Bindings in golang for [ffmpeg 6](https://ffmpeg.org/);
* Bindings in golang for [FFmpeg 6.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;
Expand Down Expand Up @@ -235,8 +235,8 @@ The license is Apache 2 so feel free to redistribute. Redistributions in either
code or binary form must reproduce the copyright notice, and please link back to this
repository for more information:

> go-media
> https://github.com/mutablelogic/go-media/
> __go-media__\
> [https://github.com/mutablelogic/go-media/](https://github.com/mutablelogic/go-media/)\
> Copyright (c) 2021-2024 David Thorpe, All rights reserved.
This software links to shared libraries of [FFmpeg](http://ffmpeg.org/) licensed under
Expand Down
185 changes: 185 additions & 0 deletions encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package media

import (

// Packages
"fmt"
"io"

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

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

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

type encoder struct {
t MediaType
ctx *ff.AVCodecContext
stream *ff.AVStream
packet *ff.AVPacket
next_pts int64
}

////////////////////////////////////////////////////////////////////////////////
// LIFECYCLE

// Create an encoder with the given parameters
func newEncoder(ctx *ff.AVFormatContext, stream_id int, param Parameters) (*encoder, error) {
encoder := new(encoder)
par := param.(*par)

// Get codec
codec_id := ff.AV_CODEC_ID_NONE
if param.Type().Is(CODEC) {
codec_id = par.codecpar.Codec
} else if par.Type().Is(AUDIO) {
codec_id = ctx.Output().AudioCodec()
} else if par.Type().Is(VIDEO) {
codec_id = ctx.Output().VideoCodec()
} else if par.Type().Is(SUBTITLE) {
codec_id = ctx.Output().SubtitleCodec()
}
if codec_id == ff.AV_CODEC_ID_NONE {
return nil, ErrBadParameter.With("no codec specified for stream")
}

// Allocate codec
codec := ff.AVCodec_find_encoder(codec_id)
if codec == nil {
return nil, ErrBadParameter.Withf("codec %q cannot encode", codec_id)
}
codecctx := ff.AVCodec_alloc_context(codec)
if codecctx == nil {
return nil, ErrInternalAppError.With("could not allocate audio codec context")
} else {
encoder.ctx = codecctx
}

// Create the stream
if stream := ff.AVFormat_new_stream(ctx, nil); stream == nil {
ff.AVCodec_free_context(codecctx)
return nil, ErrInternalAppError.With("could not allocate stream")
} else {
stream.SetId(stream_id)
encoder.stream = stream
}

// Set parameters
switch codec.Type() {
case ff.AVMEDIA_TYPE_AUDIO:
encoder.t = AUDIO

// Choose sample format
if sampleformat, err := ff.AVCodec_supported_sampleformat(codec, par.audiopar.SampleFormat); err != nil {
ff.AVCodec_free_context(codecctx)
return nil, err
} else {
codecctx.SetSampleFormat(sampleformat)
}

// TODO Choose sample rate
codecctx.SetSampleRate(par.audiopar.Samplerate)

// TODO
//if samplerate, err := ff.AVCodec_supported_samplerate(codec, par.audiopar.Samplerate); err != nil {
// ff.AVCodec_free_context(codecctx)
// return nil, err
//}

// TODO Choose channel layout
//if channellayout, err := ff.AVCodec_supported_channellayout(codec, par.audiopar.Ch); err != nil {
// ff.AVCodec_free_context(codecctx)
// return nil, err
//}

if err := codecctx.SetChannelLayout(par.audiopar.Ch); err != nil {
ff.AVCodec_free_context(codecctx)
return nil, err
}

// Set stream parameters
encoder.stream.SetTimeBase(ff.AVUtil_rational(1, par.audiopar.Samplerate))

case ff.AVMEDIA_TYPE_VIDEO:
encoder.t = VIDEO

// Choose pixel format
if pixelformat, err := ff.AVCodec_supported_pixelformat(codec, par.videopar.PixelFormat); err != nil {
ff.AVCodec_free_context(codecctx)
return nil, err
} else {
codecctx.SetPixFmt(pixelformat)
}

// Set codec parameters
codecctx.SetWidth(par.videopar.Width)
codecctx.SetHeight(par.videopar.Height)

// Set stream parameters
encoder.stream.SetTimeBase(ff.AVUtil_rational_d2q(1/par.codecpar.Framerate, 1<<24))
case ff.AVMEDIA_TYPE_SUBTITLE:
encoder.t = SUBTITLE
fmt.Println("TODO: Set encoding subtitle parameters")
default:
encoder.t = DATA
}
encoder.t |= OUTPUT

// copy parameters to the stream
if err := ff.AVCodec_parameters_from_context(encoder.stream.CodecPar(), codecctx); err != nil {
ff.AVCodec_free_context(codecctx)
return nil, err
}

// Some formats want stream headers to be separate.
if ctx.Flags().Is(ff.AVFMT_GLOBALHEADER) {
codecctx.SetFlags(codecctx.Flags() | ff.AV_CODEC_FLAG_GLOBAL_HEADER)
}

// Open it
if err := ff.AVCodec_open(codecctx, codec, nil); err != nil {
ff.AVCodec_free_context(codecctx)
return nil, ErrInternalAppError.Withf("codec_open: %v", err)
}

// Allocate packet
if packet := ff.AVCodec_packet_alloc(); packet == nil {
ff.AVCodec_free_context(codecctx)
return nil, ErrInternalAppError.With("could not allocate packet")
} else {
encoder.packet = packet
}

// Return it
return encoder, nil
}

func (encoder *encoder) Close() error {
// Free respurces
if encoder.packet != nil {
ff.AVCodec_packet_free(encoder.packet)
}
if encoder.ctx != nil {
ff.AVCodec_free_context(encoder.ctx)
}

// Release resources
encoder.stream = nil
encoder.packet = nil
encoder.ctx = nil

// Return success
return nil
}

////////////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS

func (encoder *encoder) encode(fn MuxFunc) (*ff.AVPacket, error) {
// TODO
fmt.Println("TODO: encode - get packet")
return nil, io.EOF
}
5 changes: 5 additions & 0 deletions frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ func (frame *frame) Type() MediaType {
return NONE
}

// Id is unused
func (frame *frame) Id() int {
return 0
}

// Return the timestamp as a duration, or minus one if not set
func (frame *frame) Time() time.Duration {
pts := frame.ctx.Pts()
Expand Down
30 changes: 26 additions & 4 deletions interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type Manager interface {
Create(string, Format, []Metadata, ...Parameters) (Media, error)

// Create a media stream for writing. The format will be used to
// determine the formar type and one or more CodecParameters used to
// determine the format and one or more CodecParameters used to
// create the streams. If no parameters are provided, then the
// default parameters for the format are used. It is the responsibility
// of the caller to also close the writer when done.
Expand Down Expand Up @@ -140,8 +140,14 @@ type Media interface {
// Return a decoding context for the media stream, and
// map the streams to decoders. If no function is provided
// (ie, the argument is nil) then all streams are demultiplexed.
// Will return an error if called on a writer.
Decoder(DecoderMapFunc) (Decoder, error)

// Multiplex media into packets. Pass a packet to a muxer function.
// Stop when the context is cancelled or the end of the media stream is
// signalled. Will return an error if called on a reader.
Mux(context.Context, MuxFunc) error

// Return INPUT for a demuxer or source, OUTPUT for a muxer or
// sink, DEVICE for a device, FILE for a file or stream.
Type() MediaType
Expand Down Expand Up @@ -191,6 +197,9 @@ type Parameters interface {
// Return the media type (AUDIO, VIDEO, SUBTITLE, DATA)
Type() MediaType

// Return the stream id for encoding, or zero if not set
Id() int

// Return number of planes for a specific PixelFormat
// or SampleFormat and ChannelLayout combination
NumPlanes() int
Expand Down Expand Up @@ -224,6 +233,13 @@ type VideoParameters interface {
// io.EOF if you want to stop processing the packets early.
type DecoderFunc func(Packet) error

// MuxFunc is a function that multiplexes a packet. It is
// repeatedly called with a stream identifier - return a packet
// for that stream if one is available, or nil if no
// packet is available for muxing. Return io.EOF to
// stop multiplexing.
type MuxFunc func(int) (Packet, error)

// FrameFunc is a function that processes a frame of audio
// or video data. Return io.EOF if you want to stop
// processing the frames early.
Expand All @@ -241,9 +257,15 @@ type Codec interface {
Type() MediaType
}

// Packet represents a packet of demultiplexed data.
// Currently this is quite opaque!
type Packet interface{}
// Packet represents a packet of demultiplexed data, or a packet
// to be multiplexed.
type Packet interface {
// The packet can be audio, video, subtitle or data.
Type() MediaType

// The stream identifier for the packet
Id() int
}

// Frame represents a frame of audio or video data.
type Frame interface {
Expand Down
34 changes: 34 additions & 0 deletions manager_ex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//go:build !container

package media_test

import (
"testing"

// Package imports
"github.com/stretchr/testify/assert"

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

// These tests do not run in containers

func Test_manager_008(t *testing.T) {
assert := assert.New(t)

manager, err := NewManager()
if !assert.NoError(err) {
t.SkipNow()
}

formats := manager.InputFormats(ANY)
assert.NotNil(formats)
for _, format := range formats {
if format.Type().Is(DEVICE) {
devices := manager.Devices(format)
assert.NotNil(devices)
t.Log(format, devices)
}
}
}
19 changes: 0 additions & 19 deletions manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,22 +108,3 @@ func Test_manager_007(t *testing.T) {

tablewriter.New(os.Stderr, tablewriter.OptHeader(), tablewriter.OptOutputText()).Write(codecs)
}

func Test_manager_008(t *testing.T) {
assert := assert.New(t)

manager, err := NewManager()
if !assert.NoError(err) {
t.SkipNow()
}

formats := manager.InputFormats(ANY)
assert.NotNil(formats)
for _, format := range formats {
if format.Type().Is(DEVICE) {
devices := manager.Devices(format)
assert.NotNil(devices)
t.Log(format, devices)
}
}
}
Loading

0 comments on commit 74a2c76

Please sign in to comment.