02 — Decode frames and borrow pixels
Tutorial 01 only read a clip’s shape. Now we decode it. video_decode
advances one frame at a time and returns false at end of stream; the frame’s
pixels are then borrowed inside a get_data block — a view that’s valid only
inside the block and only until the next decode, and is never copied. That borrow
is the heart of dasVideo’s “player owns, consumer borrows” model.
The library
decode_stats runs the decode loop, borrows each frame’s packed RGBA8 pixels,
and folds them into a checksum. The borrowed rgb is width*height*4 bytes;
you do your reading (or your texture upload) inside the block and let go when it
returns. replay_count shows video_rewind: decode to the end, rewind, decode
again, and you get the same frames back — that’s how looping works.
require video
require daslib/defer
struct DecodeStats {
frames : int
checksum : uint
opaque : bool // every RGBA frame had an opaque first pixel (alpha 255)
}
// Decode every frame of `path`, borrowing each frame's RGBA8 pixels and folding
// them into a cheap checksum. The borrowed `rgb` is width*height*4 bytes, valid
// only inside the block — do your reading (or upload to a texture) there.
def public decode_stats(path : string) : DecodeStats {
var player = video_open(path)
if (player == null) return DecodeStats()
defer() { video_close(player) }
var st = DecodeStats(opaque = true)
while (video_decode(player)) {
st.frames++
player |> get_data() $(rgb : array<uint8>#) {
if (rgb[3] != 255u8) {
st.opaque = false
}
for (b in rgb) {
st.checksum += uint(b)
}
}
}
return st
}
// `video_rewind` seeks back to the start so you can loop. Decode to the end,
// rewind, decode again — the second pass yields the same frame count.
def public replay_count(path : string) : int {
var player = video_open(path)
if (player == null) return 0
defer() { video_close(player) }
while (video_decode(player)) {}
video_rewind(player)
var n = 0
while (video_decode(player)) {
n++
}
return n
}
Two things worth noting. The RGBA route (get_data() $(rgb : array<uint8>#))
hands you pixels pl_mpeg has already converted to RGBA8 on the CPU — easy to upload
to one texture. There’s also a zero-copy YUV route
(get_data() $(y, u, v : array<uint8>#)) that hands you the decoder’s native
planes for GPU conversion; that’s a later tutorial. And the borrow is read-only and
scoped: never stash the rgb reference to use after the block — by then the next
decode has reused the buffer.
Self-verifying
The test decodes the bundled sample.mpg (an ffmpeg testsrc 64×48 clip, 25
frames, no audio) and checks the frame count, that the pixels are non-zero and
opaque, and that video_rewind replays the whole stream.
require decode_rgba_tut
[export]
def main() {
let st = decode_stats("tutorials/02_decode_rgba/sample.mpg")
verify(st.frames == 25, "the sample decodes to 25 frames")
verify(st.checksum > 0u, "decoded pixels are non-zero")
verify(st.opaque, "RGBA frames are opaque (alpha 255)")
verify(replay_count("tutorials/02_decode_rgba/sample.mpg") == st.frames,
"video_rewind replays the full stream")
print("tutorial 02 ok: decoded {st.frames} frames, checksum {st.checksum}\n")
}
Running it
Run it from the repo root — the test opens its sample with a repo-root-relative path:
cd <dasVideo>
daslang -load_module . tutorials/02_decode_rgba/test_decode_rgba.das
prints:
tutorial 02 ok: decoded 25 frames, checksum 0x2e0c99e
Next
03 — RGBA on the screen puts those borrowed pixels on screen: upload the RGBA frame to an OpenGL texture and draw it on a fullscreen quad in a window.