Skip to content

Commit

Permalink
Added FrameData
Browse files Browse the repository at this point in the history
  • Loading branch information
asticode committed Sep 2, 2023
1 parent e24b820 commit 63a083b
Show file tree
Hide file tree
Showing 14 changed files with 456 additions and 245 deletions.
22 changes: 15 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ name: Test

on:
push:
branches: [ "master", "test" ]
branches: [ "master", "dev" ]
pull_request:
branches: [ "master" ]

jobs:

test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]

Expand Down Expand Up @@ -41,35 +42,42 @@ jobs:
run: |
echo "FFMPEG_CACHE_PATH=${{ env.FFMPEG_PATH }}" >> $GITHUB_ENV
- name: Cache windows ffmpeg
id: cache-ffmpeg
- name: Load ffmpeg cache
id: load-ffmpeg-cache
uses: actions/cache@v3
with:
path: ${{ env.FFMPEG_CACHE_PATH }}
key: ffmpeg-${{ env.FFMPEG_VERSION }}-${{ runner.os }}

- if: ${{ steps.cache-ffmpeg.outputs.cache-hit != 'true' && runner.os == 'Linux' }}
- if: ${{ steps.load-ffmpeg-cache.outputs.cache-hit != 'true' && runner.os == 'Linux' }}
name: Prepare linux ffmpeg install
run: |
sudo apt-get install yasm
- if: ${{ steps.cache-ffmpeg.outputs.cache-hit != 'true' && runner.os == 'macOS' }}
- if: ${{ steps.load-ffmpeg-cache.outputs.cache-hit != 'true' && runner.os == 'macOS' }}
name: Prepare macos ffmpeg install
run: |
brew install yasm
- if: ${{ steps.cache-ffmpeg.outputs.cache-hit != 'true' && runner.os == 'Windows' }}
- if: ${{ steps.load-ffmpeg-cache.outputs.cache-hit != 'true' && runner.os == 'Windows' }}
name: Prepare windows ffmpeg install
run: |
choco install make
choco install yasm
echo "FFMPEG_POST_CHECKOUT='&& git apply $(cygpath -u ${{ github.WORKSPACE }})/.github/workflows/windows.patch'" >> $env:GITHUB_ENV
- if: ${{ steps.cache-ffmpeg.outputs.cache-hit != 'true' }}
- if: ${{ steps.load-ffmpeg-cache.outputs.cache-hit != 'true' }}
name: Install ffmpeg
run: |
make install-ffmpeg srcPath=${{ env.FFMPEG_PATH }}/src version=${{ env.FFMPEG_VERSION }} postCheckout=${{ env.FFMPEG_POST_CHECKOUT }}
- if: ${{ steps.load-ffmpeg-cache.outputs.cache-hit != 'true' }}
name: Save ffmpeg cache
uses: actions/cache/save@v3
with:
path: ${{ env.FFMPEG_CACHE_PATH }}
key: ffmpeg-${{ env.FFMPEG_VERSION }}-${{ runner.os }}

- if: ${{ runner.os == 'Windows' }}
name: Set windows environment variables
run: |
Expand Down
235 changes: 223 additions & 12 deletions astiav_test.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,242 @@
package astiav_test

import (
"errors"
"fmt"
"os"
"sync"
"testing"

"github.com/asticode/go-astiav"
"github.com/asticode/go-astikit"
)

var global = struct {
closer *astikit.Closer
frame *astiav.Frame
inputFormatContext *astiav.FormatContext
inputStream1 *astiav.Stream
inputStream2 *astiav.Stream
pkt *astiav.Packet
}{
closer: astikit.NewCloser(),
}
var globalHelper = newHelper()

func TestMain(m *testing.M) {
// Run
m.Run()

// Make sure to close closer
global.closer.Close()
// Make sure to close global helper
globalHelper.close()

// Exit
os.Exit(0)
}

type helper struct {
closer *astikit.Closer
inputs map[string]*helperInput
m *sync.Mutex // Locks inputs
}

