Compare commits
3 commits
4617d8eb53
...
def693a23c
Author | SHA1 | Date | |
---|---|---|---|
DataHoarder | def693a23c | ||
DataHoarder | 8a77f16435 | ||
DataHoarder | 194149963d |
18
README.md
18
README.md
|
@ -12,13 +12,21 @@
|
||||||
|
|
||||||
| Depth/Sampling | 4:0:0 | 4:2:0 | 4:2:2 | 4:4:4 |
|
| Depth/Sampling | 4:0:0 | 4:2:0 | 4:2:2 | 4:4:4 |
|
||||||
|:--------------:|:-----:|:-----:|:-----:|:-----:|
|
|:--------------:|:-----:|:-----:|:-----:|:-----:|
|
||||||
| 8-bit | ⚠️ | ✅ | ✅ | ✅ |
|
| 8-bit | ✅ | ✅ | ✅ | ✅ |
|
||||||
| 10-bit | ⚠️ | ✅ | ⚠️ | ⚠️ |
|
| 10-bit | ✅ | ✅ | ⚠️ | ⚠️ |
|
||||||
| 12-bit | ⚠️ | ⚠️ | ⚠️ | ✅ |
|
| 12-bit | ⚠️ | ⚠️ | ⚠️ | ✅ |
|
||||||
| 14-bit | ⚠️ | ⚠️ | ⚠️ | ⚠️ |
|
| 14-bit | ❔ | ❔ | ❔ | ❔ |
|
||||||
| 16-bit | ⚠️ | ⚠️ | ⚠️ | ⚠️ |
|
| 16-bit | ❔ | ❔ | ❔ | ❔ |
|
||||||
|
|
||||||
Legend: ✅ = supported, tested; ️⚠️ = supported, untested; ❌ = unsupported
|
Legend:
|
||||||
|
|
||||||
|
✅ = supported, tested regularly
|
||||||
|
|
||||||
|
⚠️ = supported, tested sparsely/manually
|
||||||
|
|
||||||
|
❔ = probably supported, not tested
|
||||||
|
|
||||||
|
❌ = unsupported
|
||||||
|
|
||||||
## Formats supported
|
## Formats supported
|
||||||
|
|
||||||
|
|
|
@ -222,7 +222,6 @@ func (d *Decoder) pictureToFrame() (frame.Frame, error) {
|
||||||
if d.picture.p.layout != C.DAV1D_PIXEL_LAYOUT_I400 {
|
if d.picture.p.layout != C.DAV1D_PIXEL_LAYOUT_I400 {
|
||||||
uData = unsafe.Slice((*byte)(d.picture.data[planeU]), chromaHeight*chromaWidth*2)
|
uData = unsafe.Slice((*byte)(d.picture.data[planeU]), chromaHeight*chromaWidth*2)
|
||||||
vData = unsafe.Slice((*byte)(d.picture.data[planeV]), chromaHeight*chromaWidth*2)
|
vData = unsafe.Slice((*byte)(d.picture.data[planeV]), chromaHeight*chromaWidth*2)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n := len(yData) + len(uData) + len(vData)
|
n := len(yData) + len(uData) + len(vData)
|
||||||
|
|
|
@ -5,9 +5,6 @@ package libdav1d
|
||||||
import (
|
import (
|
||||||
"git.gammaspectra.live/S.O.N.G/Ignite/frame"
|
"git.gammaspectra.live/S.O.N.G/Ignite/frame"
|
||||||
"git.gammaspectra.live/S.O.N.G/Ignite/testdata"
|
"git.gammaspectra.live/S.O.N.G/Ignite/testdata"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,27 +13,11 @@ func TestVersion(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDecode(sample testdata.TestSample, t *testing.T) {
|
func testDecode(sample testdata.TestSample, t *testing.T) {
|
||||||
var reader io.Reader
|
reader, err := sample.Open(t)
|
||||||
var err error
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if _, err = os.Stat(sample.Path); err != nil {
|
|
||||||
if sample.SkipNotFound || sample.Url == "" {
|
|
||||||
t.Skip("skipping without sample")
|
|
||||||
}
|
|
||||||
response, err := http.DefaultClient.Get(sample.Url)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
reader = response.Body
|
|
||||||
} else {
|
|
||||||
f, err := os.Open(sample.Path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
reader = f
|
|
||||||
}
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
if decoder, err := NewDecoder(reader, nil); err != nil {
|
if decoder, err := NewDecoder(reader, nil); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -3,34 +3,15 @@ package y4m
|
||||||
import (
|
import (
|
||||||
"git.gammaspectra.live/S.O.N.G/Ignite/frame"
|
"git.gammaspectra.live/S.O.N.G/Ignite/frame"
|
||||||
"git.gammaspectra.live/S.O.N.G/Ignite/testdata"
|
"git.gammaspectra.live/S.O.N.G/Ignite/testdata"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testDecode(sample testdata.TestSample, t *testing.T) {
|
func testDecode(sample testdata.TestSample, t *testing.T) {
|
||||||
var reader io.Reader
|
reader, err := sample.Open(t)
|
||||||
var err error
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if _, err = os.Stat(sample.Path); err != nil {
|
|
||||||
if sample.SkipNotFound || sample.Url == "" {
|
|
||||||
t.Skip("skipping without sample")
|
|
||||||
}
|
|
||||||
response, err := http.DefaultClient.Get(sample.Url)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
reader = response.Body
|
|
||||||
} else {
|
|
||||||
f, err := os.Open(sample.Path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
reader = f
|
|
||||||
}
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
var y4m *Decoder
|
var y4m *Decoder
|
||||||
|
|
||||||
|
|
|
@ -21,13 +21,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Encoder struct {
|
type Encoder struct {
|
||||||
w *obuwriter.Writer
|
w *obuwriter.Writer
|
||||||
cleaned atomic.Bool
|
cleaned atomic.Bool
|
||||||
cfg C.aom_codec_enc_cfg_t
|
cfg C.aom_codec_enc_cfg_t
|
||||||
codec C.aom_codec_ctx_t
|
codec C.aom_codec_ctx_t
|
||||||
raw *C.aom_image_t
|
raw *C.aom_image_t
|
||||||
frames uint32
|
frames uint32
|
||||||
pinner runtime.Pinner
|
resourcePinner runtime.Pinner
|
||||||
}
|
}
|
||||||
|
|
||||||
var libaomVersion = "libaom-av1 " + C.GoString(C.aom_codec_version_str())
|
var libaomVersion = "libaom-av1 " + C.GoString(C.aom_codec_version_str())
|
||||||
|
@ -106,9 +106,8 @@ func NewEncoder(w io.Writer, properties frame.StreamProperties, settings map[str
|
||||||
flags |= C.AOM_CODEC_USE_HIGHBITDEPTH
|
flags |= C.AOM_CODEC_USE_HIGHBITDEPTH
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.raw = (*C.aom_image_t)(C.malloc(C.size_t(unsafe.Sizeof(C.aom_image_t{})))); e.raw == nil {
|
e.raw = &C.aom_image_t{}
|
||||||
return nil, errors.New("error allocating memory")
|
e.resourcePinner.Pin(e.raw)
|
||||||
}
|
|
||||||
if C.aom_img_alloc(e.raw, imageFormat, C.uint(properties.Width), C.uint(properties.Height), 1) == nil {
|
if C.aom_img_alloc(e.raw, imageFormat, C.uint(properties.Width), C.uint(properties.Height), 1) == nil {
|
||||||
return nil, errors.New("failed to allocate image")
|
return nil, errors.New("failed to allocate image")
|
||||||
}
|
}
|
||||||
|
@ -298,39 +297,25 @@ func (e *Encoder) EncodeStream(stream *frame.Stream) error {
|
||||||
|
|
||||||
func (e *Encoder) Encode(f frame.Frame) error {
|
func (e *Encoder) Encode(f frame.Frame) error {
|
||||||
|
|
||||||
var luma, cb, cr unsafe.Pointer
|
|
||||||
|
|
||||||
switch typedFrame := f.(type) {
|
switch typedFrame := f.(type) {
|
||||||
case frame.TypedFrame[uint8]:
|
case frame.TypedFrame[uint8]:
|
||||||
luma = unsafe.Pointer(unsafe.SliceData(typedFrame.GetNativeLuma()))
|
luma := typedFrame.GetNativeLuma()
|
||||||
cb = unsafe.Pointer(unsafe.SliceData(typedFrame.GetNativeCb()))
|
cb := typedFrame.GetNativeCb()
|
||||||
cr = unsafe.Pointer(unsafe.SliceData(typedFrame.GetNativeCr()))
|
cr := typedFrame.GetNativeCr()
|
||||||
|
copy(unsafe.Slice((*byte)(e.raw.planes[0]), len(luma)), luma)
|
||||||
|
copy(unsafe.Slice((*byte)(e.raw.planes[1]), len(cb)), cb)
|
||||||
|
copy(unsafe.Slice((*byte)(e.raw.planes[2]), len(cr)), cr)
|
||||||
case frame.TypedFrame[uint16]:
|
case frame.TypedFrame[uint16]:
|
||||||
luma = unsafe.Pointer(unsafe.SliceData(typedFrame.GetNativeLuma()))
|
luma := typedFrame.GetNativeLuma()
|
||||||
cb = unsafe.Pointer(unsafe.SliceData(typedFrame.GetNativeCb()))
|
cb := typedFrame.GetNativeCb()
|
||||||
cr = unsafe.Pointer(unsafe.SliceData(typedFrame.GetNativeCr()))
|
cr := typedFrame.GetNativeCr()
|
||||||
|
copy(unsafe.Slice((*uint16)(unsafe.Pointer(e.raw.planes[0])), len(luma)), luma)
|
||||||
|
copy(unsafe.Slice((*uint16)(unsafe.Pointer(e.raw.planes[1])), len(cb)), cb)
|
||||||
|
copy(unsafe.Slice((*uint16)(unsafe.Pointer(e.raw.planes[2])), len(cr)), cr)
|
||||||
default:
|
default:
|
||||||
return errors.New("unknown frame type")
|
return errors.New("unknown frame type")
|
||||||
}
|
}
|
||||||
|
|
||||||
e.pinner.Pin(luma)
|
|
||||||
e.pinner.Pin(cb)
|
|
||||||
e.pinner.Pin(cr)
|
|
||||||
|
|
||||||
e.raw.planes[0] = (*C.uint8_t)(luma)
|
|
||||||
e.raw.planes[1] = (*C.uint8_t)(cb)
|
|
||||||
e.raw.planes[2] = (*C.uint8_t)(cr)
|
|
||||||
|
|
||||||
defer e.pinner.Unpin()
|
|
||||||
|
|
||||||
defer runtime.KeepAlive(f)
|
|
||||||
//cleanup pointers
|
|
||||||
defer func() {
|
|
||||||
e.raw.planes[0] = nil
|
|
||||||
e.raw.planes[1] = nil
|
|
||||||
e.raw.planes[2] = nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _, err := e.encodeFrame(f.PTS(), e.raw); err != nil {
|
if _, err := e.encodeFrame(f.PTS(), e.raw); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -397,10 +382,10 @@ func (e *Encoder) Close() {
|
||||||
if e.cleaned.Swap(true) == false {
|
if e.cleaned.Swap(true) == false {
|
||||||
if e.raw != nil {
|
if e.raw != nil {
|
||||||
C.aom_img_free(e.raw)
|
C.aom_img_free(e.raw)
|
||||||
C.free(unsafe.Pointer(e.raw))
|
|
||||||
e.raw = nil
|
e.raw = nil
|
||||||
}
|
}
|
||||||
C.aom_codec_destroy(&e.codec)
|
C.aom_codec_destroy(&e.codec)
|
||||||
|
e.resourcePinner.Unpin()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ package libaom
|
||||||
import (
|
import (
|
||||||
"git.gammaspectra.live/S.O.N.G/Ignite/decoder/y4m"
|
"git.gammaspectra.live/S.O.N.G/Ignite/decoder/y4m"
|
||||||
"git.gammaspectra.live/S.O.N.G/Ignite/testdata"
|
"git.gammaspectra.live/S.O.N.G/Ignite/testdata"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -18,27 +16,11 @@ func TestVersion(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEncode(sample testdata.TestSample, t *testing.T) {
|
func testEncode(sample testdata.TestSample, t *testing.T) {
|
||||||
var reader io.Reader
|
reader, err := sample.Open(t)
|
||||||
var err error
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if _, err = os.Stat(sample.Path); err != nil {
|
|
||||||
if sample.SkipNotFound || sample.Url == "" {
|
|
||||||
t.Skip("skipping without sample")
|
|
||||||
}
|
|
||||||
response, err := http.DefaultClient.Get(sample.Url)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
reader = response.Body
|
|
||||||
} else {
|
|
||||||
f, err := os.Open(sample.Path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
reader = f
|
|
||||||
}
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
var pipe *y4m.Decoder
|
var pipe *y4m.Decoder
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import "C"
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.gammaspectra.live/S.O.N.G/Ignite/color"
|
||||||
"git.gammaspectra.live/S.O.N.G/Ignite/frame"
|
"git.gammaspectra.live/S.O.N.G/Ignite/frame"
|
||||||
"git.gammaspectra.live/S.O.N.G/Ignite/utilities"
|
"git.gammaspectra.live/S.O.N.G/Ignite/utilities"
|
||||||
"io"
|
"io"
|
||||||
|
@ -22,13 +23,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Encoder struct {
|
type Encoder struct {
|
||||||
w io.Writer
|
w io.Writer
|
||||||
cleaned atomic.Bool
|
cleaned atomic.Bool
|
||||||
params C.x264_param_t
|
params C.x264_param_t
|
||||||
pictureIn *C.x264_picture_t
|
pictureIn *C.x264_picture_t
|
||||||
pictureOut C.x264_picture_t
|
pictureOut C.x264_picture_t
|
||||||
h *C.x264_t
|
h *C.x264_t
|
||||||
pinner runtime.Pinner
|
resourcePinner runtime.Pinner
|
||||||
}
|
}
|
||||||
|
|
||||||
var x264Version = fmt.Sprintf("x264 core:%d%s", C.X264_BUILD, C.GoString(C.Version()))
|
var x264Version = fmt.Sprintf("x264 core:%d%s", C.X264_BUILD, C.GoString(C.Version()))
|
||||||
|
@ -107,6 +108,23 @@ func NewEncoder(w io.Writer, properties frame.StreamProperties, settings map[str
|
||||||
e.params.vui.b_fullrange = 0
|
e.params.vui.b_fullrange = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch properties.ColorSpace.ChromaSamplePosition {
|
||||||
|
case color.ChromaSamplePositionUnspecified:
|
||||||
|
e.params.vui.i_chroma_loc = 0 //?
|
||||||
|
case color.ChromaSamplePositionLeft:
|
||||||
|
e.params.vui.i_chroma_loc = 0
|
||||||
|
case color.ChromaSamplePositionCenter:
|
||||||
|
e.params.vui.i_chroma_loc = 1
|
||||||
|
case color.ChromaSamplePositionTopLeft:
|
||||||
|
e.params.vui.i_chroma_loc = 2
|
||||||
|
case color.ChromaSamplePositionTop:
|
||||||
|
e.params.vui.i_chroma_loc = 3
|
||||||
|
case color.ChromaSamplePositionBottomLeft:
|
||||||
|
e.params.vui.i_chroma_loc = 4
|
||||||
|
case color.ChromaSamplePositionBottom:
|
||||||
|
e.params.vui.i_chroma_loc = 5
|
||||||
|
}
|
||||||
|
|
||||||
for k, v := range settings {
|
for k, v := range settings {
|
||||||
if k == "profile" || k == "preset" || k == "tune" {
|
if k == "profile" || k == "preset" || k == "tune" {
|
||||||
continue
|
continue
|
||||||
|
@ -161,9 +179,9 @@ func NewEncoder(w io.Writer, properties frame.StreamProperties, settings map[str
|
||||||
encoder.Close()
|
encoder.Close()
|
||||||
})
|
})
|
||||||
|
|
||||||
if e.pictureIn = (*C.x264_picture_t)(C.malloc(C.size_t(unsafe.Sizeof(C.x264_picture_t{})))); e.pictureIn == nil {
|
e.pictureIn = &C.x264_picture_t{}
|
||||||
return nil, errors.New("error allocating memory")
|
e.resourcePinner.Pin(e.pictureIn)
|
||||||
}
|
|
||||||
if C.x264_picture_alloc(e.pictureIn, e.params.i_csp, e.params.i_width, e.params.i_height) < 0 {
|
if C.x264_picture_alloc(e.pictureIn, e.params.i_csp, e.params.i_width, e.params.i_height) < 0 {
|
||||||
return nil, errors.New("error allocating input picture")
|
return nil, errors.New("error allocating input picture")
|
||||||
}
|
}
|
||||||
|
@ -175,6 +193,10 @@ func NewEncoder(w io.Writer, properties frame.StreamProperties, settings map[str
|
||||||
return nil, errors.New("error opening encoder")
|
return nil, errors.New("error opening encoder")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := e.encoderHeaders(); err != nil {
|
||||||
|
return e, err
|
||||||
|
}
|
||||||
|
|
||||||
return e, nil
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,44 +210,60 @@ func (e *Encoder) EncodeStream(stream *frame.Stream) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Encoder) Encode(f frame.Frame) error {
|
func (e *Encoder) Encode(f frame.Frame) error {
|
||||||
var luma, cb, cr unsafe.Pointer
|
|
||||||
|
|
||||||
switch typedFrame := f.(type) {
|
switch typedFrame := f.(type) {
|
||||||
case frame.TypedFrame[uint8]:
|
case frame.TypedFrame[uint8]:
|
||||||
luma = unsafe.Pointer(unsafe.SliceData(typedFrame.GetNativeLuma()))
|
luma := typedFrame.GetNativeLuma()
|
||||||
cb = unsafe.Pointer(unsafe.SliceData(typedFrame.GetNativeCb()))
|
cb := typedFrame.GetNativeCb()
|
||||||
cr = unsafe.Pointer(unsafe.SliceData(typedFrame.GetNativeCr()))
|
cr := typedFrame.GetNativeCr()
|
||||||
|
copy(unsafe.Slice((*byte)(e.pictureIn.img.plane[0]), len(luma)), luma)
|
||||||
|
copy(unsafe.Slice((*byte)(e.pictureIn.img.plane[1]), len(cb)), cb)
|
||||||
|
copy(unsafe.Slice((*byte)(e.pictureIn.img.plane[2]), len(cr)), cr)
|
||||||
case frame.TypedFrame[uint16]:
|
case frame.TypedFrame[uint16]:
|
||||||
luma = unsafe.Pointer(unsafe.SliceData(typedFrame.GetNativeLuma()))
|
luma := typedFrame.GetNativeLuma()
|
||||||
cb = unsafe.Pointer(unsafe.SliceData(typedFrame.GetNativeCb()))
|
cb := typedFrame.GetNativeCb()
|
||||||
cr = unsafe.Pointer(unsafe.SliceData(typedFrame.GetNativeCr()))
|
cr := typedFrame.GetNativeCr()
|
||||||
|
copy(unsafe.Slice((*uint16)(unsafe.Pointer(e.pictureIn.img.plane[0])), len(luma)), luma)
|
||||||
|
copy(unsafe.Slice((*uint16)(unsafe.Pointer(e.pictureIn.img.plane[1])), len(cb)), cb)
|
||||||
|
copy(unsafe.Slice((*uint16)(unsafe.Pointer(e.pictureIn.img.plane[2])), len(cr)), cr)
|
||||||
default:
|
default:
|
||||||
return errors.New("unknown frame type")
|
return errors.New("unknown frame type")
|
||||||
}
|
}
|
||||||
|
|
||||||
e.pinner.Pin(luma)
|
|
||||||
e.pinner.Pin(cb)
|
|
||||||
e.pinner.Pin(cr)
|
|
||||||
|
|
||||||
e.pictureIn.img.plane[0] = (*C.uint8_t)(luma)
|
|
||||||
e.pictureIn.img.plane[1] = (*C.uint8_t)(cb)
|
|
||||||
e.pictureIn.img.plane[2] = (*C.uint8_t)(cr)
|
|
||||||
|
|
||||||
defer e.pinner.Unpin()
|
|
||||||
|
|
||||||
defer runtime.KeepAlive(f)
|
|
||||||
//cleanup pointers
|
|
||||||
defer func() {
|
|
||||||
e.pictureIn.img.plane[0] = nil
|
|
||||||
e.pictureIn.img.plane[1] = nil
|
|
||||||
e.pictureIn.img.plane[2] = nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
e.pictureIn.i_pts = C.int64_t(f.PTS())
|
e.pictureIn.i_pts = C.int64_t(f.PTS())
|
||||||
|
|
||||||
return e.encoderEncode(e.pictureIn)
|
return e.encoderEncode(e.pictureIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Encoder) encoderHeaders() error {
|
||||||
|
if e.params.b_repeat_headers > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var nal *C.x264_nal_t
|
||||||
|
var iNal C.int
|
||||||
|
var frameSize C.int
|
||||||
|
|
||||||
|
if frameSize = C.x264_encoder_headers(e.h, &nal, &iNal); frameSize < 0 {
|
||||||
|
return errors.New("error encoding headers")
|
||||||
|
}
|
||||||
|
|
||||||
|
if frameSize == 0 || iNal == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if iNal != 1 {
|
||||||
|
//return errors.New("more than one NAL present")
|
||||||
|
}
|
||||||
|
|
||||||
|
// All NAL payloads are sequential to each other in memory
|
||||||
|
buf := unsafe.Slice((*byte)(nal.p_payload), int(frameSize))
|
||||||
|
if _, err := e.w.Write(buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Encoder) encoderEncode(picture *C.x264_picture_t) error {
|
func (e *Encoder) encoderEncode(picture *C.x264_picture_t) error {
|
||||||
var nal *C.x264_nal_t
|
var nal *C.x264_nal_t
|
||||||
var iNal C.int
|
var iNal C.int
|
||||||
|
@ -291,13 +329,13 @@ func (e *Encoder) Close() {
|
||||||
}
|
}
|
||||||
if e.pictureIn != nil {
|
if e.pictureIn != nil {
|
||||||
C.x264_picture_clean(e.pictureIn)
|
C.x264_picture_clean(e.pictureIn)
|
||||||
C.free(unsafe.Pointer(e.pictureIn))
|
|
||||||
e.pictureIn = nil
|
e.pictureIn = nil
|
||||||
}
|
}
|
||||||
if e.params.p_log_private != nil {
|
if e.params.p_log_private != nil {
|
||||||
(cgo.Handle)(e.params.p_log_private).Delete()
|
(cgo.Handle)(e.params.p_log_private).Delete()
|
||||||
e.params.p_log_private = nil
|
e.params.p_log_private = nil
|
||||||
}
|
}
|
||||||
|
e.resourcePinner.Unpin()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ package libx264
|
||||||
import (
|
import (
|
||||||
"git.gammaspectra.live/S.O.N.G/Ignite/decoder/y4m"
|
"git.gammaspectra.live/S.O.N.G/Ignite/decoder/y4m"
|
||||||
"git.gammaspectra.live/S.O.N.G/Ignite/testdata"
|
"git.gammaspectra.live/S.O.N.G/Ignite/testdata"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -18,27 +16,11 @@ func TestVersion(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEncode(sample testdata.TestSample, t *testing.T) {
|
func testEncode(sample testdata.TestSample, t *testing.T) {
|
||||||
var reader io.Reader
|
reader, err := sample.Open(t)
|
||||||
var err error
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
if _, err = os.Stat(sample.Path); err != nil {
|
|
||||||
if sample.SkipNotFound || sample.Url == "" {
|
|
||||||
t.Skip("skipping without sample")
|
|
||||||
}
|
|
||||||
response, err := http.DefaultClient.Get(sample.Url)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
reader = response.Body
|
|
||||||
} else {
|
|
||||||
f, err := os.Open(sample.Path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
reader = f
|
|
||||||
}
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
var pipe *y4m.Decoder
|
var pipe *y4m.Decoder
|
||||||
|
|
||||||
|
@ -116,6 +98,89 @@ func testEncode(sample testdata.TestSample, t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testEncodeMono(sample testdata.TestSample, t *testing.T) {
|
||||||
|
reader, err := sample.Open(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
var pipe *y4m.Decoder
|
||||||
|
|
||||||
|
switch sample.Type {
|
||||||
|
case "y4m":
|
||||||
|
pipe, err = y4m.NewDecoder(reader, nil)
|
||||||
|
case "y4m.xz":
|
||||||
|
pipe, err = y4m.NewXZCompressedDecoder(reader, nil)
|
||||||
|
default:
|
||||||
|
t.Fatal("unsupported sample type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
defer pipe.Close()
|
||||||
|
|
||||||
|
target, err := os.CreateTemp("", "encode_test_*.h264")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
name := target.Name()
|
||||||
|
target.Close()
|
||||||
|
os.Remove(name)
|
||||||
|
}()
|
||||||
|
|
||||||
|
stream := pipe.DecodeStream().Monochrome()
|
||||||
|
|
||||||
|
settings := make(map[string]any)
|
||||||
|
settings["threads"] = runtime.NumCPU()
|
||||||
|
settings["preset"] = "fast"
|
||||||
|
settings["log"] = LogLevelInfo
|
||||||
|
|
||||||
|
if sample.ColorSpace.ChromaSampling.J == 4 && sample.ColorSpace.ChromaSampling.A == 4 && sample.ColorSpace.ChromaSampling.B == 4 {
|
||||||
|
settings["profile"] = "high444"
|
||||||
|
} else if sample.ColorSpace.ChromaSampling.J == 4 && sample.ColorSpace.ChromaSampling.A == 2 && sample.ColorSpace.ChromaSampling.B == 2 {
|
||||||
|
settings["profile"] = "high422"
|
||||||
|
} else if sample.ColorSpace.BitDepth > 8 {
|
||||||
|
settings["profile"] = "high10"
|
||||||
|
} else {
|
||||||
|
settings["profile"] = "high"
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoder, err := NewEncoder(target, stream.Properties(), settings); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
defer encoder.Close()
|
||||||
|
s := stream.Copy(2)
|
||||||
|
encoded := 0
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for range s[0].Channel() {
|
||||||
|
if encoded%10 == 0 {
|
||||||
|
t.Logf("frame %d/%d", encoded, sample.Frames)
|
||||||
|
}
|
||||||
|
encoded++
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = encoder.EncodeStream(s[1]); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if encoded != sample.Frames {
|
||||||
|
t.Fatalf("expected %d frames, got %d", sample.Frames, encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEncode_YUV420_8bit(t *testing.T) {
|
func TestEncode_YUV420_8bit(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("skipping encode test in short mode")
|
t.Skip("skipping encode test in short mode")
|
||||||
|
@ -139,3 +204,19 @@ func TestEncode_YUV444_8bit(t *testing.T) {
|
||||||
|
|
||||||
testEncode(testdata.Y4M_Ducks_Take_Off_720p50_YUV444_8bit, t)
|
testEncode(testdata.Y4M_Ducks_Take_Off_720p50_YUV444_8bit, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEncode_YUV400_8bit(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping encode test in short mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
testEncodeMono(testdata.Y4M_Sintel_Trailer_720p24_YUV420_8bit, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncode_YUV400_10bit(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping encode test in short mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
testEncodeMono(testdata.Y4M_Netflix_FoodMarket_2160p60_YUV420_10bit, t)
|
||||||
|
}
|
||||||
|
|
|
@ -190,3 +190,48 @@ func (s *Stream) Sample(each int) *Stream {
|
||||||
|
|
||||||
return slice
|
return slice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Monochrome makes stream luma-only
|
||||||
|
func (s *Stream) Monochrome() *Stream {
|
||||||
|
if !s.Lock() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
props := s.Properties()
|
||||||
|
props.ColorSpace.ChromaSampling.A = 0
|
||||||
|
props.ColorSpace.ChromaSampling.B = 0
|
||||||
|
|
||||||
|
slice, channel := NewStream(props)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(channel)
|
||||||
|
for f := range s.channel {
|
||||||
|
frameProps := f.Properties()
|
||||||
|
frameProps.ColorSpace.ChromaSampling.A = 0
|
||||||
|
frameProps.ColorSpace.ChromaSampling.B = 0
|
||||||
|
|
||||||
|
switch typedFrame := f.(type) {
|
||||||
|
case TypedFrame[uint8]:
|
||||||
|
channel <- &FrameUint8{
|
||||||
|
properties: frameProps,
|
||||||
|
Pts: typedFrame.PTS(),
|
||||||
|
Y: typedFrame.GetNativeLuma(),
|
||||||
|
Cb: nil,
|
||||||
|
Cr: nil,
|
||||||
|
}
|
||||||
|
case TypedFrame[uint16]:
|
||||||
|
channel <- &FrameUint16{
|
||||||
|
properties: frameProps,
|
||||||
|
Pts: typedFrame.PTS(),
|
||||||
|
Y: typedFrame.GetNativeLuma(),
|
||||||
|
Cb: nil,
|
||||||
|
Cr: nil,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unknown frame type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
27
testdata/testdata.go
vendored
27
testdata/testdata.go
vendored
|
@ -2,9 +2,12 @@ package testdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.gammaspectra.live/S.O.N.G/Ignite/color"
|
"git.gammaspectra.live/S.O.N.G/Ignite/color"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -27,6 +30,30 @@ type TestSample struct {
|
||||||
SkipNotFound bool
|
SkipNotFound bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sample *TestSample) Open(t *testing.T) (io.ReadCloser, error) {
|
||||||
|
var reader io.ReadCloser
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if _, err = os.Stat(sample.Path); err != nil {
|
||||||
|
if sample.SkipNotFound || sample.Url == "" {
|
||||||
|
t.Skip("skipping without sample")
|
||||||
|
}
|
||||||
|
response, err := http.DefaultClient.Get(sample.Url)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
reader = response.Body
|
||||||
|
} else {
|
||||||
|
f, err := os.Open(sample.Path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
reader = f
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Y4M_Sintel_Trailer_720p24_YUV420_8bit = TestSample{
|
Y4M_Sintel_Trailer_720p24_YUV420_8bit = TestSample{
|
||||||
Path: "testdata/sintel_trailer_2k_720p24.y4m",
|
Path: "testdata/sintel_trailer_2k_720p24.y4m",
|
||||||
|
|
Loading…
Reference in a new issue