webrtc弱网-视频适配器(VideoAdapter)源码分析及算法原理

发布于:2025-08-02 ⋅ 阅读:(13) ⋅ 点赞:(0)
一、核心功能
  1. 视频帧适配:根据输出需求调整输入视频帧的分辨率和帧率

  2. 分辨率缩放:通过智能比例计算实现像素数优化

  3. 帧率控制:通过动态丢帧实现帧率限制

  4. 方向感知处理:独立处理横屏/竖屏视频的适配需求

  5. 对齐约束:确保输出分辨率满足源端和接收端的对齐要求

  6. 动态适应:运行时根据需求变化实时调整适配策略

二、核心算法原理

分辨率缩放算法(FindScale函数)

  1. 目标驱动:在不超过max_pixels的前提下接近target_pixels

  2. 比例因子:交替使用3/4和2/3缩放因子生成最佳比例

  3. 智能起点:根据输入分辨率选择2/3或4/4作为起始比例

  4. 最优选择:遍历所有可能比例,选择最接近目标像素数的方案

  5. 像素计算:scale_pixel_count = (num²/denom²) * input_pixels

帧率控制算法

  1. 双限制机制:取max_framerate_request_和output_format_request_.max_fps的最小值

  2. 时间戳跟踪:FramerateController根据时间间隔决定是否丢帧

  3. 动态调整:帧率限制可在运行时动态更新

三、关键数据结构
// 分数表示法(用于分辨率缩放)
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_;
};
四、核心方法详解
  1. 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)
  • 工作流程

    1. 帧计数更新(frames_in_++)

    2. 方向检测(横屏/竖屏)

    3. 帧率控制(DropFrame检查)

    4. 裁剪区域计算(保持目标宽高比)

    5. 缩放比例计算(FindScale)

    6. 对齐调整(roundUp)

    7. 输出分辨率计算

    8. 状态更新和日志记录

  1. OnOutputFormatRequest - 格式请求处理

void OnOutputFormatRequest(const absl::optional<VideoFormat>& format)
  • 处理逻辑

    • 分离横屏/竖屏参数

    • 维护两个版本的请求(当前使用+缓存)

    • 帧率控制器重置

  1. OnSinkWants - 接收端需求处理

void OnSinkWants(const rtc::VideoSinkWants& sink_wants)
  • 关键处理

    • 更新像素限制(target/max)

    • 计算分辨率对齐(LCM算法)

    • 处理requested_resolution特殊逻辑

    • 维护格式请求的缓存机制

五、设计亮点
  1. 智能比例选择

    // 交替使用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;
        }
    }
  2. 方向感知处理

    // 根据方向选择不同参数
    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 = /* 竖屏最大像素 */;
    }
  3. 对齐保证机制

    // 确保裁剪尺寸满足缩放和对齐要求
    *cropped_width = roundUp(*cropped_width, 
                            scale.denominator * resolution_alignment_, 
                            in_width);
  4. 请求缓存系统

    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_;
    }
六、典型工作流程
  1. 初始化阶段

    VideoAdapter adapter(16); // 创建16字节对齐的适配器
    adapter.OnOutputFormatRequest(1280x720, 30fps); // 设置基础输出格式
  2. 运行时适配

    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 {
        // 帧被丢弃
    }
  3. 动态调整

    // 接收端需求变化
    rtc::VideoSinkWants wants;
    wants.max_pixel_count = 640*480;
    wants.target_pixel_count = 320*240;
    wants.max_framerate_fps = 15;
    adapter.OnSinkWants(wants); // 立即生效
  4. 格式更新

    // 源端输出格式变化
    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;
}

该视频适配器设计通过智能比例选择、方向感知处理、动态请求缓存等机制,实现了高效灵活的视频帧适配功能,能够有效平衡视频质量和性能需求。