func newHelper() *helper {
return &helper{
closer: astikit.NewCloser(),
inputs: make(map[string]*helperInput),
m: &sync.Mutex{},
}
}

func (h *helper) close() {
h.closer.Close()
}

type helperInput struct {
firstPkt *astiav.Packet
formatContext *astiav.FormatContext
lastFrame *astiav.Frame
}

func (h *helper) inputFormatContext(name string) (fc *astiav.FormatContext, err error) {
h.m.Lock()
i, ok := h.inputs[name]
if ok && i.formatContext != nil {
h.m.Unlock()
return i.formatContext, nil
}
h.m.Unlock()

if fc = astiav.AllocFormatContext(); fc == nil {
err = errors.New("astiav_test: allocated format context is nil")
return
}
h.closer.Add(fc.Free)

if err = fc.OpenInput("testdata/"+name, nil, nil); err != nil {
err = fmt.Errorf("astiav_test: opening input failed: %w", err)
return
}
h.closer.Add(fc.CloseInput)

if err = fc.FindStreamInfo(nil); err != nil {
err = fmt.Errorf("astiav_test: finding stream info failed: %w", err)
return
}

h.m.Lock()
if _, ok := h.inputs[name]; !ok {
h.inputs[name] = &helperInput{}
}
h.inputs[name].formatContext = fc
h.m.Unlock()
return
}

func (h *helper) inputFirstPacket(name string) (pkt *astiav.Packet, err error) {
h.m.Lock()
i, ok := h.inputs[name]
if ok && i.firstPkt != nil {
h.m.Unlock()
return i.firstPkt, nil
}
h.m.Unlock()

var fc *astiav.FormatContext
if fc, err = h.inputFormatContext(name); err != nil {
err = fmt.Errorf("astiav_test: getting input format context failed")
return
}

pkt = astiav.AllocPacket()
if pkt == nil {
err = errors.New("astiav_test: pkt is nil")
return
}
h.closer.Add(pkt.Free)

if err = fc.ReadFrame(pkt); err != nil {
err = fmt.Errorf("astiav_test: reading frame failed: %w", err)
return
}

h.m.Lock()
h.inputs[name].firstPkt = pkt
h.m.Unlock()
return
}

func (h *helper) inputLastFrame(name string, mediaType astiav.MediaType) (f *astiav.Frame, err error) {
h.m.Lock()
i, ok := h.inputs[name]
if ok && i.lastFrame != nil {
h.m.Unlock()
return i.lastFrame, nil
}
h.m.Unlock()

var fc *astiav.FormatContext
if fc, err = h.inputFormatContext(name); err != nil {
err = fmt.Errorf("astiav_test: getting input format context failed: %w", err)
return
}

var cc *astiav.CodecContext
var cs *astiav.Stream
for _, s := range fc.Streams() {
if s.CodecParameters().MediaType() != mediaType {
continue
}

cs = s

c := astiav.FindDecoder(s.CodecParameters().CodecID())
if c == nil {
err = errors.New("astiav_test: no codec")
return
}

cc = astiav.AllocCodecContext(c)
if cc == nil {
err = errors.New("astiav_test: no codec context")
return
}
h.closer.Add(cc.Free)

if err = cs.CodecParameters().ToCodecContext(cc); err != nil {
err = fmt.Errorf("astiav_test: updating codec context failed: %w", err)
return
}

if err = cc.Open(c, nil); err != nil {
err = fmt.Errorf("astiav_test: opening codec context failed: %w", err)
return
}
break
}

if cs == nil {
err = errors.New("astiav_test: no valid video stream")
return
}

var pkt1 *astiav.Packet
if pkt1, err = h.inputFirstPacket(name); err != nil {
err = fmt.Errorf("astiav_test: getting input first packet failed: %w", err)
return
}

pkt2 := astiav.AllocPacket()
h.closer.Add(pkt2.Free)

f = astiav.AllocFrame()
h.closer.Add(f.Free)

lastFrame := astiav.AllocFrame()
h.closer.Add(lastFrame.Free)

pkts := []*astiav.Packet{pkt1}
for {
if err = fc.ReadFrame(pkt2); err != nil {
if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) {
if len(pkts) == 0 {
if err = f.Ref(lastFrame); err != nil {
err = fmt.Errorf("astiav_test: last refing frame failed: %w", err)
return
}
err = nil
break
}
} else {
err = fmt.Errorf("astiav_test: reading frame failed: %w", err)
return
}
} else {
pkts = append(pkts, pkt2)
}

for _, pkt := range pkts {
if pkt.StreamIndex() != cs.Index() {
continue
}

if err = cc.SendPacket(pkt); err != nil {
err = fmt.Errorf("astiav_test: sending packet failed: %w", err)
return
}

for {
if err = cc.ReceiveFrame(f); err != nil {
if errors.Is(err, astiav.ErrEof) || errors.Is(err, astiav.ErrEagain) {
err = nil
break
}
err = fmt.Errorf("astiav_test: receiving frame failed: %w", err)
return
}

if err = lastFrame.Ref(f); err != nil {
err = fmt.Errorf("astiav_test: refing frame failed: %w", err)
return
}
}
}

pkts = []*astiav.Packet{}
}

