作者:播放内核团队
来源:百度App技术
1. 前言
在日常视频播放中,我们经常会遇到这样的问题:视频的长宽比例与设备屏幕不一致,导致画面上下或左右出现黑边。虽然这并不影响视频的正常播放,但从用户体验的角度来看,这些黑边往往打断了视觉的沉浸感,显得格外突兀。
为了解决这一问题,业界主流播放器(如 YouTube、Netflix)引入了一种被称为氛围模式(Ambient Mode)的视觉增强效果。它的核心思路是:
通过实时识别视频画面的主色调,并动态将其填充到黑边区域,使边缘色彩与视频内容保持一致,提升整体视觉统一性,从而营造出与视频内容相协调的氛围效果,让观众的观看体验更加自然和沉浸。
下面是YouTube的氛围模式效果:


百度播放内核团队也将氛围模式效果应用到了视频播放场景,用于提升用户观看视频沉浸感,同时在百度App、好看App两款产品完成上线。本文将详细说明视频场景氛围模式技术方案。
2. 整体技术方案
氛围模式通过在播放内核视频后处理通道(FilterChain)添加一个AmbientFilter滤镜实现,其核心思路:通过AmbientFilter滤镜先将视频帧数据从GPU下载到CPU,然后将视频帧数据按块进行区域划分,划分完成后再通过颜色量化算法提取每个区域主色调,最后将各个区域主色调传给平台层,平台层拿到主色调进行绘制视频四周氛围效果。整体方案流程大致如下图所示:

2.1 视频帧采样
为了提取视频的主色调,需要获取视频帧数据。但提取主色调并不要求每帧都下载,太频繁下载会拖垮应用性能,在视觉上也不会带来特别好的体验。因此我们对视频帧进行采样下载:在 25 FPS 的视频下,每隔约 50 帧(约 2 秒)采集一次帧数据。同时,为了避免将视频帧数据从 GPU 下载到 CPU 时阻塞渲染线程,我们采取了以下优化:
- FBO 压缩:先将视频帧渲染到较低分辨率的 FBO(例如将 1080p 压缩到 108p),大幅减少待传输的数据量。
- PBO 异步传输:利用 PBO 异步将帧数据从 GPU 下载到 CPU,从而避免阻塞主渲染线程。
通过这种方式,我们既能保证主色调提取的效率,又不会影响视频的流畅播放。渲染线程和氛围模式工作线程两个线程工作流程如下图:

2.2 主色调提取
2.2.1 视频帧区域划分
拿到视频帧数据后,我们先将视频帧划分出几个区域。项目中我们是将视频帧画面划分为:TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter, BottomRight 六个区域,如下图所示:

接下来我们提取出每块区域的主色调。
2.2.2 提取主色调
要提取画面主色调,我们是通过颜色量化技术实现的。颜色量化(Color Quantization) 是一种图像处理技术,目的是减少图像中使用的颜色数量,同时尽量保持原图的视觉效果。代表性的颜色量化算法有:
- 中值切割法(Median Cut):将颜色空间递归分割成小立方体,取每个立方体的颜色中位数作为调色板颜色。
- K-means聚类:将颜色按相似性分组,取每组的中心作为调色板颜色。
- 八叉树算法:通过构建八叉树分层合并颜色,逐层减少叶子节点数量,最终保留高频颜色。
- 流行色算法(Popularity):统计原图颜色出现的频率,选取高频颜色作为调色板。
这几种算法从各维度对比情况如下:

从算法的速度、精度以及实现复杂度等多维度考虑,氛围模式场景我们选用中值切割法完成视频画面主色调的提取。
2.2.3 中值切割法
中值切割法(Median Cut)是一种用于图像颜色量化的算法,算法核心思想是将颜色空间递归地分割成更小的区域,以减少图像中颜色数量。该算法的目标是在颜色空间中选择一组代表性的颜色,这些颜色可以用于生成调色板,从而减少图像的颜色数量,同时尽量保留图像的视觉效果。算法核心步骤如下:
1. 初始化颜色盒
- 首先,将所有颜色视为一个大的颜色盒(即整个颜色空间的一个区域)。
- 颜色盒包含图像中所有像素的颜色。
2. 选择分割轴
- 在每次迭代中,选择颜色分量(红、绿、蓝)中范围最大的分量作为分割轴。这是为了最大限度地减少颜色空间的不均匀性。
3. 按中值分割
- 沿着选定的分割轴,根据颜色值的中值,将颜色盒分成两个较小的盒。
- 这种方法确保每个新盒子中包含的颜色数量尽可能相等。
4. 递归分割
- 对每个新的颜色盒重复步骤2和3,直到达到所需的颜色盒数量(通常是所需调色板的大小)。
5. 生成调色板
- 一旦颜色盒的数量达到预期的数量,对每个盒子计算平均颜色或中值颜色,将其作为代表颜色添加到调色板中。
6. 颜色映射
- 使用生成的调色板,重新映射原始图像中的每个像素到最接近的调色板颜色。
中值切割算法核心流程如下图:

