Class: TestRecorder::MatroskaWriter

Inherits:
Object
  • Object
show all
Defined in:
lib/test_recorder/matroska_writer.rb

Overview

Writes a Matroska stream holding a single MJPEG video track, where every frame carries the time it was captured. Chrome emits a screencast frame only when the page repaints, so frames are not evenly spaced; keeping the real times lets ffmpeg reproduce the original pacing instead of assuming a fixed frame rate.

Written against the specifications:

EBML     https://datatracker.ietf.org/doc/html/rfc8794
Matroska https://datatracker.ietf.org/doc/html/rfc9559
Codecs   https://datatracker.ietf.org/doc/html/draft-ietf-cellar-codec

RFC 9559 updates RFC 8794, so read the two together: it makes 0x80 a legal EBML ID, which leaves 0xFF as the only reserved one. Codec IDs such as V_MJPEG are defined by neither, but by the codec document, which is still a draft.

Only the elements those specifications require for this kind of stream are emitted. The stream is written as it is captured, so the sizes of the Segment and of each Cluster are not known when they start and are left unknown, which Matroska allows for those two elements alone.

Constant Summary collapse

UNKNOWN_SIZE =

A size whose data bits are all ones means "unknown". One byte is enough to say that.

[0xFF].pack("C")
TIMESTAMP_SCALE_NS =

TimestampScale is given in nanoseconds per tick, so this makes every timestamp in the stream a number of milliseconds.

1_000_000
MAX_CLUSTER_DURATION_MS =

A block states its time as a 16 bit signed offset from the timestamp of its cluster, so a cluster can only cover about 32 seconds. Matroska also recommends keeping clusters short, a few seconds at most.

5_000
TRACK_NUMBER =
1
TRACK_TYPE_VIDEO =
1
KEYFRAME =
0x80

Instance Method Summary collapse

Constructor Details

#initialize(io) ⇒ MatroskaWriter

Returns a new instance of MatroskaWriter.



68
69
70
71
72
73
# File 'lib/test_recorder/matroska_writer.rb', line 68

def initialize(io)
  @io = io
  @cluster_timestamp_ms = nil

  write_header
end

Instance Method Details

#write_frame(frame, timestamp_ms) ⇒ Object

Appends one JPEG, shown at the given number of milliseconds from the start of the stream. Timestamps must not go backwards.



77
78
79
80
81
82
83
84
# File 'lib/test_recorder/matroska_writer.rb', line 77

def write_frame(frame, timestamp_ms)
  open_cluster(timestamp_ms) if start_new_cluster?(timestamp_ms)

  # The block header is written separately from the frame so that the frame, which is by far
  # the largest part, never has to be copied into another string.
  @io.write(simple_block_header(frame.bytesize, timestamp_ms - @cluster_timestamp_ms))
  @io.write(frame)
end