diff --git a/pkg/codec/vpx/vpx_decoder.go b/pkg/codec/vpx/vpx_decoder.go new file mode 100644 index 0000000..ce761e9 --- /dev/null +++ b/pkg/codec/vpx/vpx_decoder.go @@ -0,0 +1,107 @@ +package vpx + +/* +#cgo pkg-config: vpx +#include +#include +#include +#include +#include +#include + +vpx_codec_iface_t *ifaceVP8Decoder() { + return vpx_codec_vp8_dx(); +} +vpx_codec_iface_t *ifaceVP9Decoder() { + return vpx_codec_vp9_dx(); +} + +// Allocates a new decoder context +vpx_codec_ctx_t* newDecoderCtx() { + return (vpx_codec_ctx_t*)malloc(sizeof(vpx_codec_ctx_t)); +} + +// Initializes the decoder +vpx_codec_err_t decoderInit(vpx_codec_ctx_t* ctx, vpx_codec_iface_t* iface) { + return vpx_codec_dec_init_ver(ctx, iface, NULL, 0, VPX_DECODER_ABI_VERSION); +} + +// Decodes an encoded frame +vpx_codec_err_t decodeFrame(vpx_codec_ctx_t* ctx, const uint8_t* data, unsigned int data_sz) { + return vpx_codec_decode(ctx, data, data_sz, NULL, 0); +} + +// Creates an iterator +vpx_codec_iter_t* newIter() { + return (vpx_codec_iter_t*)malloc(sizeof(vpx_codec_iter_t)); +} + +// Returns the next decoded frame +vpx_image_t* getFrame(vpx_codec_ctx_t* ctx, vpx_codec_iter_t* iter) { + return vpx_codec_get_frame(ctx, iter); +} + +// Frees a decoder context +void freeDecoderCtx(vpx_codec_ctx_t* ctx) { + vpx_codec_destroy(ctx); + free(ctx); +} + +*/ +import "C" +import ( + "fmt" + "sync" + "time" + + "github.com/pion/mediadevices/pkg/prop" +) + +type Decoder struct { + codec *C.vpx_codec_ctx_t + raw *C.vpx_image_t + cfg *C.vpx_codec_dec_cfg_t + frameIndex int + tStart time.Time + tLastFrame time.Time + + mu sync.Mutex + closed bool +} + +func NewDecoder(p prop.Media) (Decoder, error) { + cfg := &C.vpx_codec_dec_cfg_t{} + cfg.threads = 1 + cfg.w = C.uint(p.Width) + cfg.h = C.uint(p.Height) + + codec := C.newDecoderCtx() + if C.decoderInit(codec, C.ifaceVP8Decoder()) != C.VPX_CODEC_OK { + return Decoder{}, fmt.Errorf("vpx_codec_dec_init failed") + } + + return Decoder{ + codec: codec, + cfg: cfg, + }, nil +} + +func (d *Decoder) Decode(data []byte) { + if len(data) == 0 { + return + } + status := C.decodeFrame(d.codec, (*C.uint8_t)(&data[0]), C.uint(len(data))) + if status != C.VPX_CODEC_OK { + fmt.Println("Decode failed", status) + panic("Decode failed") + } +} + +func (d *Decoder) GetFrame() *C.vpx_image_t { + iter := C.newIter() + return C.getFrame(d.codec, iter) +} + +func (d *Decoder) FreeDecoderCtx() { + C.freeDecoderCtx(d.codec) +} diff --git a/pkg/codec/vpx/vpx_test.go b/pkg/codec/vpx/vpx_test.go index eb1b6bb..7b36ca8 100644 --- a/pkg/codec/vpx/vpx_test.go +++ b/pkg/codec/vpx/vpx_test.go @@ -425,3 +425,78 @@ func TestVP8DynamicQPControl(t *testing.T) { assert.Less(t, math.Abs(float64(targetBitrate-currentBitrate)), math.Abs(float64(initialBitrate-currentBitrate))) }) } + +func TestVP8EncodeDecode(t *testing.T) { + t.Run("VP8", func(t *testing.T) { + initialWidth, initialHeight := 800, 600 + decoder, err := NewDecoder(prop.Media{ + Video: prop.Video{ + Width: initialWidth, + Height: initialHeight, + FrameFormat: frame.FormatI420, + }, + }) + if err != nil { + t.Fatalf("Error creating VP8 decoder: %v", err) + } + defer decoder.FreeDecoderCtx() + + // [... encoder setup code ...] + p, err := NewVP8Params() + if err != nil { + t.Fatal(err) + } + p.LagInFrames = 0 // Disable frame lag buffering for real-time encoding + p.RateControlEndUsage = RateControlCBR + totalFrames := 100 + var cnt uint32 + r, err := p.BuildVideoEncoder( + video.ReaderFunc(func() (image.Image, func(), error) { + i := atomic.AddUint32(&cnt, 1) + if i == uint32(totalFrames+1) { + return nil, nil, io.EOF + } + img := image.NewYCbCr(image.Rect(0, 0, initialWidth, initialHeight), image.YCbCrSubsampleRatio420) + return img, func() {}, nil + }), + prop.Media{ + Video: prop.Video{ + Width: initialWidth, + Height: initialHeight, + FrameRate: 30, + FrameFormat: frame.FormatI420, + }, + }, + ) + if err != nil { + t.Fatal(err) + } + + data, rel, err := r.Read() + if err != nil { + t.Fatal(err) + } + defer rel() + + // Decode the frame + decoder.Decode(data) + + // Poll for frame with timeout + timeout := time.After(2 * time.Second) + ticker := time.NewTicker(10 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-timeout: + t.Fatal("Timeout: No frame received within 2 seconds") + case <-ticker.C: + frame := decoder.GetFrame() + if frame != nil { + t.Log("Successfully received and decoded frame") + return // Test passes + } + } + } + }) +}