feat(gateway): relay complete encoded frames
This commit is contained in:
@@ -40,7 +40,7 @@ import (
|
||||
|
||||
const (
|
||||
qualificationToolVersion = "versevdi-gateway-qualification/v6"
|
||||
qualificationImpairmentQueuePackets = 256
|
||||
qualificationImpairmentQueuePackets = nativeApolloVideoQueuePackets
|
||||
qualificationImpairmentMaxPackets = 100_000
|
||||
qualificationImpairmentPacketCount = 10_000
|
||||
qualificationProcessingLimit = 5 * time.Millisecond
|
||||
@@ -54,6 +54,7 @@ type qualificationMediaProfile struct {
|
||||
Name string
|
||||
Codec string
|
||||
BitrateKbps int64
|
||||
FPS int
|
||||
Duration time.Duration
|
||||
Warmup time.Duration
|
||||
PacketBytes int
|
||||
@@ -73,7 +74,7 @@ type qualificationPathTrace struct {
|
||||
}
|
||||
|
||||
type qualificationPath struct {
|
||||
client *Client
|
||||
client *independentGatewayClient
|
||||
server *Server
|
||||
session *nativeApolloSession
|
||||
fixture *qualificationApolloFixture
|
||||
@@ -102,6 +103,11 @@ type qualificationProcessingSummary struct {
|
||||
Codec string `json:"codec"`
|
||||
ConfiguredBitrateKbps int64 `json:"configured_bitrate_kbps"`
|
||||
ObservedBitrateKbps float64 `json:"observed_bitrate_kbps"`
|
||||
ConfiguredFPS int `json:"configured_fps"`
|
||||
ObservedFPS float64 `json:"observed_fps"`
|
||||
PayloadBytes int64 `json:"payload_bytes"`
|
||||
MinimumFrameBytes int `json:"minimum_frame_bytes"`
|
||||
MaximumFrameBytes int `json:"maximum_frame_bytes"`
|
||||
Warmup time.Duration `json:"warmup_ns"`
|
||||
ConfiguredDuration time.Duration `json:"configured_duration_ns"`
|
||||
ActualDuration time.Duration `json:"actual_duration_ns"`
|
||||
@@ -154,6 +160,7 @@ type qualificationImpairmentObservation struct {
|
||||
GatewayForwarded int `json:"gateway_forwarded"`
|
||||
GatewayDropped int `json:"gateway_dropped"`
|
||||
QUICSent int `json:"quic_sent"`
|
||||
QUICDatagramsSent int `json:"quic_datagrams_sent"`
|
||||
QUICSendDropped int `json:"quic_send_dropped"`
|
||||
ClientDeliveryDropped int `json:"client_delivery_dropped"`
|
||||
UnexplainedDropped int `json:"unexplained_dropped"`
|
||||
@@ -241,9 +248,9 @@ type qualificationManifest struct {
|
||||
|
||||
func qualificationMediaProfiles() []qualificationMediaProfile {
|
||||
return []qualificationMediaProfile{
|
||||
{Name: "1080p60-h264", Codec: "h264", BitrateKbps: 20000, Duration: 10 * time.Minute, Warmup: time.Second, PacketBytes: 1179},
|
||||
{Name: "1440p120-hevc", Codec: "hevc", BitrateKbps: 50000, Duration: 10 * time.Minute, Warmup: time.Second, PacketBytes: 1179},
|
||||
{Name: "4k60-hevc", Codec: "hevc", BitrateKbps: 80000, Duration: 10 * time.Minute, Warmup: time.Second, PacketBytes: 1179},
|
||||
{Name: "1080p60-h264", Codec: "h264", BitrateKbps: 20000, FPS: 60, Duration: 10 * time.Minute, Warmup: time.Second, PacketBytes: 1179},
|
||||
{Name: "1440p120-hevc", Codec: "hevc", BitrateKbps: 50000, FPS: 120, Duration: 10 * time.Minute, Warmup: time.Second, PacketBytes: 1179},
|
||||
{Name: "4k60-hevc", Codec: "hevc", BitrateKbps: 80000, FPS: 60, Duration: 10 * time.Minute, Warmup: time.Second, PacketBytes: 1179},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,9 +285,65 @@ func qualificationPayload(profile qualificationMediaProfile) []byte {
|
||||
return payload
|
||||
}
|
||||
|
||||
func qualificationFrameRate(profile qualificationMediaProfile) int {
|
||||
if profile.FPS > 0 {
|
||||
return profile.FPS
|
||||
}
|
||||
return 60
|
||||
}
|
||||
|
||||
func qualificationFrameSize(profile qualificationMediaProfile, index int64) int {
|
||||
fps := qualificationFrameRate(profile)
|
||||
bytesPerSecond := int(profile.BitrateKbps * 1000 / 8)
|
||||
position := int(index % int64(fps))
|
||||
if fps == 1 {
|
||||
return min(bytesPerSecond, maxCompleteFrameBytes)
|
||||
}
|
||||
keyframeBytes := min(bytesPerSecond/fps*4, maxCompleteFrameBytes)
|
||||
if position == 0 {
|
||||
return keyframeBytes
|
||||
}
|
||||
remaining := bytesPerSecond - keyframeBytes
|
||||
size := remaining / (fps - 1)
|
||||
if position <= remaining%(fps-1) {
|
||||
size++
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func qualificationFramePayload(profile qualificationMediaProfile, index int64) []byte {
|
||||
payload := make([]byte, qualificationFrameSize(profile, index))
|
||||
if profile.Codec == "h264" {
|
||||
copy(payload, []byte{0, 0, 1, 0x65})
|
||||
} else {
|
||||
copy(payload, []byte{0, 0, 1, 0x26})
|
||||
}
|
||||
for offset := 4; offset < len(payload); offset++ {
|
||||
payload[offset] = byte(int64(offset)*31 + index*17 + int64(len(profile.Name)))
|
||||
}
|
||||
if len(payload) >= 8 {
|
||||
binary.BigEndian.PutUint64(payload[len(payload)-8:], uint64(index))
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func qualificationMediaPacerKbps(profile qualificationMediaProfile, reduction int) int64 {
|
||||
payloadKbps := profile.BitrateKbps * int64(100-reduction) / 100
|
||||
return (payloadKbps*int64(profile.PacketBytes+frameHeaderSize) + int64(profile.PacketBytes) - 1) / int64(profile.PacketBytes)
|
||||
datagrams := (profile.PacketBytes + frameV2PayloadSize - 1) / frameV2PayloadSize
|
||||
wireBytes := profile.PacketBytes + datagrams*frameV2HeaderSize
|
||||
return (payloadKbps*int64(wireBytes) + int64(profile.PacketBytes) - 1) / int64(profile.PacketBytes)
|
||||
}
|
||||
|
||||
func qualificationFramePacerKbps(profile qualificationMediaProfile) int64 {
|
||||
fps := qualificationFrameRate(profile)
|
||||
var payloadBytes, wireBytes int64
|
||||
for index := range fps {
|
||||
size := qualificationFrameSize(profile, int64(index))
|
||||
datagrams := (size + frameV2PayloadSize - 1) / frameV2PayloadSize
|
||||
payloadBytes += int64(size)
|
||||
wireBytes += int64(size + datagrams*frameV2HeaderSize)
|
||||
}
|
||||
return (profile.BitrateKbps*wireBytes + payloadBytes - 1) / payloadBytes
|
||||
}
|
||||
|
||||
func qualificationBoundedRelease(target, next, now time.Time, spacing time.Duration) time.Time {
|
||||
@@ -767,7 +830,7 @@ type qualificationFleet struct {
|
||||
t *testing.T
|
||||
server *Server
|
||||
paths []*qualificationPath
|
||||
clients []*Client
|
||||
clients []*independentGatewayClient
|
||||
cancel context.CancelFunc
|
||||
serveDone chan error
|
||||
closeOnce sync.Once
|
||||
@@ -812,7 +875,7 @@ func newQualificationFleet(t *testing.T, count int, profile qualificationMediaPr
|
||||
Grant: strings.Repeat(string(rune('a'+index)), 64), ClientNonce: fmt.Sprintf("nonce-fleet-%06d", index),
|
||||
DeviceSignature: strings.Repeat("s", 86), Capabilities: DefaultCapabilities(),
|
||||
}
|
||||
client, err := Dial(context.Background(), server.Addr().String(), clientTLS, request)
|
||||
client, err := dialIndependentGateway(context.Background(), server.Addr().String(), clientTLS, request)
|
||||
if err != nil {
|
||||
fleet.Close()
|
||||
t.Fatal(err)
|
||||
@@ -894,7 +957,7 @@ func newQualificationPathWithImpairment(t *testing.T, profile qualificationMedia
|
||||
ClientNonce: "nonce-qualification", DeviceSignature: strings.Repeat("s", 86),
|
||||
Capabilities: DefaultCapabilities(),
|
||||
}
|
||||
client, err := Dial(context.Background(), server.Addr().String(), clientTLS, request)
|
||||
client, err := dialIndependentGateway(context.Background(), server.Addr().String(), clientTLS, request)
|
||||
if err != nil {
|
||||
cancel()
|
||||
_ = server.Close()
|
||||
@@ -941,7 +1004,7 @@ func newQualificationProcessingPath(t *testing.T, profile qualificationMediaProf
|
||||
ClientNonce: "nonce-qualification", DeviceSignature: strings.Repeat("s", 86),
|
||||
Capabilities: DefaultCapabilities(),
|
||||
}
|
||||
client, err := Dial(context.Background(), process.ready.GatewayAddress, clientTLS, request)
|
||||
client, err := dialIndependentGateway(context.Background(), process.ready.GatewayAddress, clientTLS, request)
|
||||
if err != nil {
|
||||
process.Close()
|
||||
t.Fatal(err)
|
||||
@@ -1007,7 +1070,7 @@ func (p *qualificationPath) traverse(t *testing.T, payload []byte) (qualificatio
|
||||
|
||||
func (p *qualificationPath) emit(t *testing.T, payload []byte) (qualificationPathTrace, error) {
|
||||
t.Helper()
|
||||
if p == nil || p.fixture == nil || len(payload) == 0 || len(payload) > 2*apolloVideoShardPayloadSize-8 {
|
||||
if p == nil || p.fixture == nil || len(payload) == 0 || len(payload) > apolloVideoMaximumBlocks*apolloVideoMaximumDataShards*apolloVideoShardPayloadSize-8 {
|
||||
return qualificationPathTrace{}, ErrProviderMalformed
|
||||
}
|
||||
p.frame++
|
||||
@@ -1024,60 +1087,50 @@ func (p *qualificationPath) emit(t *testing.T, payload []byte) (qualificationPat
|
||||
func (p *qualificationPath) receivePayload(parent context.Context) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(parent, 2*time.Second)
|
||||
defer cancel()
|
||||
var recovered []byte
|
||||
var sequence uint32
|
||||
var fragmentCount byte
|
||||
for {
|
||||
frame, err := p.client.ReceiveFrame(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if frame.Channel != ChannelVideo {
|
||||
continue
|
||||
}
|
||||
if fragmentCount == 0 {
|
||||
sequence, fragmentCount = frame.Sequence, frame.FragmentCount
|
||||
}
|
||||
if frame.Sequence != sequence || frame.FragmentIndex != byte(len(recovered)/1179) {
|
||||
return nil, errors.New("qualification QUIC fragments reordered")
|
||||
}
|
||||
recovered = append(recovered, frame.Payload...)
|
||||
if frame.FragmentIndex+1 == fragmentCount {
|
||||
break
|
||||
}
|
||||
}
|
||||
return recovered, nil
|
||||
return p.client.ReceiveMedia(ctx)
|
||||
}
|
||||
|
||||
func qualificationSourceVideoPackets(t *testing.T, key []byte, frame uint32, encoded []byte) [][]byte {
|
||||
t.Helper()
|
||||
if len(encoded) <= apolloVideoShardPayloadSize-8 {
|
||||
payload := make([]byte, apolloVideoShardPayloadSize)
|
||||
payload[0], payload[3] = 0x01, 0x01
|
||||
binary.LittleEndian.PutUint16(payload[4:6], uint16(8+len(encoded)))
|
||||
copy(payload[8:], encoded)
|
||||
raw := sourceShapedVideoRaw(frame, uint16(frame), frame, 0x07, 1, 0, 0, payload)
|
||||
return [][]byte{sourceEncryptVideoRaw(t, key, raw, qualificationVideoIV(frame, 0))}
|
||||
total := 8 + len(encoded)
|
||||
shardCount := (total + apolloVideoShardPayloadSize - 1) / apolloVideoShardPayloadSize
|
||||
if shardCount > apolloVideoMaximumBlocks*apolloVideoMaximumDataShards {
|
||||
t.Fatal("qualification encoded frame exceeds source-shaped Apollo bound")
|
||||
}
|
||||
combined := make([]byte, 2*apolloVideoShardPayloadSize)
|
||||
combined := make([]byte, shardCount*apolloVideoShardPayloadSize)
|
||||
combined[0], combined[3] = 0x01, 0x01
|
||||
binary.LittleEndian.PutUint16(combined[4:6], uint16(8+len(encoded)-apolloVideoShardPayloadSize))
|
||||
lastPayloadLength := total - (shardCount-1)*apolloVideoShardPayloadSize
|
||||
binary.LittleEndian.PutUint16(combined[4:6], uint16(lastPayloadLength))
|
||||
copy(combined[8:], encoded)
|
||||
first := sourceShapedVideoRaw(frame, uint16(frame*3), frame*3, 0x05, 2, 50, 0, combined[:apolloVideoShardPayloadSize])
|
||||
second := sourceShapedVideoRaw(frame, uint16(frame*3+1), frame*3+1, 0x03, 2, 50, 1, combined[apolloVideoShardPayloadSize:])
|
||||
parity := make([]byte, len(first))
|
||||
for index := range parity {
|
||||
parity[index] = first[index] ^ sourceGFMultiply(second[index], 142)
|
||||
}
|
||||
sourceConfigureVideoShard(parity, frame, uint16(frame*3+2), frame*3+2, 2, 50, 2)
|
||||
return [][]byte{
|
||||
sourceEncryptVideoRaw(t, key, second, qualificationVideoIV(frame, 1)),
|
||||
sourceEncryptVideoRaw(t, key, parity, qualificationVideoIV(frame, 2)),
|
||||
|
||||
lastBlock := (shardCount - 1) / apolloVideoMaximumDataShards
|
||||
packets := make([][]byte, 0, shardCount)
|
||||
for globalIndex := 0; globalIndex < shardCount; {
|
||||
block := globalIndex / apolloVideoMaximumDataShards
|
||||
dataShards := min(apolloVideoMaximumDataShards, shardCount-globalIndex)
|
||||
for shardIndex := 0; shardIndex < dataShards; shardIndex++ {
|
||||
flags := byte(0x01)
|
||||
if shardIndex == 0 {
|
||||
flags |= 0x04
|
||||
}
|
||||
if shardIndex == dataShards-1 {
|
||||
flags |= 0x02
|
||||
}
|
||||
offset := globalIndex * apolloVideoShardPayloadSize
|
||||
raw := sourceShapedVideoRaw(
|
||||
frame, uint16(frame*1024+uint32(globalIndex)), frame*1024+uint32(globalIndex),
|
||||
flags, dataShards, 0, shardIndex, combined[offset:offset+apolloVideoShardPayloadSize],
|
||||
)
|
||||
raw[27] = byte(block<<4 | lastBlock<<6)
|
||||
packets = append(packets, sourceEncryptVideoRaw(t, key, raw, qualificationVideoIV(frame, globalIndex)))
|
||||
globalIndex++
|
||||
}
|
||||
}
|
||||
return packets
|
||||
}
|
||||
|
||||
func qualificationVideoIV(frame uint32, shard byte) string {
|
||||
return fmt.Sprintf("%09x%01xQV", frame, shard)
|
||||
func qualificationVideoIV(frame uint32, shard int) string {
|
||||
return fmt.Sprintf("%06x%04xQV", frame&0xffffff, shard&0xffff)
|
||||
}
|
||||
|
||||
func qualificationProductionPathSmoke(t *testing.T, profile qualificationMediaProfile) qualificationPathTrace {
|
||||
@@ -1409,7 +1462,10 @@ func runQualificationImpairment(t *testing.T, profile qualificationImpairmentPro
|
||||
observation.ObservedOutOfOrder++
|
||||
}
|
||||
previousDelivered = packet.index
|
||||
deliveries = append(deliveries, qualificationDeliverySample{At: packet.deliveredAt, Bytes: int64(media.PacketBytes + frameHeaderSize)})
|
||||
datagrams := (media.PacketBytes + frameV2PayloadSize - 1) / frameV2PayloadSize
|
||||
deliveries = append(deliveries, qualificationDeliverySample{
|
||||
At: packet.deliveredAt, Bytes: int64(media.PacketBytes + datagrams*frameV2HeaderSize),
|
||||
})
|
||||
observation.MaxQueuePackets = max(observation.MaxQueuePackets, packet.queuePackets)
|
||||
}
|
||||
observation.Delivered = len(received.packets)
|
||||
@@ -1426,7 +1482,8 @@ func runQualificationImpairment(t *testing.T, profile qualificationImpairmentPro
|
||||
observation.ProviderEnqueued = int(path.session.mediaEnqueued.Load() - beforeEnqueued)
|
||||
observation.ProviderQueueReplaced = int(path.session.mediaDrops.Load() - beforeProviderDrops)
|
||||
observation.GatewayForwarded = int(afterMetrics.ProcessingSamples - beforeMetrics.ProcessingSamples)
|
||||
observation.QUICSent = int(afterMetrics.MediaPackets - beforeMetrics.MediaPackets)
|
||||
observation.QUICSent = observation.GatewayForwarded
|
||||
observation.QUICDatagramsSent = int(afterMetrics.MediaPackets - beforeMetrics.MediaPackets)
|
||||
observation.ProviderFECDropped = max(observation.SourceEmitted-observation.ProviderRecovered, 0)
|
||||
observation.ProviderEnqueueDropped = max(observation.ProviderRecovered-observation.ProviderEnqueued, 0)
|
||||
observation.GatewayDropped = max(observation.ProviderEnqueued-observation.ProviderQueueReplaced-observation.GatewayForwarded, 0)
|
||||
@@ -1586,11 +1643,11 @@ func qualificationMaximumDeliveryBytes(deliveries []qualificationDeliverySample,
|
||||
|
||||
func runQualificationProcessing(t *testing.T, profile qualificationMediaProfile, rawPath string) (qualificationProcessingSummary, error) {
|
||||
t.Helper()
|
||||
payload := qualificationPayload(profile)
|
||||
payload := qualificationFramePayload(profile, 0)
|
||||
if len(payload) < 4 {
|
||||
return qualificationProcessingSummary{}, errors.New("qualification payload too small")
|
||||
}
|
||||
path := newQualificationProcessingPath(t, profile, qualificationMediaPacerKbps(profile, 0))
|
||||
path := newQualificationProcessingPath(t, profile, qualificationFramePacerKbps(profile))
|
||||
defer path.Close()
|
||||
if err := runQualificationProcessWarmup(t, path, profile, payload); err != nil {
|
||||
return qualificationProcessingSummary{}, err
|
||||
@@ -1603,38 +1660,42 @@ func runQualificationProcessing(t *testing.T, profile qualificationMediaProfile,
|
||||
if err != nil {
|
||||
return qualificationProcessingSummary{}, err
|
||||
}
|
||||
bytesPerSecond := profile.BitrateKbps * 1000 / 8
|
||||
targetBytes := bytesPerSecond * profile.Duration.Nanoseconds() / int64(time.Second)
|
||||
targetPackets := (targetBytes + int64(profile.PacketBytes) - 1) / int64(profile.PacketBytes)
|
||||
spacing := time.Duration(int64(time.Second) * int64(profile.PacketBytes) * 8 / (profile.BitrateKbps * 1000))
|
||||
fps := qualificationFrameRate(profile)
|
||||
targetFrames := profile.Duration.Nanoseconds() * int64(fps) / int64(time.Second)
|
||||
if targetFrames < 1 {
|
||||
return qualificationProcessingSummary{}, errors.New("qualification duration produces no complete frames")
|
||||
}
|
||||
spacing := time.Second / time.Duration(fps)
|
||||
var targetBytes int64
|
||||
var expectedDatagrams uint64
|
||||
minimumFrameBytes, maximumFrameBytes := maxCompleteFrameBytes, 0
|
||||
for index := int64(0); index < targetFrames; index++ {
|
||||
size := qualificationFrameSize(profile, index)
|
||||
targetBytes += int64(size)
|
||||
expectedDatagrams += uint64((size + frameV2PayloadSize - 1) / frameV2PayloadSize)
|
||||
minimumFrameBytes = min(minimumFrameBytes, size)
|
||||
maximumFrameBytes = max(maximumFrameBytes, size)
|
||||
}
|
||||
maximumDuration := profile.Duration*105/100 + 250*time.Millisecond
|
||||
started := time.Now()
|
||||
receiveCtx, receiveCancel := context.WithDeadline(context.Background(), started.Add(maximumDuration+2*time.Second))
|
||||
defer receiveCancel()
|
||||
receivedDone := make(chan error, 1)
|
||||
go func() {
|
||||
for index := int64(0); index < targetPackets; index++ {
|
||||
for index := int64(0); index < targetFrames; index++ {
|
||||
recovered, receiveErr := path.receivePayload(receiveCtx)
|
||||
if receiveErr != nil {
|
||||
receivedDone <- receiveErr
|
||||
return
|
||||
}
|
||||
if len(recovered) != len(payload) {
|
||||
expected := qualificationFramePayload(profile, index)
|
||||
if len(recovered) != len(expected) {
|
||||
receivedDone <- fmt.Errorf(
|
||||
"qualification processing payload length = %d, want %d at sequence %d",
|
||||
len(recovered), len(payload), index,
|
||||
len(recovered), len(expected), index,
|
||||
)
|
||||
return
|
||||
}
|
||||
sequence := binary.BigEndian.Uint32(recovered[len(recovered)-4:])
|
||||
if sequence != uint32(index) {
|
||||
receivedDone <- fmt.Errorf(
|
||||
"qualification processing sequence = %d, want %d", sequence, index,
|
||||
)
|
||||
return
|
||||
}
|
||||
expected := append([]byte(nil), payload...)
|
||||
binary.BigEndian.PutUint32(expected[len(expected)-4:], uint32(index))
|
||||
if !bytes.Equal(recovered, expected) {
|
||||
receivedDone <- fmt.Errorf("qualification processing payload bytes changed at sequence %d", index)
|
||||
return
|
||||
@@ -1644,7 +1705,8 @@ func runQualificationProcessing(t *testing.T, profile qualificationMediaProfile,
|
||||
}()
|
||||
var processed int64
|
||||
var nextRelease time.Time
|
||||
for processed < targetPackets {
|
||||
payloadDigest := sha256.New()
|
||||
for processed < targetFrames {
|
||||
select {
|
||||
case receiveErr := <-receivedDone:
|
||||
if receiveErr != nil {
|
||||
@@ -1659,8 +1721,8 @@ func runQualificationProcessing(t *testing.T, profile qualificationMediaProfile,
|
||||
)
|
||||
qualificationWaitUntil(release)
|
||||
nextRelease = release.Add(spacing)
|
||||
current := append([]byte(nil), payload...)
|
||||
binary.BigEndian.PutUint32(current[len(current)-4:], uint32(processed))
|
||||
current := qualificationFramePayload(profile, processed)
|
||||
_, _ = payloadDigest.Write(current)
|
||||
if _, err := path.emit(t, current); err != nil {
|
||||
return qualificationProcessingSummary{}, err
|
||||
}
|
||||
@@ -1684,9 +1746,11 @@ func runQualificationProcessing(t *testing.T, profile qualificationMediaProfile,
|
||||
after.MediaRecovered-before.MediaRecovered != uint64(processed) ||
|
||||
after.MediaEnqueued-before.MediaEnqueued != uint64(processed) ||
|
||||
after.MediaDrops != before.MediaDrops ||
|
||||
after.MediaQueueMaximum > nativeApolloVideoQueuePackets ||
|
||||
after.MediaQueueMaximumBytes > nativeApolloVideoQueueBytes ||
|
||||
after.Metrics.ProcessingSamples-before.Metrics.ProcessingSamples != uint64(processed) ||
|
||||
after.PacerReservations <= before.PacerReservations ||
|
||||
after.Metrics.MediaPackets-before.Metrics.MediaPackets != uint64(processed) {
|
||||
after.Metrics.MediaPackets-before.Metrics.MediaPackets != expectedDatagrams {
|
||||
return qualificationProcessingSummary{}, fmt.Errorf("qualification subprocess bypassed a production stage: before=%#v after=%#v processed=%d", before, after, processed)
|
||||
}
|
||||
samples, err := readQualificationProcessingSamples(rawPath, record.Count)
|
||||
@@ -1704,13 +1768,18 @@ func runQualificationProcessing(t *testing.T, profile qualificationMediaProfile,
|
||||
summary.Profile = profile.Name
|
||||
summary.Codec = profile.Codec
|
||||
summary.ConfiguredBitrateKbps = profile.BitrateKbps
|
||||
summary.ObservedBitrateKbps = float64(processed*int64(profile.PacketBytes)*8) / actualDuration.Seconds() / 1000
|
||||
summary.ObservedBitrateKbps = float64(targetBytes*8) / actualDuration.Seconds() / 1000
|
||||
summary.ConfiguredFPS = fps
|
||||
summary.ObservedFPS = float64(processed) / actualDuration.Seconds()
|
||||
summary.PayloadBytes = targetBytes
|
||||
summary.MinimumFrameBytes = minimumFrameBytes
|
||||
summary.MaximumFrameBytes = maximumFrameBytes
|
||||
summary.Warmup = profile.Warmup
|
||||
summary.ConfiguredDuration = profile.Duration
|
||||
summary.ActualDuration = actualDuration
|
||||
summary.ClockOverhead = record.ClockOverhead
|
||||
summary.ClockMethod = record.ClockMethod
|
||||
summary.PayloadSHA256 = fmt.Sprintf("%x", sha256.Sum256(payload))
|
||||
summary.PayloadSHA256 = fmt.Sprintf("%x", payloadDigest.Sum(nil))
|
||||
summary.RawSamples = filepath.Base(rawPath)
|
||||
summary.RawSamplesSHA256 = sum
|
||||
summary.RawSamplesBytes = size
|
||||
|
||||
Reference in New Issue
Block a user