Compare commits

...

30 Commits

Author SHA1 Message Date
Atsushi Watanabe
4cd2e7dc65 Merge branch 'master' into add-svt-av1-codec 2025-10-23 13:47:20 +09:00
Atsushi Watanabe
5aad703236 Tidy examples/go.mod (#663)
- Run go mod tidy under examples
- Automatically tidy examples/go.mod on Renovate PR
2025-10-23 13:46:16 +09:00
renovate[bot]
e15e8f6880 fix(deps): update module github.com/pion/rtcp to v1.2.16 (#658)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-22 18:57:27 +09:00
renovate[bot]
dd99235d6f fix(deps): update module github.com/pion/rtp to v1.8.24 (#659)
Generated by Renovate Bot

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-22 18:51:47 +09:00
Atsushi Watanabe
a38279006f Update README 2025-10-22 18:48:09 +09:00
Haily Nguyen
a5e2538787 Revert "Add VPX wrappers (#652)" (#662)
This reverts commit a68a5ba4a6.
2025-10-22 02:45:06 -07:00
Atsushi Watanabe
9547f15638 Remove old comment 2025-10-22 00:45:13 +09:00
Atsushi Watanabe
9c9f9e3550 Reduce slice operation 2025-10-22 00:45:13 +09:00
Atsushi Watanabe
3efb8d5f48 Output with pool 2025-10-22 00:45:13 +09:00
Atsushi Watanabe
5e91c919df Add MaximumBufferSize 2025-10-21 13:38:35 +09:00
Atsushi Watanabe
ffe2fcb74d Add params for buffering level 2025-10-21 13:16:15 +09:00
Atsushi Watanabe
77eba99fbc Reapply "Feed frames until receiving a packet"
This reverts commit 4adbe9020c.
2025-10-21 12:09:11 +09:00
Atsushi Watanabe
5468c360f0 Revert "Set pic_send_done"
This reverts commit 057e6a8466.
2025-10-21 12:09:09 +09:00
Atsushi Watanabe
057e6a8466 Set pic_send_done 2025-10-21 11:19:51 +09:00
Atsushi Watanabe
4adbe9020c Revert "Feed frames until receiving a packet"
This reverts commit cb24697808.
2025-10-21 11:18:34 +09:00
Atsushi Watanabe
cb24697808 Feed frames until receiving a packet 2025-10-21 11:07:26 +09:00
Atsushi Watanabe
29cee4bd00 Split send_frame and get_packet 2025-10-21 11:05:56 +09:00
Atsushi Watanabe
5879326c1e Handle empty queue 2025-10-21 10:24:14 +09:00
Atsushi Watanabe
ca4f2fa186 Revert "Feed by other routine"
This reverts commit accb12f0d5.
2025-10-21 10:18:23 +09:00
Atsushi Watanabe
accb12f0d5 Feed by other routine 2025-10-21 10:18:20 +09:00
Atsushi Watanabe
6cbe3de4a3 Update params 2025-10-21 10:18:06 +09:00
Atsushi Watanabe
1258ff726b Read encoded packet 2025-10-21 01:37:54 +09:00
Atsushi Watanabe
89cbba8b77 Feed raw frames 2025-10-21 00:41:43 +09:00
Atsushi Watanabe
a74a31d62a Fix force keyframe 2025-10-20 23:36:25 +09:00
Atsushi Watanabe
807eaefef8 Fix v3 support 2025-10-20 23:22:50 +09:00
Atsushi Watanabe
60ef86b312 Support v3.0.0 2025-10-20 23:10:55 +09:00
Atsushi Watanabe
47ef30e9b3 Apply keyframe interval 2025-10-20 23:05:16 +09:00
Atsushi Watanabe
bf655c675c Fix MacOS CI 2025-10-20 22:44:21 +09:00
Atsushi Watanabe
8840daf7ea Initialize SVT-AV1 codec 2025-10-20 22:37:45 +09:00
Haily Nguyen
a68a5ba4a6 Add VPX wrappers (#652)
* Add VPX improvements from pion-mediadevices

- Add vpx_image.go: VpxImage wrapper for vpx_image_t with convenient methods
- Move BitrateTracker to vpx package: More specific to VPX codec usage
- Add bitrate_tracker_test.go: Test coverage for VPX-specific bitrate tracking
- Remove generic codec-level BitrateTracker: Replaced by VPX-specific version

These changes improve VPX codec functionality and organization by:
1. Adding image handling utilities specific to VPX
2. Providing better bitrate tracking for VPX codecs
3. Improving code organization by moving VPX-specific code to VPX package

* Revert bitrate tracker changes

- Remove vpx-specific bitrate tracker files
- Restore original codec-level bitrate tracker and test
- Keep only the vpx_image.go addition from pion-mediadevices

* Add comprehensive unit tests for VpxImage

- Add vpx_image_test.go with full test coverage for VpxImage wrapper
- Test interface compliance and constructor behavior
- Test nil pointer handling (documents expected panic behavior)
- Test common video format constants and plane indices
- All tests pass and integrate with existing VPX test suite

This improves test coverage for the new VpxImage utility from pion-mediadevices.

* Add comprehensive unit tests for VpxImage

- Add vpx_image_test.go with full test coverage for VpxImage wrapper
- Test interface compliance and constructor behavior
- Test nil pointer handling (documents expected panic behavior)
- Test common video format constants and plane indices
- All tests pass and integrate with existing VPX test suite

This improves test coverage for the new VpxImage utility from pion-mediadevices.
2025-10-09 16:15:33 -07:00
12 changed files with 548 additions and 18 deletions

View File

@@ -27,6 +27,7 @@ jobs:
sudo apt-get update -qq \
&& sudo apt-get install --no-install-recommends -y \
libopus-dev \
libsvtav1enc-dev \
libva-dev \
libvpx-dev \
libx11-dev \
@@ -55,9 +56,10 @@ jobs:
run: |
which brew
brew install \
pkg-config \
opus \
libvpx \
opus \
pkg-config \
svt-av1 \
x264
- name: Run Test Suite
run: make test

View File

@@ -20,4 +20,6 @@ jobs:
github_token: ${{ secrets.PIONBOT_GITHUB_TOKEN }}
commit_style: squash
push: force
go_mod_paths: ./
go_mod_paths: |
./
./examples/

View File

@@ -149,6 +149,14 @@ A codec library which supports H.264 encoding and decoding. It is suitable for u
* Package: [github.com/pion/mediadevices/pkg/codec/openh264](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/openh264)
* Installation: no installation needed, included as a static binary
##### svtav1
A free software video codec library from the Alliance for Open Media that implements AV1 video coding formats.
* Package: [github.com/pion/mediadevices/pkg/codec/svtav1](https://pkg.go.dev/github.com/pion/mediadevices/pkg/codec/svtav1)
* Installation:
* Mac: `brew install svt-av1`
* Ubuntu: `apt install libsvtav1enc-dev`
##### vpx
A free software video codec library from Google and the Alliance for Open Media that implements VP8/VP9 video coding formats.

View File

@@ -10,7 +10,7 @@ require (
require (
github.com/blackjack/webcam v0.6.1 // indirect
github.com/gen2brain/malgo v0.11.23 // indirect
github.com/gen2brain/malgo v0.11.24 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/pion/datachannel v1.5.10 // indirect
github.com/pion/dtls/v3 v3.0.7 // indirect
@@ -19,8 +19,8 @@ require (
github.com/pion/logging v0.2.4 // indirect
github.com/pion/mdns/v2 v2.0.7 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.15 // indirect
github.com/pion/rtp v1.8.23 // indirect
github.com/pion/rtcp v1.2.16 // indirect
github.com/pion/rtp v1.8.24 // indirect
github.com/pion/sctp v1.8.39 // indirect
github.com/pion/sdp/v3 v3.0.16 // indirect
github.com/pion/srtp/v3 v3.0.8 // indirect

View File

@@ -6,8 +6,8 @@ github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44am
github.com/esimov/pigo v1.4.6 h1:wpB9FstbqeGP/CZP+nTR52tUJe7XErq8buG+k4xCXlw=
github.com/esimov/pigo v1.4.6/go.mod h1:uqj9Y3+3IRYhFK071rxz1QYq0ePhA6+R9jrUZavi46M=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/gen2brain/malgo v0.11.23 h1:3/VAI8DP9/Wyx1CUDNlUQJVdWUvGErhjHDqYcHVk9ME=
github.com/gen2brain/malgo v0.11.23/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww=
github.com/gen2brain/malgo v0.11.24 h1:hHcIJVfzWcEDHFdPl5Dl/CUSOjzOleY0zzAV8Kx+imE=
github.com/gen2brain/malgo v0.11.24/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -25,10 +25,10 @@ github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtp v1.8.23 h1:kxX3bN4nM97DPrVBGq5I/Xcl332HnTHeP1Swx3/MCnU=
github.com/pion/rtp v1.8.23/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
github.com/pion/rtp v1.8.24 h1:+ICyZXUQDv95EsHN70RrA4XKJf5MGWyC6QQc1u6/ynI=
github.com/pion/rtp v1.8.24/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo=

4
go.mod
View File

@@ -9,8 +9,8 @@ require (
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018
github.com/pion/interceptor v0.1.41
github.com/pion/logging v0.2.4
github.com/pion/rtcp v1.2.15
github.com/pion/rtp v1.8.23
github.com/pion/rtcp v1.2.16
github.com/pion/rtp v1.8.24
github.com/pion/webrtc/v4 v4.1.5
github.com/stretchr/testify v1.11.1
golang.org/x/image v0.23.0

8
go.sum
View File

@@ -36,10 +36,10 @@ github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtp v1.8.23 h1:kxX3bN4nM97DPrVBGq5I/Xcl332HnTHeP1Swx3/MCnU=
github.com/pion/rtp v1.8.23/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
github.com/pion/rtp v1.8.24 h1:+ICyZXUQDv95EsHN70RrA4XKJf5MGWyC6QQc1u6/ynI=
github.com/pion/rtp v1.8.24/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo=

127
pkg/codec/svtav1/bridge.h Normal file
View File

@@ -0,0 +1,127 @@
#include <EbSvtAv1.h>
#include <EbSvtAv1Enc.h>
#include <EbSvtAv1ErrorCodes.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#define ERR_INIT_ENC_HANDLER 1
#define ERR_SET_ENC_PARAM 2
#define ERR_ENC_INIT 3
#define ERR_SEND_PICTURE 4
#define ERR_GET_PACKET 5
typedef struct Encoder {
EbSvtAv1EncConfiguration *param;
EbComponentType *handle;
EbBufferHeaderType *in_buf;
bool force_keyframe;
} Encoder;
int enc_free(Encoder *e) {
free(e->in_buf->p_buffer);
free(e->in_buf);
free(e->param);
free(e);
return 0;
}
int enc_new(Encoder **e) {
*e = malloc(sizeof(Encoder));
(*e)->param = malloc(sizeof(EbSvtAv1EncConfiguration));
(*e)->in_buf = malloc(sizeof(EbBufferHeaderType));
memset((*e)->in_buf, 0, sizeof(EbBufferHeaderType));
(*e)->in_buf->p_buffer = malloc(sizeof(EbSvtIOFormat));
(*e)->in_buf->size = sizeof(EbBufferHeaderType);
#if SVT_AV1_CHECK_VERSION(3, 0, 0)
const EbErrorType sret = svt_av1_enc_init_handle(&(*e)->handle, (*e)->param);
#else
const EbErrorType sret = svt_av1_enc_init_handle(&(*e)->handle, NULL, (*e)->param);
#endif
if (sret != EB_ErrorNone) {
enc_free(*e);
return ERR_INIT_ENC_HANDLER;
}
return 0;
}
int enc_init(Encoder *e) {
EbErrorType sret;
e->param->encoder_bit_depth = 8;
e->param->encoder_color_format = EB_YUV420;
sret = svt_av1_enc_set_parameter(e->handle, e->param);
if (sret != EB_ErrorNone) {
return ERR_SET_ENC_PARAM;
}
sret = svt_av1_enc_init(e->handle);
if (sret != EB_ErrorNone) {
return ERR_ENC_INIT;
}
return 0;
}
int enc_apply_param(Encoder *e) {
const EbErrorType sret = svt_av1_enc_set_parameter(e->handle, e->param);
if (sret != EB_ErrorNone) {
return ERR_SET_ENC_PARAM;
}
return 0;
}
int enc_force_keyframe(Encoder *e) {
e->force_keyframe = true;
return 0;
}
int enc_send_frame(Encoder *e, uint8_t *y, uint8_t *cb, uint8_t *cr, int ystride, int cstride) {
EbSvtIOFormat *in_data = (EbSvtIOFormat *)e->in_buf->p_buffer;
in_data->luma = y;
in_data->cb = cb;
in_data->cr = cr;
in_data->y_stride = ystride;
in_data->cb_stride = cstride;
in_data->cr_stride = cstride;
e->in_buf->pic_type = EB_AV1_INVALID_PICTURE; // auto
if (e->force_keyframe) {
e->in_buf->pic_type = EB_AV1_KEY_PICTURE;
e->force_keyframe = false;
}
e->in_buf->flags = 0;
e->in_buf->pts++;
e->in_buf->n_filled_len = ystride * e->param->source_height;
e->in_buf->n_filled_len += 2 * cstride * e->param->source_height / 2;
const EbErrorType sret = svt_av1_enc_send_picture(e->handle, e->in_buf);
if (sret != EB_ErrorNone) {
return ERR_SEND_PICTURE;
}
return 0;
}
int enc_get_packet(Encoder *e, EbBufferHeaderType **out) {
const EbErrorType sret = svt_av1_enc_get_packet(e->handle, out, 0);
if (sret == EB_NoErrorEmptyQueue) {
return 0;
}
if (sret != EB_ErrorNone) {
return ERR_GET_PACKET;
}
return 0;
}
void memcpy_uint8(uint8_t *dst, const uint8_t *src, size_t n) {
// Just make CGO types compatible
memcpy(dst, src, n);
}

View File

@@ -0,0 +1,14 @@
package svtav1
import (
"errors"
)
var (
ErrUnknownErrorCode = errors.New("unknown error code")
ErrInitEncHandler = errors.New("failed to initialize encoder handler")
ErrSetEncParam = errors.New("failed to set encoder parameters")
ErrEncInit = errors.New("failed to initialize encoder")
ErrSendPicture = errors.New("failed to send picture")
ErrGetPacket = errors.New("failed to get packet")
)

View File

@@ -0,0 +1,47 @@
package svtav1
import (
"time"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
// Params stores libx264 specific encoding parameters.
type Params struct {
codec.BaseParams
// Preset configuration number of SVT-AV1
// 1-3: extremely high efficiency but heavy
// 4-6: a balance of efficiency and reasonable compute time
// 7-13: real-time encoding
Preset int
StartingBufferLevel time.Duration
OptimalBufferLevel time.Duration
MaximumBufferSize time.Duration
}
// NewParams returns default x264 codec specific parameters.
func NewParams() (Params, error) {
return Params{
BaseParams: codec.BaseParams{
KeyFrameInterval: 60,
},
Preset: 9,
StartingBufferLevel: 400 * time.Millisecond,
OptimalBufferLevel: 200 * time.Millisecond,
MaximumBufferSize: 500 * time.Millisecond,
}, nil
}
// RTPCodec represents the codec metadata
func (p *Params) RTPCodec() *codec.RTPCodec {
return codec.NewRTPAV1Codec(90000)
}
// BuildVideoEncoder builds x264 encoder with given params
func (p *Params) BuildVideoEncoder(r video.Reader, property prop.Media) (codec.ReadCloser, error) {
return newEncoder(r, property, *p)
}

184
pkg/codec/svtav1/svtav1.go Normal file
View File

@@ -0,0 +1,184 @@
// Package svtav1 implements AV1 encoder.
// This package requires libSvtAv1Enc headers and libraries to be built.
package svtav1
// #cgo pkg-config: SvtAv1Enc
// #include "bridge.h"
import "C"
import (
"image"
"io"
"sync"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
type encoder struct {
engine *C.Encoder
r video.Reader
mu sync.Mutex
closed bool
outPool sync.Pool
}
func newEncoder(r video.Reader, p prop.Media, params Params) (codec.ReadCloser, error) {
var enc *C.Encoder
if p.FrameRate == 0 {
p.FrameRate = 30
}
if err := errFromC(C.enc_new(&enc)); err != nil {
return nil, err
}
enc.param.source_width = C.uint32_t(p.Width)
enc.param.source_height = C.uint32_t(p.Height)
enc.param.profile = C.MAIN_PROFILE
enc.param.enc_mode = C.int8_t(params.Preset)
enc.param.rate_control_mode = C.SVT_AV1_RC_MODE_CBR
enc.param.pred_structure = C.SVT_AV1_PRED_LOW_DELAY_B
enc.param.target_bit_rate = C.uint32_t(params.BitRate)
enc.param.frame_rate_numerator = C.uint32_t(p.FrameRate * 1000)
enc.param.frame_rate_denominator = 1000
enc.param.intra_refresh_type = C.SVT_AV1_KF_REFRESH
enc.param.intra_period_length = C.int32_t(params.KeyFrameInterval)
enc.param.starting_buffer_level_ms = C.int64_t(params.StartingBufferLevel.Milliseconds())
enc.param.optimal_buffer_level_ms = C.int64_t(params.OptimalBufferLevel.Milliseconds())
enc.param.maximum_buffer_size_ms = C.int64_t(params.MaximumBufferSize.Milliseconds())
if err := errFromC(C.enc_init(enc)); err != nil {
_ = C.enc_free(enc)
return nil, err
}
e := encoder{
engine: enc,
r: video.ToI420(r),
outPool: sync.Pool{
New: func() any {
return []byte(nil)
},
},
}
return &e, nil
}
func errFromC(ret C.int) error {
switch ret {
case 0:
return nil
case C.ERR_INIT_ENC_HANDLER:
return ErrInitEncHandler
case C.ERR_SET_ENC_PARAM:
return ErrSetEncParam
case C.ERR_ENC_INIT:
return ErrEncInit
case C.ERR_SEND_PICTURE:
return ErrSendPicture
case C.ERR_GET_PACKET:
return ErrGetPacket
default:
return ErrUnknownErrorCode
}
}
func (e *encoder) Read() ([]byte, func(), error) {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil, func() {}, io.EOF
}
for {
img, release, err := e.r.Read()
if err != nil {
return nil, func() {}, err
}
defer release()
yuvImg := img.(*image.YCbCr)
if err := errFromC(C.enc_send_frame(
e.engine,
(*C.uchar)(&yuvImg.Y[0]),
(*C.uchar)(&yuvImg.Cb[0]),
(*C.uchar)(&yuvImg.Cr[0]),
C.int(yuvImg.YStride),
C.int(yuvImg.CStride),
)); err != nil {
return nil, func() {}, err
}
var buf *C.EbBufferHeaderType
if err := errFromC(C.enc_get_packet(e.engine, &buf)); err != nil {
return nil, func() {}, err
}
if buf == nil {
// Feed frames until receiving a packet
continue
}
n := int(buf.n_filled_len)
outBuf := e.outPool.Get().([]byte)
if cap(outBuf) < n {
outBuf = make([]byte, n)
} else {
outBuf = outBuf[:n]
}
C.memcpy_uint8((*C.uchar)(&outBuf[0]), buf.p_buffer, C.size_t(n))
C.svt_av1_enc_release_out_buffer(&buf)
return outBuf, func() {
e.outPool.Put(outBuf)
}, err
}
}
func (e *encoder) ForceKeyFrame() error {
e.mu.Lock()
defer e.mu.Unlock()
if err := errFromC(C.enc_force_keyframe(e.engine)); err != nil {
return err
}
return nil
}
func (e *encoder) SetBitRate(bitrate int) error {
e.mu.Lock()
defer e.mu.Unlock()
e.engine.param.target_bit_rate = C.uint32_t(bitrate)
if err := errFromC(C.enc_apply_param(e.engine)); err != nil {
return err
}
return nil
}
func (e *encoder) Controller() codec.EncoderController {
return e
}
func (e *encoder) Close() error {
e.mu.Lock()
defer e.mu.Unlock()
if e.closed {
return nil
}
if err := errFromC(C.enc_free(e.engine)); err != nil {
return err
}
e.closed = true
return nil
}

View File

@@ -0,0 +1,146 @@
package svtav1
import (
"image"
"testing"
"github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/codec/internal/codectest"
"github.com/pion/mediadevices/pkg/frame"
"github.com/pion/mediadevices/pkg/io/video"
"github.com/pion/mediadevices/pkg/prop"
)
func getTestVideoEncoder() (codec.ReadCloser, error) {
p, err := NewParams()
if err != nil {
return nil, err
}
p.BitRate = 200000
enc, err := p.BuildVideoEncoder(video.ReaderFunc(func() (image.Image, func(), error) {
return image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
), nil, nil
}), prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
})
if err != nil {
return nil, err
}
return enc, nil
}
func TestEncoder(t *testing.T) {
t.Run("SimpleRead", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
p.BitRate = 200000
codectest.VideoEncoderSimpleReadTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
t.Run("CloseTwice", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
p.BitRate = 200000
codectest.VideoEncoderCloseTwiceTest(t, &p, prop.Media{
Video: prop.Video{
Width: 640,
Height: 480,
FrameRate: 30,
FrameFormat: frame.FormatI420,
},
})
})
t.Run("ReadAfterClose", func(t *testing.T) {
p, err := NewParams()
if err != nil {
t.Fatal(err)
}
p.BitRate = 200000
codectest.VideoEncoderReadAfterCloseTest(t, &p,
prop.Media{
Video: prop.Video{
Width: 256,
Height: 144,
FrameFormat: frame.FormatI420,
},
},
image.NewYCbCr(
image.Rect(0, 0, 256, 144),
image.YCbCrSubsampleRatio420,
),
)
})
}
func TestShouldImplementKeyFrameControl(t *testing.T) {
e := &encoder{}
if _, ok := e.Controller().(codec.KeyFrameController); !ok {
t.Error()
}
}
func TestNoErrorOnForceKeyFrame(t *testing.T) {
enc, err := getTestVideoEncoder()
if err != nil {
t.Fatal(err)
}
kfc, ok := enc.Controller().(codec.KeyFrameController)
if !ok {
t.Fatal("Failed to get KeyFrameController")
}
if err := kfc.ForceKeyFrame(); err != nil {
t.Error(err)
}
_, rel, err := enc.Read() // try to read the encoded frame
rel()
if err != nil {
t.Fatal(err)
}
}
func TestShouldImplementBitRateControl(t *testing.T) {
e := &encoder{}
if _, ok := e.Controller().(codec.BitRateController); !ok {
t.Error()
}
}
func TestNoErrorOnSetBitRate(t *testing.T) {
enc, err := getTestVideoEncoder()
if err != nil {
t.Fatal(err)
}
brc, ok := enc.Controller().(codec.BitRateController)
if !ok {
t.Fatal("Failed to get BitRateController")
}
if err := brc.SetBitRate(1000); err != nil { // 1000 bit/second is ridiculously low, but this is a testcase.
t.Error(err)
}
_, rel, err := enc.Read() // try to read the encoded frame
rel()
if err != nil {
t.Fatal(err)
}
}