|
1 | 1 | package recorder |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
4 | 5 | "fmt" |
5 | 6 | "os" |
6 | 7 | "path/filepath" |
7 | 8 | "testing" |
8 | 9 | "time" |
9 | 10 |
|
| 11 | + "github.com/pion/rtp" |
| 12 | + |
10 | 13 | "github.com/bluenviron/gortsplib/v4/pkg/description" |
11 | 14 | rtspformat "github.com/bluenviron/gortsplib/v4/pkg/format" |
12 | 15 | "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265" |
@@ -539,6 +542,159 @@ func TestRecorderSkipTracksFull(t *testing.T) { |
539 | 542 | } |
540 | 543 | } |
541 | 544 |
|
| 545 | +func TestRecorderMPEGTSPassthrough(t *testing.T) { |
| 546 | + // Create a test MPEG-TS packet |
| 547 | + testData := []byte{ |
| 548 | + 0x47, 0x40, 0x00, 0x10, 0x00, // TS header with PID 0x1000 (video) |
| 549 | + // Add some dummy payload - this is a PAT (Program Association Table) |
| 550 | + 0x00, 0x00, 0xB0, 0x0D, 0x00, 0x00, 0xC1, 0x00, |
| 551 | + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 552 | + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 553 | + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 554 | + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 555 | + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 556 | + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 557 | + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 558 | + } |
| 559 | + |
| 560 | + t.Run("basic", func(t *testing.T) { |
| 561 | + // Create a temporary directory for the test |
| 562 | + dir, err := os.MkdirTemp("", "mediamtx-agent") |
| 563 | + require.NoError(t, err) |
| 564 | + defer os.RemoveAll(dir) |
| 565 | + |
| 566 | + // Create the output directory |
| 567 | + recordPath := filepath.Join(dir, "%path/%Y-%m-%d_%H-%M-%S-%f") |
| 568 | + |
| 569 | + // Create a stream with MPEG-TS format |
| 570 | + mpegtsFormat := &rtspformat.MPEGTS{} |
| 571 | + |
| 572 | + desc := &description.Session{Medias: []*description.Media{ |
| 573 | + { |
| 574 | + Type: description.MediaTypeVideo, |
| 575 | + Formats: []rtspformat.Format{mpegtsFormat}, |
| 576 | + }, |
| 577 | + }} |
| 578 | + |
| 579 | + strm := &stream.Stream{ |
| 580 | + WriteQueueSize: 512, |
| 581 | + UDPMaxPayloadSize: 1472, |
| 582 | + Desc: desc, |
| 583 | + GenerateRTPPackets: false, // Disable RTP packet generation |
| 584 | + Parent: test.NilLogger, |
| 585 | + } |
| 586 | + err = strm.Initialize() |
| 587 | + require.NoError(t, err) |
| 588 | + defer strm.Close() |
| 589 | + |
| 590 | + // Create a recorder with MPEG-TS format |
| 591 | + r := &Recorder{ |
| 592 | + PathFormat: recordPath, |
| 593 | + Format: conf.RecordFormatMPEGTS, |
| 594 | + PartDuration: 1 * time.Second, |
| 595 | + SegmentDuration: 2 * time.Second, |
| 596 | + PathName: "mypath", |
| 597 | + Stream: strm, |
| 598 | + Parent: test.NilLogger, |
| 599 | + } |
| 600 | + |
| 601 | + r.Initialize() |
| 602 | + |
| 603 | + // Verify the recorder instance was created |
| 604 | + require.NotNil(t, r.currentInstance, "Recorder instance is nil") |
| 605 | + |
| 606 | + // Create a channel to receive data from the stream |
| 607 | + dataChan := make(chan []byte, 10) |
| 608 | + |
| 609 | + strm.AddReader(r, desc.Medias[0], mpegtsFormat, func(u unit.Unit) error { |
| 610 | + rtpPackets := u.GetRTPPackets() |
| 611 | + |
| 612 | + for _, pkt := range rtpPackets { |
| 613 | + if pkt == nil { |
| 614 | + continue |
| 615 | + } |
| 616 | + |
| 617 | + // Send the payload to the channel |
| 618 | + if len(pkt.Payload) > 0 { |
| 619 | + dataChan <- pkt.Payload |
| 620 | + } |
| 621 | + } |
| 622 | + |
| 623 | + return nil |
| 624 | + }) |
| 625 | + |
| 626 | + strm.StartReader(r) |
| 627 | + strm.WaitRunningReader() |
| 628 | + |
| 629 | + // Write multiple RTP packets to ensure we have enough data |
| 630 | + for i := 0; i < 10; i++ { |
| 631 | + // Create a new RTP packet for each iteration with updated timestamp and sequence number |
| 632 | + pkt := &rtp.Packet{ |
| 633 | + Header: rtp.Header{ |
| 634 | + Version: 2, |
| 635 | + PayloadType: 33, // Standard MPEG-TS payload type (RFC 2250) |
| 636 | + SequenceNumber: 100 + uint16(i), |
| 637 | + Timestamp: 123456 + uint32(i*90000), // 1 second apart |
| 638 | + SSRC: 0x9D8F, |
| 639 | + }, |
| 640 | + Payload: testData, |
| 641 | + } |
| 642 | + |
| 643 | + // Use the MPEG-TS format directly |
| 644 | + internalFormat := mpegtsFormat |
| 645 | + |
| 646 | + // Write the RTP packet to the stream |
| 647 | + strm.WriteRTPPacket(desc.Medias[0], internalFormat, pkt, time.Now(), 0) |
| 648 | + } |
| 649 | + |
| 650 | + // Check if the recorder instance was properly initialized |
| 651 | + if r.currentInstance == nil { |
| 652 | + t.Fatal("Recorder currentInstance is nil") |
| 653 | + } |
| 654 | + |
| 655 | + // Manually flush the buffer writer to ensure all data is written |
| 656 | + if r.currentInstance != nil && r.currentInstance.format2 != nil { |
| 657 | + if mpegtsFormat, ok := r.currentInstance.format2.(*formatMPEGTS); ok && mpegtsFormat.bw != nil { |
| 658 | + err = mpegtsFormat.bw.Flush() |
| 659 | + require.NoError(t, err, "Failed to flush buffer writer") |
| 660 | + |
| 661 | + // Close the current segment if it exists |
| 662 | + if mpegtsFormat.currentSegment != nil { |
| 663 | + err = mpegtsFormat.currentSegment.close() |
| 664 | + require.NoError(t, err, "Failed to close segment") |
| 665 | + mpegtsFormat.currentSegment = nil |
| 666 | + } |
| 667 | + } |
| 668 | + } |
| 669 | + |
| 670 | + // Close the recorder to flush any remaining data |
| 671 | + r.Close() |
| 672 | + |
| 673 | + // Verify the recording was created |
| 674 | + entries, err := os.ReadDir(filepath.Join(dir, "mypath")) |
| 675 | + require.NoError(t, err) |
| 676 | + |
| 677 | + // Find the .ts file |
| 678 | + var tsFile string |
| 679 | + for _, entry := range entries { |
| 680 | + if filepath.Ext(entry.Name()) == ".ts" { |
| 681 | + tsFile = filepath.Join(dir, "mypath", entry.Name()) |
| 682 | + break |
| 683 | + } |
| 684 | + } |
| 685 | + |
| 686 | + if tsFile == "" { |
| 687 | + t.Fatalf("No .ts file found in %s. Directory contents: %v", recordPath, entries) |
| 688 | + } |
| 689 | + |
| 690 | + // Verify the file contains our test data |
| 691 | + data, err := os.ReadFile(tsFile) |
| 692 | + require.NoError(t, err) |
| 693 | + require.Greater(t, len(data), 0, "Recorded file is empty") |
| 694 | + require.True(t, bytes.Contains(data, testData), "Test data not found in output") |
| 695 | + }) |
| 696 | +} |
| 697 | + |
542 | 698 | func TestRecorderFMP4SegmentSwitch(t *testing.T) { |
543 | 699 | desc := &description.Session{Medias: []*description.Media{ |
544 | 700 | { |
|
0 commit comments