fix(video): 修正默认码率配置并优化软件编码器

- 将默认码率从 8 Mbps 降至 1 Mbps,更适合嵌入式设备
- 修复 WebRtcConfig 中 max_bitrate < target_bitrate 的逻辑错误
- 优化 libx264/libx265 软件编码器的低延迟配置
- 优化 libvpx (VP8/VP9) 的实时编码参数
This commit is contained in:
mofeng-git
2025-12-31 22:04:58 +08:00
parent 8be45155ac
commit bc85810849
11 changed files with 148 additions and 19 deletions

View File

@@ -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;
} }

View File

@@ -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,

View File

@@ -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_);

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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

View File

@@ -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,
} }

View File

@@ -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,

View File

@@ -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)