// Channel byte accounting for the pipeline message types. // // TRACES: AR-004 | SR-002 // // kpn::ChannelDataSize is what a channel reports as bytes pushed, and its // primary template returns sizeof(T). Every message type here is a handful of // vectors and a cv::Mat header owning megabytes on the heap, so unspecialised // the diagnostics reported ~200 bytes for a message carrying a full decoded // frame — off by four orders of magnitude at 1080p. // // That is the instrument for choosing channel capacities against a memory // ceiling, which is the open half of AR-004. These cases assert it measures the // payload rather than the header, because a stat that is quietly wrong is worse // than no stat: it was read as evidence. #include #include #include "types.hpp" namespace { Frame frame_with_image(int w, int h) { Frame f; f.image = cv::Mat(h, w, CV_8UC3, cv::Scalar(0, 0, 0)); f.timestamp_sec = 1.0; return f; } } // namespace TEST_CASE("frame bytes count the decoded image, not the header", "[channel_bytes]") { const Frame f = frame_with_image(1920, 1080); const std::size_t got = kpn::ChannelDataSize::bytes(f); // 1920 * 1080 * 3 = 6,220,800 payload bytes. REQUIRE(got >= 1920u * 1080u * 3u); // The header is a rounding error next to it; this is the assertion that // fails on the unspecialised default. CHECK(got > 100u * sizeof(Frame)); } TEST_CASE("an empty frame costs only its header", "[channel_bytes]") { // The eof sentinel carries no image, and must not be charged for one. Frame eof; eof.eof = true; CHECK(kpn::ChannelDataSize::bytes(eof) == sizeof(Frame)); } TEST_CASE("crops and embeddings are counted on top of the frame", "[channel_bytes]") { // The case AR-003 created: a crowd frame occupies one slot exactly as an // empty one does, and only the byte figure distinguishes them. EmbeddedSceneFrame v; v.source = frame_with_image(640, 360); const std::size_t bare = kpn::ChannelDataSize::bytes(v); constexpr int kFaces = 60; for (int i = 0; i < kFaces; ++i) { v.faces.push_back({}); v.crops.emplace_back(112, 112, CV_8UC3, cv::Scalar(0, 0, 0)); v.embeddings.emplace_back(); } const std::size_t crowded = kpn::ChannelDataSize::bytes(v); // 60 crops at 112*112*3 = 2,257,920 bytes, plus 60 * 2 KiB of embeddings. CHECK(crowded - bare >= kFaces * (112u * 112u * 3u + sizeof(Embedding))); // And the crowd frame really is the multiple of the empty one that the // item-count capacity cannot see: 640x360x3 is ~691 KB, the crops ~2.26 MB. CHECK(crowded > 3 * bare); } TEST_CASE("every message type on a channel measures its payload", "[channel_bytes]") { // A specialisation missing for any one of these silently reverts that // channel to sizeof(T), which is exactly how this went unnoticed. const Frame f = frame_with_image(320, 240); const std::size_t img = 320u * 240u * 3u; SceneFrame sf; sf.source = f; AlignedSceneFrame af; af.source = f; EmbeddedSceneFrame ef; ef.source = f; TrackedSceneFrame tf; tf.source = f; MatchedSceneFrame mf; mf.source = f; CHECK(kpn::ChannelDataSize::bytes(sf) >= img); CHECK(kpn::ChannelDataSize::bytes(af) >= img); CHECK(kpn::ChannelDataSize::bytes(ef) >= img); CHECK(kpn::ChannelDataSize::bytes(tf) >= img); CHECK(kpn::ChannelDataSize::bytes(mf) >= img); // SceneAnnotation carries no source frame — only the actors it identified, // each with its own crop. SceneAnnotation sa; sa.visible_actors.push_back({}); sa.visible_actors.back().crop = cv::Mat(112, 112, CV_8UC3, cv::Scalar(0, 0, 0)); CHECK(kpn::ChannelDataSize::bytes(sa) >= 112u * 112u * 3u); } TEST_CASE("a shared image is charged to each message holding it", "[channel_bytes]") { // cv::Mat is reference-counted, so a frame referenced from several messages // is counted once per reference. The sum is an upper bound on distinct // bytes, and the right bound for "what would this channel keep alive if // nothing else held it" — which is the question a capacity answers. const Frame f = frame_with_image(320, 240); SceneFrame a; a.source = f; SceneFrame b; b.source = f; // shares the same pixel buffer CHECK(kpn::ChannelDataSize::bytes(a) == kpn::ChannelDataSize::bytes(b)); }