|
| 1 | +// The wrapper through its modules alone: VP9 frames encoded through the typed |
| 2 | +// controls, muxed into a WebM in memory with mkvmuxer (track UID given), |
| 3 | +// parsed back with mkvparser and decoded within a PSNR; the same bytes twice. |
| 4 | +import std; |
| 5 | +import mcpplibs.mwrap.libvpx; |
| 6 | + |
| 7 | +namespace m = mcpplibs::mwrap::libvpx::macros; |
| 8 | +namespace vpx = mcpplibs::mwrap::libvpx; |
| 9 | + |
| 10 | +namespace { |
| 11 | + |
| 12 | +constexpr int W = 160, H = 96, FRAMES = 20; |
| 13 | + |
| 14 | +void draw(vpx_image_t& img, int t) { |
| 15 | + for (int y = 0; y < H; ++y) |
| 16 | + for (int x = 0; x < W; ++x) |
| 17 | + img.planes[0][y * img.stride[0] + x] = static_cast<unsigned char>(x >= 20 + 4 * t && x < 50 + 4 * t && y >= 30 && y < 60 ? 230 : 16 + (x + y + 3 * t) % 200); |
| 18 | + for (int p = 1; p < 3; ++p) |
| 19 | + for (int y = 0; y < H / 2; ++y) |
| 20 | + for (int x = 0; x < W / 2; ++x) img.planes[p][y * img.stride[p] + x] = static_cast<unsigned char>(p == 1 ? 100 + x : 150 - y); |
| 21 | +} |
| 22 | + |
| 23 | +class Memory final : public mkvmuxer::IMkvWriter { |
| 24 | +public: |
| 25 | + std::vector<unsigned char> bytes; |
| 26 | + std::int64_t at = 0; |
| 27 | + mkvmuxer::int32 Write(const void* buf, mkvmuxer::uint32 len) override { |
| 28 | + if (bytes.size() < static_cast<std::size_t>(at) + len) bytes.resize(static_cast<std::size_t>(at) + len); |
| 29 | + std::memcpy(bytes.data() + at, buf, len); |
| 30 | + at += len; |
| 31 | + return 0; |
| 32 | + } |
| 33 | + mkvmuxer::int64 Position() const override { return at; } |
| 34 | + mkvmuxer::int32 Position(mkvmuxer::int64 p) override { return at = p, 0; } |
| 35 | + bool Seekable() const override { return true; } |
| 36 | + void ElementStartNotify(mkvmuxer::uint64, mkvmuxer::int64) override {} |
| 37 | +}; |
| 38 | + |
| 39 | +class Reader final : public mkvparser::IMkvReader { |
| 40 | +public: |
| 41 | + explicit Reader(const std::vector<unsigned char>& b) : bytes(b) {} |
| 42 | + int Read(long long pos, long len, unsigned char* buf) override { |
| 43 | + if (pos < 0 || len < 0 || static_cast<std::size_t>(pos + len) > bytes.size()) return -1; |
| 44 | + std::memcpy(buf, bytes.data() + pos, static_cast<std::size_t>(len)); |
| 45 | + return 0; |
| 46 | + } |
| 47 | + int Length(long long* total, long long* available) override { |
| 48 | + *total = *available = static_cast<long long>(bytes.size()); |
| 49 | + return 0; |
| 50 | + } |
| 51 | + const std::vector<unsigned char>& bytes; |
| 52 | +}; |
| 53 | + |
| 54 | +std::vector<unsigned char> webm() { |
| 55 | + vpx_codec_enc_cfg_t cfg; |
| 56 | + vpx_codec_enc_config_default(vpx_codec_vp9_cx(), &cfg, 0); |
| 57 | + cfg.g_w = W; |
| 58 | + cfg.g_h = H; |
| 59 | + cfg.g_timebase = {1, 30}; |
| 60 | + cfg.g_lag_in_frames = 0; |
| 61 | + cfg.rc_end_usage = VPX_Q; |
| 62 | + vpx_codec_ctx_t enc; |
| 63 | + if (m::vpx_codec_enc_init(&enc, vpx_codec_vp9_cx(), &cfg, 0) != VPX_CODEC_OK) return {}; |
| 64 | + vpx::vpx_codec_control_VP8E_SET_CPUUSED(&enc, VP8E_SET_CPUUSED, 6); |
| 65 | + vpx::vpx_codec_control_VP8E_SET_CQ_LEVEL(&enc, VP8E_SET_CQ_LEVEL, 20u); |
| 66 | + Memory out; |
| 67 | + mkvmuxer::Segment segment; |
| 68 | + segment.Init(&out); |
| 69 | + segment.set_mode(mkvmuxer::Segment::kFile); |
| 70 | + const auto track = segment.AddVideoTrack(W, H, 1); |
| 71 | + auto* v = static_cast<mkvmuxer::VideoTrack*>(segment.GetTrackByNumber(track)); |
| 72 | + v->set_codec_id(mkvmuxer::Tracks::kVp9CodecId); |
| 73 | + v->set_uid(7); |
| 74 | + vpx_image_t img; |
| 75 | + vpx_img_alloc(&img, VPX_IMG_FMT_I420, W, H, 16); |
| 76 | + for (int t = 0; t <= FRAMES; ++t) { |
| 77 | + if (t < FRAMES) draw(img, t); |
| 78 | + vpx_codec_encode(&enc, t < FRAMES ? &img : nullptr, t, 1, 0, m::VPX_DL_GOOD_QUALITY); |
| 79 | + vpx_codec_iter_t it = nullptr; |
| 80 | + while (const vpx_codec_cx_pkt_t* p = vpx_codec_get_cx_data(&enc, &it)) |
| 81 | + if (p->kind == VPX_CODEC_CX_FRAME_PKT) |
| 82 | + segment.AddFrame(static_cast<const std::uint8_t*>(p->data.frame.buf), p->data.frame.sz, track, |
| 83 | + static_cast<std::uint64_t>(p->data.frame.pts) * 1'000'000'000 / 30, (p->data.frame.flags & m::VPX_FRAME_IS_KEY) != 0); |
| 84 | + } |
| 85 | + vpx_img_free(&img); |
| 86 | + vpx_codec_destroy(&enc); |
| 87 | + segment.Finalize(); |
| 88 | + return out.bytes; |
| 89 | +} |
| 90 | + |
| 91 | +} // namespace |
| 92 | + |
| 93 | +int main() { |
| 94 | + const auto file = webm(); |
| 95 | + if (file.empty() || file != webm()) return 1; |
| 96 | + Reader reader(file); |
| 97 | + long long pos = 0; |
| 98 | + mkvparser::EBMLHeader header; |
| 99 | + mkvparser::Segment* raw = nullptr; |
| 100 | + if (header.Parse(&reader, pos) != 0 || std::string_view(header.m_docType) != "webm") return 2; |
| 101 | + if (mkvparser::Segment::CreateInstance(&reader, pos, raw) != 0 || !raw || raw->Load() < 0) return 3; |
| 102 | + std::unique_ptr<mkvparser::Segment> segment(raw); |
| 103 | + vpx_codec_dec_cfg_t dcfg{}; |
| 104 | + vpx_codec_ctx_t dec; |
| 105 | + if (m::vpx_codec_dec_init(&dec, vpx_codec_vp9_dx(), &dcfg, 0) != VPX_CODEC_OK) return 4; |
| 106 | + vpx_image_t ref; |
| 107 | + vpx_img_alloc(&ref, VPX_IMG_FMT_I420, W, H, 16); |
| 108 | + int frames = 0; |
| 109 | + double worst = 99; |
| 110 | + for (const mkvparser::Cluster* c = segment->GetFirst(); c && !c->EOS(); c = segment->GetNext(c)) { |
| 111 | + const mkvparser::BlockEntry* e = nullptr; |
| 112 | + for (c->GetFirst(e); e && !e->EOS(); c->GetNext(e, e)) { |
| 113 | + const mkvparser::Block::Frame& f = e->GetBlock()->GetFrame(0); |
| 114 | + std::vector<unsigned char> data(static_cast<std::size_t>(f.len)); |
| 115 | + f.Read(&reader, data.data()); |
| 116 | + vpx_codec_decode(&dec, data.data(), static_cast<unsigned>(data.size()), nullptr, 0); |
| 117 | + vpx_codec_iter_t it = nullptr; |
| 118 | + while (vpx_image_t* out = vpx_codec_get_frame(&dec, &it)) { |
| 119 | + draw(ref, frames++); |
| 120 | + double se = 0; |
| 121 | + for (int y = 0; y < H; ++y) |
| 122 | + for (int x = 0; x < W; ++x) { |
| 123 | + const int d = out->planes[0][y * out->stride[0] + x] - ref.planes[0][y * ref.stride[0] + x]; |
| 124 | + se += d * d; |
| 125 | + } |
| 126 | + if (se > 0) worst = std::min(worst, 10 * std::log10(255.0 * 255.0 * W * H / se)); |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | + vpx_img_free(&ref); |
| 131 | + vpx_codec_destroy(&dec); |
| 132 | + std::println("{} frames in {} bytes of WebM, worst PSNR {:.1f} dB ({})", frames, file.size(), worst, vpx_codec_version_str()); |
| 133 | + return frames == FRAMES && worst > 35 ? 0 : 5; |
| 134 | +} |
0 commit comments