mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-29 00:51:53 +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"
|
#define LOG_MODULE "UTIL"
|
||||||
#include "log.h"
|
#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 {
|
namespace util_encode {
|
||||||
|
|
||||||
void set_av_codec_ctx(AVCodecContext *c, const std::string &name, int kbs,
|
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->has_b_frames = 0;
|
||||||
c->max_b_frames = 0;
|
c->max_b_frames = 0;
|
||||||
if (gop > 0 && gop < std::numeric_limits<int16_t>::max()) {
|
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->framerate = av_make_q(fps, 1);
|
||||||
c->flags |= AV_CODEC_FLAG2_LOCAL_HEADER;
|
c->flags |= AV_CODEC_FLAG2_LOCAL_HEADER;
|
||||||
c->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
c->flags |= AV_CODEC_FLAG_LOW_DELAY;
|
||||||
|
|
||||||
|
// 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->slices = 1;
|
||||||
c->thread_type = FF_THREAD_SLICE;
|
c->thread_type = FF_THREAD_SLICE;
|
||||||
c->thread_count = c->slices;
|
c->thread_count = c->slices;
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/obsproject/obs-studio/blob/3cc7dc0e7cf8b01081dc23e432115f7efd0c8877/plugins/obs-ffmpeg/obs-ffmpeg-mux.c#L160
|
// https://github.com/obsproject/obs-studio/blob/3cc7dc0e7cf8b01081dc23e432115f7efd0c8877/plugins/obs-ffmpeg/obs-ffmpeg-mux.c#L160
|
||||||
c->color_range = AVCOL_RANGE_MPEG;
|
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_primaries = AVCOL_PRI_SMPTE170M;
|
||||||
c->color_trc = AVCOL_TRC_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;
|
c->profile = FF_PROFILE_H264_HIGH;
|
||||||
} else if (name.find("hevc") != std::string::npos) {
|
} else if (name.find("hevc") != std::string::npos) {
|
||||||
c->profile = FF_PROFILE_HEVC_MAIN;
|
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;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ extern "C" {
|
|||||||
namespace util_encode {
|
namespace util_encode {
|
||||||
|
|
||||||
void set_av_codec_ctx(AVCodecContext *c, const std::string &name, int kbs,
|
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_lantency_free(void *priv_data, const std::string &name);
|
||||||
bool set_quality(void *priv_data, const std::string &name, int quality);
|
bool set_quality(void *priv_data, const std::string &name, int quality);
|
||||||
bool set_rate_control(AVCodecContext *c, const std::string &name, int rc,
|
bool set_rate_control(AVCodecContext *c, const std::string &name, int rc,
|
||||||
|
|||||||
@@ -235,12 +235,15 @@ public:
|
|||||||
c_->pix_fmt =
|
c_->pix_fmt =
|
||||||
hw_pixfmt_ != AV_PIX_FMT_NONE ? hw_pixfmt_ : (AVPixelFormat)pixfmt_;
|
hw_pixfmt_ != AV_PIX_FMT_NONE ? hw_pixfmt_ : (AVPixelFormat)pixfmt_;
|
||||||
c_->sw_pix_fmt = (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_)) {
|
if (!util_encode::set_lantency_free(c_->priv_data, name_)) {
|
||||||
LOG_ERROR(std::string("set_lantency_free failed, name: ") + name_);
|
LOG_ERROR(std::string("set_lantency_free failed, name: ") + name_);
|
||||||
return false;
|
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_rate_control(c_, name_, rc_, q_);
|
||||||
util_encode::set_gpu(c_->priv_data, name_, gpu_);
|
util_encode::set_gpu(c_->priv_data, name_, gpu_);
|
||||||
util_encode::force_hw(c_->priv_data, name_);
|
util_encode::force_hw(c_->priv_data, name_);
|
||||||
|
|||||||
@@ -119,14 +119,14 @@ impl CodecInfo {
|
|||||||
pub fn soft() -> CodecInfos {
|
pub fn soft() -> CodecInfos {
|
||||||
CodecInfos {
|
CodecInfos {
|
||||||
h264: Some(CodecInfo {
|
h264: Some(CodecInfo {
|
||||||
name: "h264".to_owned(),
|
name: "libx264".to_owned(),
|
||||||
mc_name: Default::default(),
|
mc_name: Default::default(),
|
||||||
format: H264,
|
format: H264,
|
||||||
hwdevice: AV_HWDEVICE_TYPE_NONE,
|
hwdevice: AV_HWDEVICE_TYPE_NONE,
|
||||||
priority: Priority::Soft as _,
|
priority: Priority::Soft as _,
|
||||||
}),
|
}),
|
||||||
h265: Some(CodecInfo {
|
h265: Some(CodecInfo {
|
||||||
name: "hevc".to_owned(),
|
name: "libx265".to_owned(),
|
||||||
mc_name: Default::default(),
|
mc_name: Default::default(),
|
||||||
format: H265,
|
format: H265,
|
||||||
hwdevice: AV_HWDEVICE_TYPE_NONE,
|
hwdevice: AV_HWDEVICE_TYPE_NONE,
|
||||||
|
|||||||
@@ -375,7 +375,7 @@ impl Default for StreamConfig {
|
|||||||
Self {
|
Self {
|
||||||
mode: StreamMode::Mjpeg,
|
mode: StreamMode::Mjpeg,
|
||||||
encoder: EncoderType::Auto,
|
encoder: EncoderType::Auto,
|
||||||
bitrate_kbps: 8000,
|
bitrate_kbps: 1000,
|
||||||
gop_size: 30,
|
gop_size: 30,
|
||||||
stun_server: Some("stun:stun.l.google.com:19302".to_string()),
|
stun_server: Some("stun:stun.l.google.com:19302".to_string()),
|
||||||
turn_server: None,
|
turn_server: None,
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ impl VideoCapturer {
|
|||||||
/// Create a new video capturer
|
/// Create a new video capturer
|
||||||
pub fn new(config: CaptureConfig) -> Self {
|
pub fn new(config: CaptureConfig) -> Self {
|
||||||
let (state_tx, state_rx) = watch::channel(CaptureState::Stopped);
|
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 {
|
Self {
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ impl Default for H264Config {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
base: EncoderConfig::default(),
|
base: EncoderConfig::default(),
|
||||||
bitrate_kbps: 8000,
|
bitrate_kbps: 1000,
|
||||||
gop_size: 30,
|
gop_size: 30,
|
||||||
fps: 30,
|
fps: 30,
|
||||||
input_format: H264InputFormat::Nv12,
|
input_format: H264InputFormat::Nv12,
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ impl Default for SharedVideoPipelineConfig {
|
|||||||
resolution: Resolution::HD720,
|
resolution: Resolution::HD720,
|
||||||
input_format: PixelFormat::Yuyv,
|
input_format: PixelFormat::Yuyv,
|
||||||
output_codec: VideoEncoderType::H264,
|
output_codec: VideoEncoderType::H264,
|
||||||
bitrate_kbps: 8000,
|
bitrate_kbps: 1000,
|
||||||
fps: 30,
|
fps: 30,
|
||||||
gop_size: 30,
|
gop_size: 30,
|
||||||
encoder_backend: None,
|
encoder_backend: None,
|
||||||
@@ -311,7 +311,7 @@ impl SharedVideoPipeline {
|
|||||||
config.input_format
|
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 (running_tx, running_rx) = watch::channel(false);
|
||||||
let nv12_size = (config.resolution.width * config.resolution.height * 3 / 2) as usize;
|
let nv12_size = (config.resolution.width * config.resolution.height * 3 / 2) as usize;
|
||||||
let yuv420p_size = nv12_size; // Same size as NV12
|
let yuv420p_size = nv12_size; // Same size as NV12
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ impl Default for WebRtcConfig {
|
|||||||
turn_servers: vec![],
|
turn_servers: vec![],
|
||||||
enable_datachannel: true,
|
enable_datachannel: true,
|
||||||
video_codec: VideoCodec::H264,
|
video_codec: VideoCodec::H264,
|
||||||
target_bitrate_kbps: 8000,
|
target_bitrate_kbps: 1000,
|
||||||
max_bitrate_kbps: 5000,
|
max_bitrate_kbps: 2000,
|
||||||
min_bitrate_kbps: 500,
|
min_bitrate_kbps: 500,
|
||||||
enable_audio: true,
|
enable_audio: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ impl Default for UniversalSessionConfig {
|
|||||||
codec: VideoEncoderType::H264,
|
codec: VideoEncoderType::H264,
|
||||||
resolution: Resolution::HD720,
|
resolution: Resolution::HD720,
|
||||||
input_format: PixelFormat::Mjpeg,
|
input_format: PixelFormat::Mjpeg,
|
||||||
bitrate_kbps: 8000,
|
bitrate_kbps: 1000,
|
||||||
fps: 30,
|
fps: 30,
|
||||||
gop_size: 30,
|
gop_size: 30,
|
||||||
audio_enabled: false,
|
audio_enabled: false,
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ const selectedDevice = ref<string>('')
|
|||||||
const selectedFormat = ref<string>('')
|
const selectedFormat = ref<string>('')
|
||||||
const selectedResolution = ref<string>('')
|
const selectedResolution = ref<string>('')
|
||||||
const selectedFps = ref<number>(30)
|
const selectedFps = ref<number>(30)
|
||||||
const selectedBitrate = ref<number[]>([8000])
|
const selectedBitrate = ref<number[]>([1000])
|
||||||
|
|
||||||
// UI state
|
// UI state
|
||||||
const applying = ref(false)
|
const applying = ref(false)
|
||||||
|
|||||||
Reference in New Issue
Block a user