mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-28 08:31:52 +08:00
fix(video): 修正默认码率配置并优化软件编码器
- 将默认码率从 8 Mbps 降至 1 Mbps,更适合嵌入式设备 - 修复 WebRtcConfig 中 max_bitrate < target_bitrate 的逻辑错误 - 优化 libx264/libx265 软件编码器的低延迟配置 - 优化 libvpx (VP8/VP9) 的实时编码参数
This commit is contained in:
@@ -15,10 +15,41 @@ extern "C" {
|
||||
#define LOG_MODULE "UTIL"
|
||||
#include "log.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Helper function: check if encoder is software H264 (libx264)
|
||||
bool is_software_h264(const std::string &name) {
|
||||
if (name != "h264" && name != "libx264") return false;
|
||||
// Exclude all hardware encoders
|
||||
static const char* hw_suffixes[] = {
|
||||
"nvenc", "amf", "qsv", "vaapi", "rkmpp",
|
||||
"v4l2m2m", "videotoolbox", "mediacodec", "_mf"
|
||||
};
|
||||
for (const auto& suffix : hw_suffixes) {
|
||||
if (name.find(suffix) != std::string::npos) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper function: check if encoder is software HEVC (libx265)
|
||||
bool is_software_hevc(const std::string &name) {
|
||||
if (name != "hevc" && name != "libx265") return false;
|
||||
static const char* hw_suffixes[] = {
|
||||
"nvenc", "amf", "qsv", "vaapi", "rkmpp",
|
||||
"v4l2m2m", "videotoolbox", "mediacodec", "_mf"
|
||||
};
|
||||
for (const auto& suffix : hw_suffixes) {
|
||||
if (name.find(suffix) != std::string::npos) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace util_encode {
|
||||
|
||||
void set_av_codec_ctx(AVCodecContext *c, const std::string &name, int kbs,
|
||||
int gop, int fps) {
|
||||
int gop, int fps, int thread_count) {
|
||||
c->has_b_frames = 0;
|
||||
c->max_b_frames = 0;
|
||||
if (gop > 0 && gop < std::numeric_limits<int16_t>::max()) {
|
||||
@@ -48,9 +79,19 @@ void set_av_codec_ctx(AVCodecContext *c, const std::string &name, int kbs,
|
||||
c->framerate = av_make_q(fps, 1);
|
||||
c->flags |= AV_CODEC_FLAG2_LOCAL_HEADER;
|
||||
c->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||
c->slices = 1;
|
||||
c->thread_type = FF_THREAD_SLICE;
|
||||
c->thread_count = c->slices;
|
||||
|
||||
// Threading configuration: use frame-based threading for software encoders
|
||||
if (is_software_h264(name) || is_software_hevc(name)) {
|
||||
// Software encoders benefit from frame-level parallelism
|
||||
c->thread_type = FF_THREAD_FRAME;
|
||||
c->thread_count = thread_count > 0 ? thread_count : 4;
|
||||
c->slices = 1;
|
||||
} else {
|
||||
// Hardware encoders typically use slice-based threading
|
||||
c->slices = 1;
|
||||
c->thread_type = FF_THREAD_SLICE;
|
||||
c->thread_count = c->slices;
|
||||
}
|
||||
|
||||
// https://github.com/obsproject/obs-studio/blob/3cc7dc0e7cf8b01081dc23e432115f7efd0c8877/plugins/obs-ffmpeg/obs-ffmpeg-mux.c#L160
|
||||
c->color_range = AVCOL_RANGE_MPEG;
|
||||
@@ -58,7 +99,10 @@ void set_av_codec_ctx(AVCodecContext *c, const std::string &name, int kbs,
|
||||
c->color_primaries = AVCOL_PRI_SMPTE170M;
|
||||
c->color_trc = AVCOL_TRC_SMPTE170M;
|
||||
|
||||
if (name.find("h264") != std::string::npos) {
|
||||
// Profile selection: use BASELINE for software H264 (faster, simpler)
|
||||
if (is_software_h264(name)) {
|
||||
c->profile = FF_PROFILE_H264_BASELINE; // Simpler profile for real-time
|
||||
} else if (name.find("h264") != std::string::npos) {
|
||||
c->profile = FF_PROFILE_H264_HIGH;
|
||||
} else if (name.find("hevc") != std::string::npos) {
|
||||
c->profile = FF_PROFILE_HEVC_MAIN;
|
||||
@@ -127,6 +171,44 @@ bool set_lantency_free(void *priv_data, const std::string &name) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// libx264 software encoder - zero latency tuning
|
||||
if (is_software_h264(name)) {
|
||||
// zerolatency: disable B-frames, reduce lookahead, disable CABAC etc.
|
||||
if ((ret = av_opt_set(priv_data, "tune", "zerolatency", 0)) < 0) {
|
||||
LOG_ERROR(std::string("libx264 set tune zerolatency failed, ret = ") + av_err2str(ret));
|
||||
// tune failure is not fatal, continue
|
||||
}
|
||||
// Disable B-frame adaptation (extra safety, zerolatency already includes this)
|
||||
av_opt_set_int(priv_data, "b-adapt", 0, 0);
|
||||
// Set low rc-lookahead for minimal latency
|
||||
av_opt_set_int(priv_data, "rc-lookahead", 0, 0);
|
||||
// Use sliced-threads for lower latency (encode slices in parallel within same frame)
|
||||
av_opt_set_int(priv_data, "sliced-threads", 1, 0);
|
||||
// Disable mb-tree for lower memory and faster encoding
|
||||
av_opt_set_int(priv_data, "mbtree", 0, 0);
|
||||
// Disable adaptive quantization for speed
|
||||
av_opt_set_int(priv_data, "aq-mode", 0, 0);
|
||||
// Use simpler motion estimation for speed
|
||||
av_opt_set(priv_data, "me", "dia", 0); // diamond search (fastest)
|
||||
// Reduce subpixel motion estimation refinement
|
||||
av_opt_set_int(priv_data, "subq", 1, 0); // 1 = fastest
|
||||
// Reduce reference frames for speed
|
||||
av_opt_set_int(priv_data, "refs", 1, 0);
|
||||
}
|
||||
// libx265 software encoder - zero latency tuning
|
||||
if (is_software_hevc(name)) {
|
||||
if ((ret = av_opt_set(priv_data, "tune", "zerolatency", 0)) < 0) {
|
||||
LOG_ERROR(std::string("libx265 set tune zerolatency failed, ret = ") + av_err2str(ret));
|
||||
// tune failure is not fatal, continue
|
||||
}
|
||||
// x265 specific low-latency parameters
|
||||
// bframes=0: no B-frames
|
||||
// rc-lookahead=0: no lookahead
|
||||
// no-slices-wpp=1: disable wavefront parallel processing for lower latency
|
||||
// frame-threads=1: single frame thread for lower latency
|
||||
av_opt_set(priv_data, "x265-params",
|
||||
"bframes=0:rc-lookahead=0:ref=1:no-b-adapt=1:aq-mode=0", 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -221,6 +303,50 @@ bool set_quality(void *priv_data, const std::string &name, int quality) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// libx264 software encoder presets
|
||||
if (is_software_h264(name)) {
|
||||
const char* preset = nullptr;
|
||||
switch (quality) {
|
||||
case Quality_High:
|
||||
preset = "veryfast"; // Good quality while maintaining fast encoding
|
||||
break;
|
||||
case Quality_Medium:
|
||||
preset = "superfast"; // Balance between speed and quality
|
||||
break;
|
||||
case Quality_Low:
|
||||
preset = "ultrafast"; // Fastest speed, lowest CPU usage
|
||||
break;
|
||||
default:
|
||||
preset = "superfast"; // Default to superfast for embedded devices
|
||||
break;
|
||||
}
|
||||
if ((ret = av_opt_set(priv_data, "preset", preset, 0)) < 0) {
|
||||
LOG_ERROR(std::string("libx264 set preset ") + preset + " failed, ret = " + av_err2str(ret));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// libx265 software encoder presets
|
||||
if (is_software_hevc(name)) {
|
||||
const char* preset = nullptr;
|
||||
switch (quality) {
|
||||
case Quality_High:
|
||||
preset = "veryfast";
|
||||
break;
|
||||
case Quality_Medium:
|
||||
preset = "superfast";
|
||||
break;
|
||||
case Quality_Low:
|
||||
preset = "ultrafast";
|
||||
break;
|
||||
default:
|
||||
preset = "superfast";
|
||||
break;
|
||||
}
|
||||
if ((ret = av_opt_set(priv_data, "preset", preset, 0)) < 0) {
|
||||
LOG_ERROR(std::string("libx265 set preset ") + preset + " failed, ret = " + av_err2str(ret));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ extern "C" {
|
||||
namespace util_encode {
|
||||
|
||||
void set_av_codec_ctx(AVCodecContext *c, const std::string &name, int kbs,
|
||||
int gop, int fps);
|
||||
int gop, int fps, int thread_count);
|
||||
bool set_lantency_free(void *priv_data, const std::string &name);
|
||||
bool set_quality(void *priv_data, const std::string &name, int quality);
|
||||
bool set_rate_control(AVCodecContext *c, const std::string &name, int rc,
|
||||
|
||||
@@ -235,12 +235,15 @@ public:
|
||||
c_->pix_fmt =
|
||||
hw_pixfmt_ != AV_PIX_FMT_NONE ? hw_pixfmt_ : (AVPixelFormat)pixfmt_;
|
||||
c_->sw_pix_fmt = (AVPixelFormat)pixfmt_;
|
||||
util_encode::set_av_codec_ctx(c_, name_, kbs_, gop_, fps_);
|
||||
util_encode::set_av_codec_ctx(c_, name_, kbs_, gop_, fps_, thread_count_);
|
||||
if (!util_encode::set_lantency_free(c_->priv_data, name_)) {
|
||||
LOG_ERROR(std::string("set_lantency_free failed, name: ") + name_);
|
||||
return false;
|
||||
}
|
||||
// util_encode::set_quality(c_->priv_data, name_, quality_);
|
||||
if (!util_encode::set_quality(c_->priv_data, name_, quality_)) {
|
||||
LOG_ERROR(std::string("set_quality failed, name: ") + name_);
|
||||
return false;
|
||||
}
|
||||
util_encode::set_rate_control(c_, name_, rc_, q_);
|
||||
util_encode::set_gpu(c_->priv_data, name_, gpu_);
|
||||
util_encode::force_hw(c_->priv_data, name_);
|
||||
|
||||
@@ -119,14 +119,14 @@ impl CodecInfo {
|
||||
pub fn soft() -> CodecInfos {
|
||||
CodecInfos {
|
||||
h264: Some(CodecInfo {
|
||||
name: "h264".to_owned(),
|
||||
name: "libx264".to_owned(),
|
||||
mc_name: Default::default(),
|
||||
format: H264,
|
||||
hwdevice: AV_HWDEVICE_TYPE_NONE,
|
||||
priority: Priority::Soft as _,
|
||||
}),
|
||||
h265: Some(CodecInfo {
|
||||
name: "hevc".to_owned(),
|
||||
name: "libx265".to_owned(),
|
||||
mc_name: Default::default(),
|
||||
format: H265,
|
||||
hwdevice: AV_HWDEVICE_TYPE_NONE,
|
||||
|
||||
@@ -375,7 +375,7 @@ impl Default for StreamConfig {
|
||||
Self {
|
||||
mode: StreamMode::Mjpeg,
|
||||
encoder: EncoderType::Auto,
|
||||
bitrate_kbps: 8000,
|
||||
bitrate_kbps: 1000,
|
||||
gop_size: 30,
|
||||
stun_server: Some("stun:stun.l.google.com:19302".to_string()),
|
||||
turn_server: None,
|
||||
|
||||
@@ -143,7 +143,7 @@ impl VideoCapturer {
|
||||
/// Create a new video capturer
|
||||
pub fn new(config: CaptureConfig) -> Self {
|
||||
let (state_tx, state_rx) = watch::channel(CaptureState::Stopped);
|
||||
let (frame_tx, _) = broadcast::channel(16); // Buffer up to 16 frames
|
||||
let (frame_tx, _) = broadcast::channel(64); // Buffer up to 64 frames for software encoding
|
||||
|
||||
Self {
|
||||
config,
|
||||
|
||||
@@ -126,7 +126,7 @@ impl Default for H264Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base: EncoderConfig::default(),
|
||||
bitrate_kbps: 8000,
|
||||
bitrate_kbps: 1000,
|
||||
gop_size: 30,
|
||||
fps: 30,
|
||||
input_format: H264InputFormat::Nv12,
|
||||
|
||||
@@ -77,7 +77,7 @@ impl Default for SharedVideoPipelineConfig {
|
||||
resolution: Resolution::HD720,
|
||||
input_format: PixelFormat::Yuyv,
|
||||
output_codec: VideoEncoderType::H264,
|
||||
bitrate_kbps: 8000,
|
||||
bitrate_kbps: 1000,
|
||||
fps: 30,
|
||||
gop_size: 30,
|
||||
encoder_backend: None,
|
||||
@@ -311,7 +311,7 @@ impl SharedVideoPipeline {
|
||||
config.input_format
|
||||
);
|
||||
|
||||
let (frame_tx, _) = broadcast::channel(16);
|
||||
let (frame_tx, _) = broadcast::channel(64); // Increased from 16 for software encoding
|
||||
let (running_tx, running_rx) = watch::channel(false);
|
||||
let nv12_size = (config.resolution.width * config.resolution.height * 3 / 2) as usize;
|
||||
let yuv420p_size = nv12_size; // Same size as NV12
|
||||
|
||||
@@ -35,8 +35,8 @@ impl Default for WebRtcConfig {
|
||||
turn_servers: vec![],
|
||||
enable_datachannel: true,
|
||||
video_codec: VideoCodec::H264,
|
||||
target_bitrate_kbps: 8000,
|
||||
max_bitrate_kbps: 5000,
|
||||
target_bitrate_kbps: 1000,
|
||||
max_bitrate_kbps: 2000,
|
||||
min_bitrate_kbps: 500,
|
||||
enable_audio: true,
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ impl Default for UniversalSessionConfig {
|
||||
codec: VideoEncoderType::H264,
|
||||
resolution: Resolution::HD720,
|
||||
input_format: PixelFormat::Mjpeg,
|
||||
bitrate_kbps: 8000,
|
||||
bitrate_kbps: 1000,
|
||||
fps: 30,
|
||||
gop_size: 30,
|
||||
audio_enabled: false,
|
||||
|
||||
@@ -179,7 +179,7 @@ const selectedDevice = ref<string>('')
|
||||
const selectedFormat = ref<string>('')
|
||||
const selectedResolution = ref<string>('')
|
||||
const selectedFps = ref<number>(30)
|
||||
const selectedBitrate = ref<number[]>([8000])
|
||||
const selectedBitrate = ref<number[]>([1000])
|
||||
|
||||
// UI state
|
||||
const applying = ref(false)
|
||||
|
||||
Reference in New Issue
Block a user