mirror of
https://github.com/asticode/go-astiav.git
synced 2025-10-05 00:02:45 +08:00
Added FrameData
This commit is contained in:
22
.github/workflows/test.yml
vendored
22
.github/workflows/test.yml
vendored
@@ -2,7 +2,7 @@ name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master", "test" ]
|
||||
branches: [ "master", "dev" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
@@ -10,6 +10,7 @@ jobs:
|
||||
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
@@ -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: |
|
||||
|
235
astiav_test.go
235
astiav_test.go
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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())
|
||||
|
@@ -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())
|
||||
|
33
frame.go
33
frame.go
@@ -6,6 +6,7 @@ package astiav
|
||||
//#include <libavutil/imgutils.h>
|
||||
//#include <libavutil/samplefmt.h>
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
const NumDataPointers = uint(C.AV_NUM_DATA_POINTERS)
|
||||
|
||||
@@ -53,20 +54,8 @@ func (f *Frame) SetColorRange(r ColorRange) {
|
||||
f.c.color_range = C.enum_AVColorRange(r)
|
||||
}
|
||||
|
||||
func (f *Frame) Data() [NumDataPointers][]byte {
|
||||
b := [NumDataPointers][]byte{}
|
||||
for i := 0; i < int(NumDataPointers); i++ {
|
||||
b[i] = bytesFromC(func(size *cUlong) *C.uint8_t {
|
||||
*size = cUlong(f.c.linesize[i])
|
||||
if f.c.height > 0 {
|
||||
*size = *size * cUlong(f.c.height)
|
||||
} else if f.c.channels > 0 {
|
||||
*size = *size * cUlong(f.c.channels)
|
||||
}
|
||||
return f.c.data[i]
|
||||
})
|
||||
}
|
||||
return b
|
||||
func (f *Frame) Data() *FrameData {
|
||||
return newFrameData(f)
|
||||
}
|
||||
|
||||
func (f *Frame) Height() int {
|
||||
@@ -89,6 +78,22 @@ func (f *Frame) SetKeyFrame(k bool) {
|
||||
f.c.key_frame = C.int(i)
|
||||
}
|
||||
|
||||
func (f *Frame) ImageBufferSize(align int) (int, error) {
|
||||
ret := C.av_image_get_buffer_size((C.enum_AVSampleFormat)(f.c.format), f.c.width, f.c.height, C.int(align))
|
||||
if ret < 0 {
|
||||
return 0, newError(ret)
|
||||
}
|
||||
return int(ret), nil
|
||||
}
|
||||
|
||||
func (f *Frame) ImageCopyToBuffer(b []byte, align int) (int, error) {
|
||||
ret := C.av_image_copy_to_buffer((*C.uint8_t)(unsafe.Pointer(&b[0])), C.int(len(b)), &f.c.data[0], &f.c.linesize[0], (C.enum_AVSampleFormat)(f.c.format), f.c.width, f.c.height, C.int(align))
|
||||
if ret < 0 {
|
||||
return 0, newError(ret)
|
||||
}
|
||||
return int(ret), nil
|
||||
}
|
||||
|
||||
func (f *Frame) ImageFillBlack() error {
|
||||
linesize := [NumDataPointers]cLong{}
|
||||
for i := 0; i < int(NumDataPointers); i++ {
|
||||
|
120
frame_data.go
Normal file
120
frame_data.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package astiav
|
||||
|
||||
//#include <stdint.h>
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type FrameData struct {
|
||||
f *Frame
|
||||
}
|
||||
|
||||
func newFrameData(f *Frame) *FrameData {
|
||||
return &FrameData{f: f}
|
||||
}
|
||||
|
||||
func (d *FrameData) Bytes(align int) ([]byte, error) {
|
||||
switch {
|
||||
// Video
|
||||
case d.f.Height() > 0 && d.f.Width() > 0:
|
||||
// Get buffer size
|
||||
s, err := d.f.ImageBufferSize(align)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("astiav: getting image buffer size failed: %w", err)
|
||||
}
|
||||
|
||||
// Invalid buffer size
|
||||
if s == 0 {
|
||||
return nil, errors.New("astiav: invalid image buffer size")
|
||||
}
|
||||
|
||||
// Create buffer
|
||||
b := make([]byte, s)
|
||||
|
||||
// Copy image to buffer
|
||||
if _, err = d.f.ImageCopyToBuffer(b, align); err != nil {
|
||||
return nil, fmt.Errorf("astiav: copying image to buffer failed: %w", err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
return nil, errors.New("astiav: frame type not implemented")
|
||||
}
|
||||
|
||||
func (d *FrameData) planeData(i int, sizeFunc func(linesize int) int) []byte {
|
||||
return bytesFromC(func(size *cUlong) *C.uint8_t {
|
||||
*size = cUlong(sizeFunc(int(d.f.c.linesize[i])))
|
||||
return d.f.c.data[i]
|
||||
})
|
||||
}
|
||||
|
||||
func (d *FrameData) imageYCbCrSubsampleRatio() image.YCbCrSubsampleRatio {
|
||||
name := d.f.PixelFormat().Name()
|
||||
for s, r := range map[string]image.YCbCrSubsampleRatio{
|
||||
"410": image.YCbCrSubsampleRatio410,
|
||||
"411": image.YCbCrSubsampleRatio411,
|
||||
"420": image.YCbCrSubsampleRatio420,
|
||||
"422": image.YCbCrSubsampleRatio422,
|
||||
"440": image.YCbCrSubsampleRatio440,
|
||||
"444": image.YCbCrSubsampleRatio444,
|
||||
} {
|
||||
if strings.Contains(name, s) {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return image.YCbCrSubsampleRatio444
|
||||
}
|
||||
|
||||
func (d *FrameData) imageNRGBA() *image.NRGBA {
|
||||
return &image.NRGBA{
|
||||
Pix: d.planeData(0, func(linesize int) int { return linesize * d.f.Height() }),
|
||||
Stride: d.f.Linesize()[0],
|
||||
Rect: image.Rect(0, 0, d.f.Width(), d.f.Height()),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *FrameData) imageYCbCr() *image.YCbCr {
|
||||
return &image.YCbCr{
|
||||
Y: d.planeData(0, func(linesize int) int { return linesize * d.f.Height() }),
|
||||
Cb: d.planeData(1, func(linesize int) int { return linesize * d.f.Height() }),
|
||||
Cr: d.planeData(2, func(linesize int) int { return linesize * d.f.Height() }),
|
||||
YStride: d.f.Linesize()[0],
|
||||
CStride: d.f.Linesize()[1],
|
||||
SubsampleRatio: d.imageYCbCrSubsampleRatio(),
|
||||
Rect: image.Rect(0, 0, d.f.Width(), d.f.Height()),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *FrameData) imageNYCbCrA() *image.NYCbCrA {
|
||||
return &image.NYCbCrA{
|
||||
YCbCr: *d.imageYCbCr(),
|
||||
A: d.planeData(3, func(linesize int) int { return linesize * d.f.Height() }),
|
||||
AStride: d.f.Linesize()[3],
|
||||
}
|
||||
}
|
||||
|
||||
func (d *FrameData) Image() (image.Image, error) {
|
||||
// Switch on pixel format
|
||||
switch d.f.PixelFormat() {
|
||||
// NRGBA
|
||||
case PixelFormatRgba:
|
||||
return d.imageNRGBA(), nil
|
||||
// NYCbCrA
|
||||
case PixelFormatYuva420P,
|
||||
PixelFormatYuva422P,
|
||||
PixelFormatYuva444P:
|
||||
return d.imageNYCbCrA(), nil
|
||||
// YCbCr
|
||||
case PixelFormatYuv410P,
|
||||
PixelFormatYuv411P, PixelFormatYuvj411P,
|
||||
PixelFormatYuv420P, PixelFormatYuvj420P,
|
||||
PixelFormatYuv422P, PixelFormatYuvj422P,
|
||||
PixelFormatYuv440P, PixelFormatYuvj440P,
|
||||
PixelFormatYuv444P, PixelFormatYuvj444P:
|
||||
return d.imageYCbCr(), nil
|
||||
}
|
||||
return nil, fmt.Errorf("astiav: %s pixel format not handled by the Go standard image package", d.f.PixelFormat())
|
||||
}
|
47
frame_data_test.go
Normal file
47
frame_data_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package astiav_test
|
||||
|
||||
import (
|
||||
"image/png"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/asticode/go-astiav"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFrameData(t *testing.T) {
|
||||
for _, v := range []struct {
|
||||
ext string
|
||||
name string
|
||||
}{
|
||||
{
|
||||
ext: "png",
|
||||
name: "image-rgba",
|
||||
},
|
||||
// TODO Find a way to test yuv and yuva even though result seems to change randomly
|
||||
} {
|
||||
// We use a closure to ease closing files
|
||||
func() {
|
||||
f, err := globalHelper.inputLastFrame(v.name+"."+v.ext, astiav.MediaTypeVideo)
|
||||
require.NoError(t, err)
|
||||
fd := f.Data()
|
||||
|
||||
b1, err := fd.Bytes(1)
|
||||
require.NoError(t, err)
|
||||
|
||||
b2, err := os.ReadFile("testdata/" + v.name + "-bytes")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, b1, b2)
|
||||
|
||||
f1, err := os.Open("testdata/" + v.name + "." + v.ext)
|
||||
require.NoError(t, err)
|
||||
defer f1.Close()
|
||||
|
||||
i1, err := fd.Image()
|
||||
require.NoError(t, err)
|
||||
i2, err := png.Decode(f1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, i1, i2)
|
||||
}()
|
||||
}
|
||||
}
|
136
frame_test.go
136
frame_test.go
@@ -2,136 +2,15 @@ package astiav_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/asticode/go-astiav"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func videoInputLastVideoFrame() (f *astiav.Frame, err error) {
|
||||
if global.frame != nil {
|
||||
return global.frame, nil
|
||||
}
|
||||
|
||||
var fc *astiav.FormatContext
|
||||
if fc, err = videoInputFormatContext(); 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() != astiav.MediaTypeVideo {
|
||||
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
|
||||
}
|
||||
global.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 = videoInputFirstPacket(); err != nil {
|
||||
err = fmt.Errorf("astiav_test: getting input first packet failed: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
pkt2 := astiav.AllocPacket()
|
||||
global.closer.Add(pkt2.Free)
|
||||
|
||||
f = astiav.AllocFrame()
|
||||
global.closer.Add(f.Free)
|
||||
|
||||
lastFrame := astiav.AllocFrame()
|
||||
global.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 err = f.Ref(lastFrame); err != nil {
|
||||
err = fmt.Errorf("astiav_test: refing frame failed: %w", err)
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
err = fmt.Errorf("astiav_test: reading frame failed: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
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{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestFrame(t *testing.T) {
|
||||
f1, err := videoInputLastVideoFrame()
|
||||
f1, err := globalHelper.inputLastFrame("video.mp4", astiav.MediaTypeVideo)
|
||||
require.NoError(t, err)
|
||||
_, err = os.ReadFile("testdata/frame")
|
||||
require.NoError(t, err)
|
||||
// TODO Fix in Github action
|
||||
//require.Equal(t, string(b), fmt.Sprintf("%+v", f1.Data()))
|
||||
require.Equal(t, [8]int{384, 192, 192, 0, 0, 0, 0, 0}, f1.Linesize())
|
||||
require.Equal(t, int64(60928), f1.PktDts())
|
||||
|
||||
@@ -216,7 +95,16 @@ func TestFrame(t *testing.T) {
|
||||
f6.SetHeight(2)
|
||||
f6.SetPixelFormat(astiav.PixelFormatYuv420P)
|
||||
f6.SetWidth(4)
|
||||
require.NoError(t, f6.AllocBuffer(1))
|
||||
require.NoError(t, f6.AllocImage(1))
|
||||
const align = 1
|
||||
require.NoError(t, f6.AllocBuffer(align))
|
||||
require.NoError(t, f6.AllocImage(align))
|
||||
require.NoError(t, f6.ImageFillBlack())
|
||||
n, err := f6.ImageBufferSize(align)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 12, n)
|
||||
b := make([]byte, n)
|
||||
n, err = f6.ImageCopyToBuffer(b, align)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 12, n)
|
||||
require.Equal(t, []byte{0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x80, 0x80, 0x80, 0x80}, b)
|
||||
}
|
||||
|
@@ -1,43 +1,14 @@
|
||||
package astiav_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/asticode/go-astiav"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func videoInputFirstPacket() (pkt *astiav.Packet, err error) {
|
||||
if global.pkt != nil {
|
||||
return global.pkt, nil
|
||||
}
|
||||
|
||||
var fc *astiav.FormatContext
|
||||
if fc, err = videoInputFormatContext(); 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
|
||||
}
|
||||
global.closer.Add(pkt.Free)
|
||||
|
||||
if err = fc.ReadFrame(pkt); err != nil {
|
||||
err = fmt.Errorf("astiav_test: reading frame failed: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
global.pkt = pkt
|
||||
return
|
||||
}
|
||||
|
||||
func TestPacket(t *testing.T) {
|
||||
pkt1, err := videoInputFirstPacket()
|
||||
pkt1, err := globalHelper.inputFirstPacket("video.mp4")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte{0x0, 0x0, 0x0, 0xd1, 0x65, 0x88, 0x82, 0x0, 0x1f, 0x5f, 0xff, 0xf8, 0x22, 0x8a, 0x0, 0x2, 0x2d, 0xbe, 0x38, 0xc7, 0x19, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x3a, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xb9, 0xb8, 0xe6, 0x39, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xc0}, pkt1.Data())
|
||||
require.Equal(t, int64(0), pkt1.Dts())
|
||||
|
@@ -1,40 +1,19 @@
|
||||
package astiav_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/asticode/go-astiav"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func videoInputStreams() (fc *astiav.FormatContext, s1, s2 *astiav.Stream, err error) {
|
||||
if global.inputFormatContext != nil && global.inputStream1 != nil && global.inputStream2 != nil {
|
||||
return global.inputFormatContext, global.inputStream1, global.inputStream2, nil
|
||||
}
|
||||
|
||||
if fc, err = videoInputFormatContext(); err != nil {
|
||||
err = fmt.Errorf("astiav_test: getting video input format context failed: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
ss := fc.Streams()
|
||||
if len(ss) < 2 {
|
||||
err = fmt.Errorf("astiav_test: invalid streams len: %d", len(ss))
|
||||
return
|
||||
}
|
||||
|
||||
s1 = ss[0]
|
||||
s2 = ss[1]
|
||||
|
||||
global.inputStream1 = s1
|
||||
global.inputStream2 = s2
|
||||
return
|
||||
}
|
||||
|
||||
func TestStream(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]
|
||||
|
||||
require.Equal(t, 0, s1.Index())
|
||||
require.Equal(t, astiav.NewRational(24, 1), s1.AvgFrameRate())
|
||||
|
1
testdata/frame
vendored
1
testdata/frame
vendored
File diff suppressed because one or more lines are too long
BIN
testdata/image-rgba-bytes
vendored
Normal file
BIN
testdata/image-rgba-bytes
vendored
Normal file
Binary file not shown.
BIN
testdata/image-rgba.png
vendored
Normal file
BIN
testdata/image-rgba.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
Reference in New Issue
Block a user