3. 平台渲染氛围效果
当native层提取完视频帧各区域主色调后,将色值传给平台层(Android/iOS)。平台层收到色值后,将色值渲染到视频四周以产生氛围效果。为保证各个区域色值过渡自然,以及前后两帧的色值平滑过渡,需要借助平台层渐变、动画、rgb插值等技术实现。 下面结合Android和iOS两个平台分别介绍具体思路。
3.1 Android平台
Android 使用自定义view技术,完成氛围色值的渲染。我们提供一个自定义view名为AmbientView 来完成这个功能。有了AmbientView之后,布局结构大致如下:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center">
<com.baidu.cyberplayer.sdk.AmbientView
android:id="@+id/left_ambient"
android:layout_width="xxxdp"
android:layout_height="match_parent"/>
<FrameLayout
android:id="@+id/video_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.baidu.cyberplayer.sdk.AmbientView
android:id="@+id/right_ambient"
android:layout_width="xxxdp"
android:layout_height="match_parent"/>
</FrameLayout>
上面为视频横屏下布局大致情况,id为video_container的FrameLayout是播放器容器,在播放器容器左右各摆放一个AmbientView渲染氛围模式,AmbientView的宽度会根据播放器的尺寸的变化在代码中动态调整。
AmbientView核心功能:
1. 相邻区域的主色调,使用LinearGradient拉出线形渐变。对于横屏视频,我们渐变方向就是从上至下。所以更新氛围色值的代码如下:
private void updateGradient() {
mLinearGradient = new LinearGradient(0, 0, 0, getHeight(),
mColors, null, Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);
invalidate();
}
2. 前后两帧氛围色值的切换,为了颜色切换不显得生硬,我们借助Android属性动画以及RGB插值实现色值缓慢渐变效果,核心代码如下:
private void startColorAnimator() {
int[] lastColors = new int[mLastColors.length];
for (int i = 0; i < lastColors.length; i++) {
lastColors[i] = mLastColors[i];
}
mColorAnimator = ValueAnimator.ofFloat(0, 1f);
mColorAnimator.setDuration(1500);
mColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(@NonNull ValueAnimator valueAnimator) {
float progress = (float) valueAnimator.getAnimatedValue();
interpolateColors(progress, lastColors);
updateGradient();
}
});
mColorAnimator.start();
}
/**
* 插值计算color
*/
private void interpolateColors(float progress, int[] lastColors) {
if (mCurColors == null || mCurColors.length <= 0) {
return;
}
ArgbEvaluator evaluator = new ArgbEvaluator();
for (int i = 0; i < mCurColors.length; i++) {
mColors[i] = (int) evaluator.evaluate(progress, lastColors[i], mCurColors[i]);
}
}
mColorAnimator是一个ValueAnimator对象,通过ValueAnimator我们创建一个1500ms的动画,在动画的更新函数里面,我们调用了interpolateColors,这个方法内部就是用ArgbEvaluator完成RGB颜色插值,更新到mColors数组中。最后调用updateGradient方法触发AmbientView重绘。
3. 渐变遮罩:最后我们还要在上面添加一层黑色渐变遮罩,保证氛围区域不要太突兀,以免过度吸引用户眼球,导致用户注意力不在视频内容本身上面。黑色遮罩实现也非常简单,代码如下所示:
float[] mPositions = {0.0f, 1.0f};
int[] mMaskColors = {0x88000000, 0xff000000};
// 从左到右渐变
mMaskLinearGradient = new LinearGradient(0, 0, getWidth(), 0,
mMaskColors, mPositions, Shader.TileMode.CLAMP);
mMaskPaint.setShader(mMaskLinearGradient);
// 绘制黑色渐变蒙层
canvas.drawRect(0, 0, getWidth(), getHeight(), mMaskPaint);
3.2 iOS平台
iOS端同样提供了一个自定义的 AmbientView(氛围视图),为视频播放场景提供动态渐变背景和遮罩效果,增强视觉沉浸感。
1. 双图层架构设计:采用主渐变层与遮罩层分离的架构方案,确保色彩渲染与边缘遮罩效果互不干扰,提升整体渲染效率。
- (void)setupSubLayers {
_gradientLayer = [CAGradientLayer layer];
_gradientLayer.frame = self.bounds;
[self.layer addSublayer:_gradientLayer];
_maskLayer = [CAGradientLayer layer];
_maskLayer.frame = self.bounds;
[self.layer addSublayer:_maskLayer];
}
2. 流畅动画引擎:基于CADisplayLink构建动画循环,通过实时颜色插值计算,实现细腻流畅的色彩过渡效果。
- (void)startAnimation {
// 核心功能代码
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateColors)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)updateColors {
CGFloat progress = MIN(1.0, (CACurrentMediaTime() - self.startTime) / self.animationDuration);
NSMutableArray *interpolated = [NSMutableArray array];
for (NSUInteger i = 0; i < self.endColors.count; i++) {
UIColor *from = i < self.startColors.count ? self.startColors[i] : [UIColor clearColor];
UIColor *to = self.endColors[i];
[interpolated addObject:(__bridge id)[self interpolateFrom:from to:to progress:progress].CGColor];
}
_gradientLayer.colors = interpolated;
}
- (UIColor *)interpolateFrom:(UIColor *)from to:(UIColor *)to progress:(CGFloat)progress {
CGFloat fr, fg, fb, fa, tr, tg, tb, ta;
[from getRed:&fr green:&fg blue:&fb alpha:&fa];
[to getRed:&tr green:&tg blue:&tb alpha:&ta];
return [UIColor colorWithRed:fr + (tr - fr) * progress
green:fg + (tg - fg) * progress
blue:fb + (tb - fb) * progress
alpha:fa + (ta - fa) * progress];
}
3. 渐变遮罩:采用多段式渐变遮罩配合加速曲线算法,打造自然的边缘过渡,有效增强视觉层次感。
- (void)makeMaskColorsAndLocations {
const NSInteger steps = 6;
for (NSInteger i = 0; i < steps; i++) {
CGFloat t = (CGFloat)i / (steps - 1);
CGFloat acceleratedT = t * t;
CGFloat currentAlpha = a + (1.0 - a) * acceleratedT;
UIColor *color = [UIColor colorWithRed:r green:g blue:b alpha:currentAlpha];
[_maskColors addObject:(__bridge id)color.CGColor];
[_maskColorsLocations addObject:@(t)];
}
_maskLayer.colors = _maskColors;
_maskLayer.locations = _maskColorsLocations;
_maskLayer.startPoint = CGPointMake(0, 0);
_maskLayer.endPoint = CGPointMake(1, 0);
}
该实现确保了氛围渲染的高性能和优美视觉效果,为用户提供了沉浸式的观看体验。
4. 效果展示
氛围模式已在百度内包括百度App和好看App两款App完成上线,其中百度App主要集中在搜索三方影视场景,好看App所有视频横屏场景(排除广告视频)。同时在视频观看时长、分发、完播率等UBS指标取得了正向收益,说明氛围模式给用户带来了不错的沉浸式观影体验。
下面是百度App和好看App效果展示:

5. 总结
氛围模式是一种视觉增强功能,通过技术手段有效解决了视频比例不匹配导致的黑边问题,显著提升了用户视觉体验,主要表现在如下几个方面:
- 视觉沉浸:氛围模式通过在视频周围添加柔和的背景颜色,使屏幕的边缘与视频内容更好地融合。这种设计使得用户在观看视频时感觉更加沉浸,减少了视频与周围环境之间的视觉割裂
- 舒适观看:这种模式可以减少长时间观看视频时的眼睛疲劳。通过在视频周围使用柔和的色彩过渡,可以缓解亮度差异带来的视觉刺激,从而提高观看舒适度。
- 提升观感:氛围模式通过智能地调整背景色彩,使其与视频中的主要色调相匹配,提升整体观感。这使得视频内容更加突出,同时为观看者提供一种更为和谐的视觉体验。
通过本文介绍的技术方案,开发者可以实现类似主流视频平台的高质量氛围模式效果,为用户带来更加沉浸的观看体验。
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。