mirror of
https://github.com/kerberos-io/agent.git
synced 2026-09-09 17:58:32 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
faa3b4eabb | ||
|
|
f0a6eb7d98 | ||
|
|
33a58cddf7 | ||
|
|
fea6d81246 |
@@ -62,7 +62,7 @@ func recordingUploadMetadata(name, deviceKey string, timestamp int64, mp4Video *
|
||||
}
|
||||
value := mp4Video.AverageFPS()
|
||||
if value > 0 && value <= 240 && !math.IsInf(value, 0) && !math.IsNaN(value) {
|
||||
metadata.FPS = int(math.Floor(value))
|
||||
metadata.FPS = value
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
@@ -29,9 +29,13 @@ func TestQueueRecordingForUploadStoresFinalizedMetadata(t *testing.T) {
|
||||
if err := json.Unmarshal(got, &stored); err != nil {
|
||||
t.Fatalf("decode upload marker: %v", err)
|
||||
}
|
||||
if stored.FileName != "recording.mp4" || stored.DeviceKey != "device-key" || stored.Timestamp != 1785934709414 || stored.Duration != 20452 || stored.FPS != 29 {
|
||||
expectedFPS := mp4Video.AverageFPS()
|
||||
if stored.FileName != "recording.mp4" || stored.DeviceKey != "device-key" || stored.Timestamp != 1785934709414 || stored.Duration != 20452 || math.Abs(stored.FPS-expectedFPS) > 1e-9 {
|
||||
t.Fatalf("upload marker = %+v", stored)
|
||||
}
|
||||
if stored.FPS == math.Floor(stored.FPS) {
|
||||
t.Fatalf("upload marker FPS = %v, want fractional precision", stored.FPS)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueueRecordingForUploadKeepsUnknownFPSCompatible(t *testing.T) {
|
||||
@@ -44,7 +48,7 @@ func TestQueueRecordingForUploadKeepsUnknownFPSCompatible(t *testing.T) {
|
||||
|
||||
metadata := models.RecordingUploadMetadata{FileName: "recording.mp4"}
|
||||
if fps >= 1 && fps <= 240 && !math.IsNaN(fps) && !math.IsInf(fps, 0) {
|
||||
metadata.FPS = int(math.Floor(fps))
|
||||
metadata.FPS = fps
|
||||
}
|
||||
queueRecordingForUpload(configDirectory, metadata)
|
||||
|
||||
|
||||
@@ -59,6 +59,16 @@ func BroadcastPath(prefix string, deviceKey string) string {
|
||||
return prefix + "/" + strings.Trim(deviceKey, "/") + "/live.hang"
|
||||
}
|
||||
|
||||
// TimestampUs converts the capture presentation timestamp from milliseconds.
|
||||
// CompositionTime must not be added: it is already represented in the PTS and
|
||||
// is only used by muxers to derive DTS for streams containing B-frames.
|
||||
func TimestampUs(presentationTimeMs int64) uint64 {
|
||||
if presentationTimeMs < 0 {
|
||||
return 0
|
||||
}
|
||||
return uint64(presentationTimeMs) * 1000
|
||||
}
|
||||
|
||||
func hasAnnexBStartCode(payload []byte) bool {
|
||||
return len(payload) >= 4 && payload[0] == 0 && payload[1] == 0 &&
|
||||
((payload[2] == 0 && payload[3] == 1) || payload[2] == 1)
|
||||
|
||||
@@ -72,3 +72,12 @@ func TestBroadcastPath(t *testing.T) {
|
||||
t.Fatalf("BroadcastPath() default = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampUs(t *testing.T) {
|
||||
if got := TimestampUs(1234); got != 1_234_000 {
|
||||
t.Fatalf("TimestampUs() = %d, want 1234000", got)
|
||||
}
|
||||
if got := TimestampUs(-1); got != 0 {
|
||||
t.Fatalf("TimestampUs() negative = %d, want 0", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,17 +152,13 @@ func publishLiveStreamMoQ(ctx context.Context, config liveMoQConfig) error {
|
||||
writing = true
|
||||
log.Log.Info("cloud.publishLiveStreamMoQ(): first H.264 keyframe received; broadcast is live")
|
||||
}
|
||||
presentationTimeMs := packet.Time + packet.CompositionTime
|
||||
if presentationTimeMs < 0 {
|
||||
presentationTimeMs = 0
|
||||
}
|
||||
payload, err := livemoq.NormalizeH264AccessUnit(packet.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("normalize H.264 access unit: %w", err)
|
||||
}
|
||||
frame := moq.Frame{
|
||||
Payload: payload,
|
||||
TimestampUs: uint64(presentationTimeMs) * 1000,
|
||||
TimestampUs: livemoq.TimestampUs(packet.Time),
|
||||
}
|
||||
if err := stream.WriteFrame(frame); err != nil {
|
||||
return fmt.Errorf("write H.264 access unit: %w", err)
|
||||
|
||||
@@ -28,10 +28,10 @@ func queuedRecordingFPS(fileName string) string {
|
||||
marker := strings.TrimSpace(string(value))
|
||||
if strings.HasPrefix(marker, "{") {
|
||||
metadata, ok := decodeRecordingUploadMetadata(value)
|
||||
if !ok || metadata.FPS <= 0 || metadata.FPS > 240 {
|
||||
if !ok || metadata.FPS <= 0 || metadata.FPS > 240 || math.IsInf(metadata.FPS, 0) || math.IsNaN(metadata.FPS) {
|
||||
return ""
|
||||
}
|
||||
return strconv.Itoa(metadata.FPS)
|
||||
return strconv.FormatFloat(metadata.FPS, 'f', -1, 64)
|
||||
}
|
||||
|
||||
// Compatibility with markers created before upload metadata used JSON.
|
||||
|
||||
@@ -272,7 +272,7 @@ func TestUploadVaultResumable_HappyPath(t *testing.T) {
|
||||
fileName := "1564859471_6-474162_oprit_577-283-727-375_1153_27.mp4"
|
||||
payload := bytes.Repeat([]byte("x"), 4096)
|
||||
withRecording(t, fileName, payload)
|
||||
withQueuedRecordingFPS(t, fileName, `{"filename":"recording.mp4","device_key":"device-key","timestamp":1785934709414,"duration":20452,"fps":29}`)
|
||||
withQueuedRecordingFPS(t, fileName, `{"filename":"recording.mp4","device_key":"device-key","timestamp":1785934709414,"duration":20452,"fps":29.97}`)
|
||||
|
||||
uploaded, responded, supported, _, err := uploadVaultResumable(testVault(ts.URL), "pk", "dev", fileName, "test", "primary")
|
||||
if err != nil {
|
||||
@@ -289,8 +289,8 @@ func TestUploadVaultResumable_HappyPath(t *testing.T) {
|
||||
}
|
||||
posts := srv.requestsForMethod(http.MethodPost)
|
||||
metadata := decodeTusMetadata(posts[0].header.Get("Upload-Metadata"))
|
||||
if got := metadata["fps"]; got != "29" {
|
||||
t.Fatalf("POST metadata fps = %q, want %q", got, "29")
|
||||
if got := metadata["fps"]; got != "29.97" {
|
||||
t.Fatalf("POST metadata fps = %q, want %q", got, "29.97")
|
||||
}
|
||||
if got := metadata["duration"]; got != "20452" {
|
||||
t.Fatalf("POST metadata duration = %q, want %q", got, "20452")
|
||||
@@ -307,6 +307,7 @@ func TestQueuedRecordingFPSValidation(t *testing.T) {
|
||||
want string
|
||||
}{
|
||||
{name: "json", fps: `{"fps":29}`, want: "29"},
|
||||
{name: "json fractional", fps: `{"fps":17.35}`, want: "17.35"},
|
||||
{name: "json with future field", fps: `{"fps":29,"codec":"h264"}`, want: "29"},
|
||||
{name: "json without fps", fps: `{}`},
|
||||
{name: "json invalid fps", fps: `{"fps":241}`},
|
||||
|
||||
@@ -11,11 +11,11 @@ const RecordingUploadMetadataExtension = ".metadata"
|
||||
// with a recording. New optional fields can be added without changing the queue
|
||||
// mechanism or breaking older agents.
|
||||
type RecordingUploadMetadata struct {
|
||||
FileName string `json:"filename"`
|
||||
DeviceKey string `json:"device_key"`
|
||||
Timestamp int64 `json:"timestamp"` // Unix milliseconds.
|
||||
Duration uint64 `json:"duration"` // Milliseconds.
|
||||
FPS int `json:"fps,omitempty"`
|
||||
FileName string `json:"filename"`
|
||||
DeviceKey string `json:"device_key"`
|
||||
Timestamp int64 `json:"timestamp"` // Unix milliseconds.
|
||||
Duration uint64 `json:"duration"` // Milliseconds.
|
||||
FPS float64 `json:"fps,omitempty"`
|
||||
}
|
||||
|
||||
// RecordingUploadMetadataFileName returns the queue marker name associated
|
||||
|
||||
Reference in New Issue
Block a user