diff --git a/pkg/codec/vpx/bitrate_tracker.go b/pkg/codec/vpx/bitrate_tracker.go new file mode 100644 index 0000000..8abe512 --- /dev/null +++ b/pkg/codec/vpx/bitrate_tracker.go @@ -0,0 +1,48 @@ +package vpx + +import ( + "time" +) + +type BitrateTracker struct { + windowSize time.Duration + buffer []int + times []time.Time +} + +func NewBitrateTracker(windowSize time.Duration) *BitrateTracker { + return &BitrateTracker{ + windowSize: windowSize, + } +} + +func (bt *BitrateTracker) AddFrame(sizeBytes int, timestamp time.Time) { + bt.buffer = append(bt.buffer, sizeBytes) + bt.times = append(bt.times, timestamp) + + // Remove old entries outside the window + cutoff := timestamp.Add(-bt.windowSize) + i := 0 + for ; i < len(bt.times); i++ { + if bt.times[i].After(cutoff) { + break + } + } + bt.buffer = bt.buffer[i:] + bt.times = bt.times[i:] +} + +func (bt *BitrateTracker) GetBitrate() float64 { + if len(bt.times) < 2 { + return 0 + } + totalBytes := 0 + for _, b := range bt.buffer { + totalBytes += b + } + duration := bt.times[len(bt.times)-1].Sub(bt.times[0]).Seconds() + if duration <= 0 { + return 0 + } + return float64(totalBytes*8) / duration // bits per second +} diff --git a/pkg/codec/vpx/bitrate_tracker_test.go b/pkg/codec/vpx/bitrate_tracker_test.go new file mode 100644 index 0000000..6a1da7d --- /dev/null +++ b/pkg/codec/vpx/bitrate_tracker_test.go @@ -0,0 +1,19 @@ +package vpx + +import ( + "math" + "testing" + "time" +) + +func TestBitrateTracker(t *testing.T) { + packetSize := 1000 + bt := NewBitrateTracker(time.Second) + bt.AddFrame(packetSize, time.Now()) + bt.AddFrame(packetSize, time.Now().Add(time.Millisecond*100)) + bt.AddFrame(packetSize, time.Now().Add(time.Millisecond*999)) + eps := float64(packetSize*8) / 10 + if got, want := bt.GetBitrate(), float64(packetSize*8)*3; math.Abs(got-want) > eps { + t.Fatalf("GetBitrate() = %v, want %v (|diff| <= %v)", got, want, eps) + } +} diff --git a/pkg/codec/vpx/vpx_image.go b/pkg/codec/vpx/vpx_image.go new file mode 100644 index 0000000..5ba4dae --- /dev/null +++ b/pkg/codec/vpx/vpx_image.go @@ -0,0 +1,40 @@ +package vpx + +/* +#cgo pkg-config: vpx +#include +*/ +import "C" +import "unsafe" + +type VpxImage struct { + img *C.vpx_image_t +} + +func NewImageFromPtr(ptr *C.vpx_image_t) *VpxImage { + return &VpxImage{img: ptr} +} + +func (i *VpxImage) Width() int { + return int(i.img.d_w) +} + +func (i *VpxImage) Height() int { + return int(i.img.d_h) +} + +func (i *VpxImage) YStride() int { + return int(i.img.stride[0]) +} + +func (i *VpxImage) UStride() int { + return int(i.img.stride[1]) +} + +func (i *VpxImage) VStride() int { + return int(i.img.stride[2]) +} + +func (i *VpxImage) Plane(n int) unsafe.Pointer { + return unsafe.Pointer(i.img.planes[n]) +} diff --git a/pkg/codec/vpx/vpx_image_test.go b/pkg/codec/vpx/vpx_image_test.go new file mode 100644 index 0000000..e54d3a1 --- /dev/null +++ b/pkg/codec/vpx/vpx_image_test.go @@ -0,0 +1,94 @@ +package vpx + +import ( + "testing" + "unsafe" +) + +// TestVpxImageStructure tests the VpxImage struct methods +// Note: These tests verify the interface and structure without requiring actual VPX images +func TestVpxImageStructure(t *testing.T) { + // Test that VpxImage can be created (interface test) + // We can't easily test with real C structures in unit tests due to CGO limitations + // but we can test the structure and interface + + t.Run("VpxImageInterface", func(t *testing.T) { + // This test ensures the VpxImage type exists and has the expected methods + // We use a type assertion to verify the interface + var _ interface { + Width() int + Height() int + YStride() int + UStride() int + VStride() int + Plane(int) unsafe.Pointer + } = (*VpxImage)(nil) + }) +} + +// TestNewImageFromPtr tests the constructor +func TestNewImageFromPtr(t *testing.T) { + // Test with nil pointer + vpxImg := NewImageFromPtr(nil) + if vpxImg == nil { + t.Error("NewImageFromPtr should not return nil even with nil input") + } + if vpxImg != nil && vpxImg.img != nil { + t.Error("VpxImage should contain nil pointer when created with nil") + } +} + +// TestVpxImageMethodsWithNil tests that methods panic appropriately with nil pointer +// This documents the expected behavior - methods will panic if called with nil C pointer +func TestVpxImageMethodsWithNil(t *testing.T) { + vpxImg := NewImageFromPtr(nil) + + // These methods should panic with nil img (this is expected behavior) + testCases := []struct { + name string + fn func() + }{ + {"Width", func() { vpxImg.Width() }}, + {"Height", func() { vpxImg.Height() }}, + {"YStride", func() { vpxImg.YStride() }}, + {"UStride", func() { vpxImg.UStride() }}, + {"VStride", func() { vpxImg.VStride() }}, + {"Plane0", func() { vpxImg.Plane(0) }}, + {"Plane1", func() { vpxImg.Plane(1) }}, + {"Plane2", func() { vpxImg.Plane(2) }}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Method %s should panic with nil image but didn't", tc.name) + } + }() + tc.fn() + }) + } +} + +// TestVpxImageConstants tests expected behavior with common video formats +func TestVpxImageConstants(t *testing.T) { + // Test that the VpxImage type can be used in common video processing scenarios + testCases := []struct { + name string + planeIndex int + description string + }{ + {"Y Plane", 0, "Luma plane"}, + {"U Plane", 1, "Chroma U plane"}, + {"V Plane", 2, "Chroma V plane"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Verify plane indices are within expected range + if tc.planeIndex < 0 || tc.planeIndex > 2 { + t.Errorf("Plane index %d is out of expected range [0-2]", tc.planeIndex) + } + }) + } +}