Android WebRTC 视频的裁剪与缩放

如果一个设备屏幕是 16:9 的宽高比,另外一个设备屏幕是 32:9 的宽高比。这种情况下互相共享屏幕,应该怎么处理两个设备之间屏幕宽高比的差异呢?

裁剪&缩放时机

什么时候是最合适裁剪时机呢?

我们思考有几个可能的地方:

  1. sdp 会交换双方设备的网络地址与屏幕分辨率尺寸等信息
  2. 采集端采集视频到传输之间
  3. 渲染端接收到视频帧到渲染到 surfaceview 之间

[Android WebRTC VideoFrame] 一文中提及到在采集时候做美颜和滤镜的处理,官方既然已经提供了在视频帧传输前的 hook 处理时机,这个时机同样适用于裁剪和缩放,我们认为这个时机就是最佳时机。

VideoProcessor

/**
 * Applies the frame adaptation parameters to a frame. Returns null if the frame is meant to be
 * dropped. Returns a new frame. The caller is responsible for releasing the returned frame.
 */
public static @Nullable VideoFrame applyFrameAdaptationParameters(
    VideoFrame frame, FrameAdaptationParameters parameters) {
  if (parameters.drop) {
    return null;
  }

  final VideoFrame.Buffer adaptedBuffer =
      frame.getBuffer().cropAndScale(parameters.cropX, parameters.cropY, parameters.cropWidth,
          parameters.cropHeight, parameters.scaleWidth, parameters.scaleHeight);
  return new VideoFrame(adaptedBuffer, frame.getRotation(), parameters.timestampNs);
}

VideoFrame & cropAndScale

VideoFrame 中提供了这样一个方法

/**
 * Crops a region defined by `cropx`, `cropY`, `cropWidth` and `cropHeight`. Scales it to size
 * `scaleWidth` x `scaleHeight`.
 */
@CalledByNative("Buffer")
Buffer cropAndScale(
    int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight);

做下各个参数的释义:

参数说明

  • cropX: 裁剪区域的左上角的 X 坐标。
  • cropY: 裁剪区域的左上角的 Y 坐标。
  • cropWidth: 裁剪区域的宽度。
  • cropHeight: 裁剪区域的高度。
  • scaleWidth: 裁剪后图像的目标宽度。
  • scaleHeight: 裁剪后图像的目标高度。

详细含义

  1. 裁剪 (Crop) :

    • 通过指定 (cropX, cropY) 确定裁剪区域的左上角。
    • cropWidthcropHeight 决定了裁剪区域的大小。
    • 这个过程会从原始图像中提取一个子矩形区域。
  2. 缩放 (Scale) :

    • 在裁剪区域确定后,将该区域缩放到指定的 scaleWidthscaleHeight
    • 缩放操作可以是放大也可以是缩小,具体取决于裁剪区域和目标尺寸的相对大小。

实践

时机有了,方法也有了,接下来我们来实践一下。 先取两块一样大小的 pad 都做横屏 设备宽高是:Physical size: 1876x3000

为什么打印出来的是 1080 * 1726

FrameAdaptationParameters{cropX=0, cropY=0, cropWidth=1726, cropHeight=1080, scaleWidth=1726, scaleHeight=1080, timestampNs=27612737112000, drop=false}

1080 / 1726 = 0.625 1876 / 3000 = 0.625

宽高比是一样的

每个方法都是本人亲自试验了的,直接提供一个轮子:

public class CropOrScaleUtils {

    /**
     * 裁剪自身中间一半,不缩放. 视觉中心*2
     *
     * @param parameters
     */
    public static void halfMiddle(VideoProcessor.FrameAdaptationParameters parameters) {
        if (parameters == null) {
            return;
        }

        parameters.cropX = parameters.cropWidth >> 2;
        parameters.cropY = parameters.cropHeight >> 2;
        parameters.cropWidth >>= 1;
        parameters.cropHeight >>= 1;
    }

    /**
     * 裁剪自身上半部分,高度缩小2 . 视觉上部
     *
     * @param parameters
     */
    public static void halfTop(VideoProcessor.FrameAdaptationParameters parameters) {
        if (parameters == null) {
            return;
        }
        parameters.cropHeight >>= 1;
        parameters.scaleHeight >>= 1;//高度必须缩小 2 不然会拉伸
    }


