Files
Piik/native/capture/windows/vp8_encoder.cpp
T
AAA da47189fc3 refactor: unify Go runtime and audited frontend
Squash integration of the reviewed Go core, frontend audit fixes, release tooling, and deployment hygiene. CI passed all Server/Client jobs.
2026-09-06 23:26:01 +08:00

99 lines
3.7 KiB
C++

#include "vp8_encoder.h"
#include <algorithm>
#include <limits>
#include <stdexcept>
#include <string>
#include <thread>
#include <vpx/vp8cx.h>
namespace screener::capture {
namespace {
void CheckVpx(vpx_codec_err_t result) {
if (result != VPX_CODEC_OK) {
throw std::runtime_error(std::string("VP8 encoder: ") +
vpx_codec_err_to_string(result));
}
}
} // namespace
Vp8Encoder::Vp8Encoder(uint32_t width, uint32_t height, uint32_t frame_rate,
uint32_t bit_rate)
: width_(width), height_(height), duration100ns_(0) {
if (width == 0 || height == 0 || (width & 1) || (height & 1) ||
frame_rate == 0 || bit_rate < 1'000) {
throw std::runtime_error("VP8 encoder profile is invalid");
}
duration100ns_ = 10'000'000 / frame_rate;
vpx_codec_enc_cfg_t config{};
CheckVpx(vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &config, 0));
config.g_w = width;
config.g_h = height;
config.g_timebase = {1, 10'000'000};
config.g_threads = std::min(4u, std::max(1u, std::thread::hardware_concurrency()));
config.g_lag_in_frames = 0;
config.rc_end_usage = VPX_CBR;
config.rc_target_bitrate = bit_rate / 1'000;
config.rc_dropframe_thresh = 0;
// Use WebRTC's low-latency VP8 CBR buffer settings.
config.rc_undershoot_pct = 100;
config.rc_overshoot_pct = 15;
config.rc_buf_initial_sz = 500;
config.rc_buf_optimal_sz = 600;
config.rc_buf_sz = 1000;
config.kf_max_dist = frame_rate * 2;
CheckVpx(vpx_codec_enc_init(&codec_, vpx_codec_vp8_cx(), &config, 0));
const auto speed_result = vpx_codec_control(&codec_, VP8E_SET_CPUUSED, 8);
if (speed_result != VPX_CODEC_OK) {
vpx_codec_destroy(&codec_);
CheckVpx(speed_result);
}
}
Vp8Encoder::~Vp8Encoder() { vpx_codec_destroy(&codec_); }
const char* Vp8Encoder::Version() { return vpx_codec_version_str(); }
Vp8Frame Vp8Encoder::Encode(uint8_t* nv12, size_t row_pitch,
uint64_t timestamp100ns, bool key_frame) {
if (nv12 == nullptr || row_pitch < width_ ||
row_pitch > static_cast<size_t>(std::numeric_limits<int>::max()) ||
timestamp100ns > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
throw std::runtime_error("VP8 encoder input is invalid");
}
vpx_image_t image{};
if (!vpx_img_wrap(&image, VPX_IMG_FMT_NV12, width_, height_, 1, nv12)) {
throw std::runtime_error("VP8 encoder could not wrap NV12 input");
}
for (int plane = VPX_PLANE_Y; plane <= VPX_PLANE_V; ++plane) {
image.stride[plane] = static_cast<int>(row_pitch);
}
image.planes[VPX_PLANE_U] = nv12 + row_pitch * height_;
image.planes[VPX_PLANE_V] = image.planes[VPX_PLANE_U] + 1;
CheckVpx(vpx_codec_encode(&codec_, &image,
static_cast<vpx_codec_pts_t>(timestamp100ns),
duration100ns_, key_frame ? VPX_EFLAG_FORCE_KF : 0,
VPX_DL_REALTIME));
Vp8Frame frame;
vpx_codec_iter_t iterator = nullptr;
while (const auto* packet = vpx_codec_get_cx_data(&codec_, &iterator)) {
if (packet->kind != VPX_CODEC_CX_FRAME_PKT) continue;
if (!frame.bytes.empty() || packet->data.frame.pts !=
static_cast<vpx_codec_pts_t>(timestamp100ns)) {
throw std::runtime_error("VP8 encoder changed output order");
}
const auto* bytes = static_cast<const uint8_t*>(packet->data.frame.buf);
frame.bytes.assign(bytes, bytes + packet->data.frame.sz);
frame.key_frame = (packet->data.frame.flags & VPX_FRAME_IS_KEY) != 0;
}
if (frame.bytes.empty() || (key_frame && !frame.key_frame)) {
throw std::runtime_error("VP8 encoder did not produce the requested frame");
}
return frame;
}
} // namespace screener::capture