一、核心功能
视频帧适配:根据输出需求调整输入视频帧的分辨率和帧率
分辨率缩放:通过智能比例计算实现像素数优化
帧率控制:通过动态丢帧实现帧率限制
方向感知处理:独立处理横屏/竖屏视频的适配需求
对齐约束:确保输出分辨率满足源端和接收端的对齐要求
动态适应:运行时根据需求变化实时调整适配策略
二、核心算法原理
分辨率缩放算法(FindScale函数):
目标驱动:在不超过max_pixels的前提下接近target_pixels
比例因子:交替使用3/4和2/3缩放因子生成最佳比例
智能起点:根据输入分辨率选择2/3或4/4作为起始比例
最优选择:遍历所有可能比例,选择最接近目标像素数的方案
像素计算:scale_pixel_count = (num²/denom²) * input_pixels
帧率控制算法:
双限制机制:取max_framerate_request_和output_format_request_.max_fps的最小值
时间戳跟踪:FramerateController根据时间间隔决定是否丢帧
动态调整:帧率限制可在运行时动态更新
三、关键数据结构
// 分数表示法(用于分辨率缩放) struct Fraction { int numerator; // 分子 int denominator; // 分母 void DivideByGcd(); // 约分方法 int scale_pixel_count(int input_pixels); // 像素计算 }; // 输出格式请求 struct OutputFormatRequest { absl::optional<std::pair<int, int>> target_landscape_aspect_ratio; // 横屏宽高比 absl::optional<int> max_landscape_pixel_count; // 横屏最大像素 absl::optional<std::pair<int, int>> target_portrait_aspect_ratio; // 竖屏宽高比 absl::optional<int> max_portrait_pixel_count; // 竖屏最大像素 absl::optional<int> max_fps; // 最大帧率 }; // 视频适配器核心类 class VideoAdapter { // 状态跟踪 int frames_in_, frames_out_, frames_scaled_; int previous_width_, previous_height_; // 配置参数 const int source_resolution_alignment_; int resolution_alignment_; // 请求参数 OutputFormatRequest output_format_request_; int resolution_request_target_pixel_count_; int resolution_request_max_pixel_count_; int max_framerate_request_; };
四、核心方法详解
AdaptFrameResolution - 帧适配核心方法
bool AdaptFrameResolution(int in_width, int in_height, int64_t in_timestamp_ns, int* cropped_width, int* cropped_height, int* out_width, int* out_height)
工作流程:
帧计数更新(frames_in_++)
方向检测(横屏/竖屏)
帧率控制(DropFrame检查)
裁剪区域计算(保持目标宽高比)
缩放比例计算(FindScale)
对齐调整(roundUp)
输出分辨率计算
状态更新和日志记录
OnOutputFormatRequest - 格式请求处理
void OnOutputFormatRequest(const absl::optional<VideoFormat>& format)
处理逻辑:
分离横屏/竖屏参数
维护两个版本的请求(当前使用+缓存)
帧率控制器重置
OnSinkWants - 接收端需求处理
void OnSinkWants(const rtc::VideoSinkWants& sink_wants)
关键处理:
更新像素限制(target/max)
计算分辨率对齐(LCM算法)
处理requested_resolution特殊逻辑
维护格式请求的缓存机制
五、设计亮点
智能比例选择:
// 交替使用3/4和2/3缩放因子 while (current_scale.scale_pixel_count(input_pixels) > target_pixels) { if (current_scale.numerator % 3 == 0 && current_scale.denominator % 2 == 0) { // 应用2/3缩放 current_scale.numerator /= 3; current_scale.denominator /= 2; } else { // 应用3/4缩放 current_scale.numerator *= 3; current_scale.denominator *= 4; } }
方向感知处理:
// 根据方向选择不同参数 if (in_width > in_height) { target_aspect_ratio = output_format_request_.target_landscape_aspect_ratio; max_pixel_count = /* 横屏最大像素 */; } else { target_aspect_ratio = output_format_request_.target_portrait_aspect_ratio; max_pixel_count = /* 竖屏最大像素 */; }
对齐保证机制:
// 确保裁剪尺寸满足缩放和对齐要求 *cropped_width = roundUp(*cropped_width, scale.denominator * resolution_alignment_, in_width);
请求缓存系统:
if (sink_wants.requested_resolution) { if (!stashed_output_format_request_) { // 缓存当前请求 stashed_output_format_request_ = output_format_request_; } // 使用requested_resolution覆盖 } else if (stashed_output_format_request_) { // 恢复缓存请求 output_format_request_ = *stashed_output_format_request_; }
六、典型工作流程
初始化阶段:
VideoAdapter adapter(16); // 创建16字节对齐的适配器 adapter.OnOutputFormatRequest(1280x720, 30fps); // 设置基础输出格式
运行时适配:
int cropped_w, cropped_h, out_w, out_h; if (adapter.AdaptFrameResolution(1920, 1080, timestamp, &cropped_w, &cropped_h, &out_w, &out_h)) { // 使用适配后的帧(cropped_w x cropped_h -> out_w x out_h) } else { // 帧被丢弃 }
动态调整:
// 接收端需求变化 rtc::VideoSinkWants wants; wants.max_pixel_count = 640*480; wants.target_pixel_count = 320*240; wants.max_framerate_fps = 15; adapter.OnSinkWants(wants); // 立即生效
格式更新:
// 源端输出格式变化 adapter.OnOutputFormatRequest(absl::nullopt, 1920*1080, 60);
源码解析
// 查找最优缩放比例 // 输入:原始宽高、目标像素数、最大像素数、是否启用智能起始比例 // 输出:满足约束的最佳分数表示 Fraction FindScale(int input_width, int input_height, int target_pixels, int max_pixels, bool variable_start_scale_factor) { // 仅当目标像素小于输入像素时才需要缩放 if (target_pixels >= input_pixels) return Fraction{1, 1}; // 智能起始比例选择 Fraction current_scale = Fraction{1, 1}; if (variable_start_scale_factor) { // 当分辨率能被3整除时,起始比例为2/3 if (input_width % 3 == 0 && input_height % 3 == 0) { current_scale = Fraction{6, 6}; // 等效2/3 } // 当分辨率能被9整除时,起始比例为4/9 if (input_width % 9 == 0 && input_height % 9 == 0) { current_scale = Fraction{36, 36}; // 等效4/9 } } // 比例迭代优化:交替使用3/4和2/3 while (当前比例计算的像素 > 目标像素) { if (当前比例分子能被3整除 && 分母能被2整除) { // 应用2/3缩放 current_scale.numerator /= 3; current_scale.denominator /= 2; } else { // 应用3/4缩放 current_scale.numerator *= 3; current_scale.denominator *= 4; } // 检查是否优于当前最佳方案 if (输出像素 <= 最大像素 && 更接近目标像素) { 更新最佳比例; } } return 最佳比例; } // 帧适配核心方法 bool VideoAdapter::AdaptFrameResolution(/* 参数 */) { // 步骤1:帧率控制决策 if (需要丢帧) { // 每90帧记录日志 if ((frames_in_ - frames_out_) % 90 == 0) { RTC_LOG(LS_INFO) << "VAdapt Drop Frame: ..."; } return false; } // 步骤2:分辨率合法性检查 if (in_width < kMinWidth || in_height < kMinHeight) { RTC_LOG(LS_INFO) << "VAdapt Drop Frame: ..."; return false; } // 步骤3:基于方向的目标参数选择 absl::optional<std::pair<int, int>> target_aspect_ratio; if (横屏) { // 使用横屏参数 } else { // 使用竖屏参数 } // 步骤4:裁剪区域计算(保持宽高比) if (需要裁剪) { *cropped_width = std::min(in_width, (int)(in_height * 请求宽高比)); *cropped_height = std::min(in_height, (int)(in_width / 请求宽高比)); } // 步骤5:计算最优缩放比例 Fraction scale = FindScale(/* 参数 */); // 步骤6:分辨率对齐调整 *cropped_width = roundUp(/* 对齐到scale.denominator * alignment */); *cropped_height = roundUp(/* 同上 */); // 步骤7:计算输出分辨率 *out_width = *cropped_width / scale.denominator * scale.numerator; *out_height = *cropped_height / scale.denominator * scale.numerator; // 步骤8:状态更新和日志 if (分辨率变化) { ++adaption_changes_; RTC_LOG(LS_INFO) << "Frame size changed: ..."; } return true; }
该视频适配器设计通过智能比例选择、方向感知处理、动态请求缓存等机制,实现了高效灵活的视频帧适配功能,能够有效平衡视频质量和性能需求。