h.m.Lock()
h.inputs[name].lastFrame = f
h.m.Unlock()
return
}
6 changes: 5 additions & 1 deletion codec_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import (
)

func TestCodecContext(t *testing.T) {
_, s1, s2, err := videoInputStreams()
fc, err := globalHelper.inputFormatContext("video.mp4")
require.NoError(t, err)
ss := fc.Streams()
require.Len(t, ss, 2)
s1 := ss[0]
s2 := ss[1]

c1 := astiav.FindDecoder(s1.CodecParameters().CodecID())
require.NotNil(t, c1)
Expand Down
6 changes: 5 additions & 1 deletion codec_parameters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import (
)

func TestCodecParameters(t *testing.T) {
_, s1, s2, err := videoInputStreams()
fc, err := globalHelper.inputFormatContext("video.mp4")
require.NoError(t, err)
ss := fc.Streams()
require.Len(t, ss, 2)
s1 := ss[0]
s2 := ss[1]

cp1 := s1.CodecParameters()
require.Equal(t, int64(441324), cp1.BitRate())
Expand Down
33 changes: 4 additions & 29 deletions format_context_test.go
Original file line number Diff line number Diff line change
@@ -1,43 +1,18 @@
package astiav_test

import (
"errors"
"fmt"
"testing"

"github.com/asticode/go-astiav"
"github.com/stretchr/testify/require"
)

func videoInputFormatContext() (fc1 *astiav.FormatContext, err error) {
if global.inputFormatContext != nil {
return global.inputFormatContext, nil
}

if fc1 = astiav.AllocFormatContext(); fc1 == nil {
err = errors.New("astiav_test: allocated format context is nil")
return
}
global.closer.Add(fc1.Free)

if err = fc1.OpenInput("testdata/video.mp4", nil, nil); err != nil {
err = fmt.Errorf("astiav_test: opening input failed: %w", err)
return
}
global.closer.Add(fc1.CloseInput)

if err = fc1.FindStreamInfo(nil); err != nil {
err = fmt.Errorf("astiav_test: finding stream info failed: %w", err)
return
}

global.inputFormatContext = fc1
return
}

func TestFormatContext(t *testing.T) {
fc1, s1, _, err := videoInputStreams()
fc1, err := globalHelper.inputFormatContext("video.mp4")
require.NoError(t, err)
ss := fc1.Streams()
require.Len(t, ss, 2)
s1 := ss[0]

require.Equal(t, int64(607583), fc1.BitRate())
require.Equal(t, astiav.NewFormatContextCtxFlags(0), fc1.CtxFlags())
Expand Down
Loading

0 comments on commit 63a083b

Please sign in to comment.