    /**
     * 裁剪自身下半部分,高度缩小2 . 视觉下部
     *
     * @param parameters
     */
    public static void halfBottom(VideoProcessor.FrameAdaptationParameters parameters) {
        if (parameters == null) {
            return;
        }

        parameters.cropY = parameters.cropHeight >> 1;
        parameters.cropHeight >>= 1;
        parameters.scaleHeight >>= 1;//高度必须缩小 2 不然会拉伸
    }

    /**
     * 裁剪自身左半部分,宽度缩小2 . 视觉左部
     *
     * @param parameters
     */
    public static void halfLeft(VideoProcessor.FrameAdaptationParameters parameters) {
        if (parameters == null) {
            return;
        }
        parameters.cropWidth >>= 1;
        parameters.scaleWidth >>= 1;//宽度必须缩小 2 不然会拉伸
    }

    /**
     * 裁剪自身右半部分,宽度缩小2 . 视觉右部
     *
     * @param parameters
     */
    public static void halfRight(VideoProcessor.FrameAdaptationParameters parameters) {
        if (parameters == null) {
            return;
        }

        parameters.cropX = parameters.cropWidth >> 1;
        parameters.cropWidth >>= 1;
        parameters.scaleWidth >>= 1;//宽度必须缩小 2 不然会拉伸
    }

    /**
     * 模拟同尺寸屏宽高逆转方案1
     * 宽高逆转:竖屏向横屏上投或者横屏向竖屏上投
     * 暴力缩放会比例失调,但是无黑边填充满屏幕
     */
    public static void horizontalAndVerticalReversal1(VideoProcessor.FrameAdaptationParameters parameters) {
        int temp = parameters.scaleHeight;
        parameters.scaleHeight = parameters.scaleWidth;
        parameters.scaleWidth = temp;
    }


    /**
     * 模拟同尺寸屏宽高逆转方案2
     * 宽高逆转:竖屏向横屏上投或者横屏向竖屏上投
     * 无黑边且保持比例,没有拉伸感,会损失部分画面
     */
    public static void horizontalAndVerticalReversal2(VideoProcessor.FrameAdaptationParameters parameters) {
        //交换宽高
        int temp = parameters.scaleHeight;
        parameters.scaleHeight = parameters.scaleWidth;
        parameters.scaleWidth = temp;

        // 计算裁剪后的宽高比例
        float originalAspectRatio = (float) parameters.cropWidth / parameters.cropHeight;
        float newAspectRatio = (float) parameters.scaleWidth / parameters.scaleHeight;

        if (originalAspectRatio > newAspectRatio) {
            // 原始画面更宽,需要裁剪宽度
            int newCropWidth = (int) (parameters.cropHeight * newAspectRatio);
            int cropWidthDiff = parameters.cropWidth - newCropWidth;
            parameters.cropX += cropWidthDiff / 2;
            parameters.cropWidth = newCropWidth;
        } else {
            // 原始画面更高,需要裁剪高度
            int newCropHeight = (int) (parameters.cropWidth / newAspectRatio);
            int cropHeightDiff = parameters.cropHeight - newCropHeight;
            parameters.cropY += cropHeightDiff / 2;
            parameters.cropHeight = newCropHeight;
        }

        // 需要确保裁剪后的尺寸仍然是正数
        parameters.cropWidth = Math.max(1, parameters.cropWidth);
        parameters.cropHeight = Math.max(1, parameters.cropHeight);
    }

    /**
     * 方法3 什么都不做。既能保持画面完整性,也能保证不被拉伸,只是存在黑边。
     *
     * @param parameters
     */
    public static void horizontalAndVerticalReversal3(VideoProcessor.FrameAdaptationParameters parameters) {

    }
}

投屏传输(右投左)+中间裁剪保留原始宽高比游戏画面示例:

总结

设备间传输视频画面最优解是两个设备是两个设备尺寸分辨率一样,或者是设备尺寸不一样宽高比一样也可,但是实际情况往往有很多出入:

webrtc 视频传输如何去处理设备间屏幕或者宽高比差异有以下三种方式

  1. 保留原输出宽高比,保留所有内容可见,会存在黑边 (默认处理方式)
  2. 填充满屏幕,保持原输出宽高比,但是会损失部分画面内容
  3. 填充满屏幕,保留所有内容可见,但是会视觉呈现拉伸效果

如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料


在这里插入图片描述
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值