Repository: pion/mediadevices (go)

Issue:
TestMeasureBitRate sometimes fails
```
=== RUN   TestMeasureBitRateDynamic
##[error]    TestMeasureBitRateDynamic: measurement_test.go:95: expected: 25600.000000 (with 8.000000 precision), but got 25585.959280
--- FAIL: TestMeasureBitRateDynamic (5.00s)
```
https://github.com/pion/mediadevices/pull/139/checks?check_run_id=598870347 (restarted)


Pull request description:
Fix bitrate measurement stability
- improve accuracy of bitrate calculation
- reduce test input timing error

fix #144 

Patch:
diff --git a/internal/codec/measurement.go b/internal/codec/measurement.go
index c14e45b..3010aa7 100644
--- a/internal/codec/measurement.go
+++ b/internal/codec/measurement.go
@@ -11,12 +11,15 @@ import (
 func MeasureBitRate(r io.Reader, dur time.Duration) (float64, error) {
 	var n, totalBytes int
 	var err error
+
 	buf := make([]byte, 1024)
 	start := time.Now()
 	now := start
 	end := now.Add(dur)
-	for now.Before(end) {
+	for {
 		n, err = r.Read(buf)
+		now = time.Now()
+
 		if err != nil {
 			if e, ok := err.(*mio.InsufficientBufferError); ok {
 				buf = make([]byte, 2*e.RequiredSize)
@@ -24,6 +27,7 @@ func MeasureBitRate(r io.Reader, dur time.Duration) (float64, error) {
 			}
 
 			if err == io.EOF {
+				dur = now.Sub(start)
 				totalBytes += n
 				break
 			}
@@ -31,11 +35,12 @@ func MeasureBitRate(r io.Reader, dur time.Duration) (float64, error) {
 			return 0, err
 		}
 
-		totalBytes += n
-		now = time.Now()
+		if now.After(end) {
+			break
+		}
+		totalBytes += n // count bytes if the data arrived within the period
 	}
 
-	elapsed := time.Now().Sub(start).Seconds()
-	avg := float64(totalBytes*8) / elapsed
+	avg := float64(totalBytes*8) / dur.Seconds()
 	return avg, nil
 }


Test patch:
diff --git a/internal/codec/measurement_test.go b/internal/codec/measurement_test.go
index 0b2d711..8796089 100644
--- a/internal/codec/measurement_test.go
+++ b/internal/codec/measurement_test.go
@@ -9,18 +9,25 @@ import (
 
 func TestMeasureBitRateStatic(t *testing.T) {
 	r, w := io.Pipe()
-	dur := time.Second * 5
-	dataSize := 1000
-	var precision float64 = 8 // 1 byte
+	const (
+		dataSize       = 1000
+		dur            = 5 * time.Second
+		packetInterval = time.Second
+		precision      = 8.0 // 1 byte
+	)
 	var wg sync.WaitGroup
 	wg.Add(1)
 	done := make(chan struct{})
 
 	go func() {
 		data := make([]byte, dataSize)
+		ticker := time.NewTicker(packetInterval)
+
+		// Wait half interval
+		time.Sleep(packetInterval / 2)
+
 		// Make sure that this goroutine is synchronized with main goroutine
 		wg.Done()
-		ticker := time.NewTicker(time.Second)
 
 		for {
 			select {
@@ -48,30 +55,34 @@ func TestMeasureBitRateStatic(t *testing.T) {
 
 func TestMeasureBitRateDynamic(t *testing.T) {
 	r, w := io.Pipe()
-	dur := time.Second * 5
-	dataSize := 1000
-	var precision float64 = 8 // 1 byte
+	const (
+		dataSize       = 1000
+		dur            = 5 * time.Second
+		packetInterval = time.Millisecond * 250
+		precision      = 8.0 // 1 byte
+	)
 	var wg sync.WaitGroup
 	wg.Add(1)
 	done := make(chan struct{})
 
 	go func() {
 		data := make([]byte, dataSize)
+		ticker := time.NewTicker(packetInterval)
+
+		// Wait half interval
+		time.Sleep(packetInterval / 2)
+
 		wg.Done()
-		ticker := time.NewTicker(time.Millisecond * 500)
-		var count int
 
+		var count int
 		for {
 			select {
 			case <-ticker.C:
-				w.Write(data)
-				count++
-				// Wait until 4 slow ticks, which is also equal to 2 seconds
-				if count == 4 {
-					ticker.Stop()
-					// Speed up the tick by 2 times for the rest
-					ticker = time.NewTicker(time.Millisecond * 250)
+				// 4 x 500ms ticks and 250ms ticks
+				if count%2 == 1 || count >= 8 {
+					w.Write(data)
 				}
+				count++
 			case <-done:
 				w.Close()
 				return