播放器视频后处理实践(二)氛围模式
点击蓝字,关注我们
01
在日常视频播放中,我们经常会遇到这样的问题:视频的长宽比例与设备屏幕不一致,导致画面上下或左右出现黑边。虽然这并不影响视频的正常播放,但从用户体验的角度来看,这些黑边往往打断了视觉的沉浸感,显得格外突兀。
为了解决这一问题,业界主流播放器(如 YouTube、Netflix)引入了一种被称为氛围模式(Ambient Mode)的视觉增强效果。它的核心思路是:
通过实时识别视频画面的主色调,并动态将其填充到黑边区域,使边缘色彩与视频内容保持一致,提升整体视觉统一性,从而营造出与视频内容相协调的氛围效果,让观众的观看体验更加自然和沉浸。
下面是YouTube的氛围模式效果:
youtube横屏效果
百度播放内核团队也将氛围模式效果应用到了视频播放场景,用于提升用户观看视频沉浸感,同时在百度App、好看App两款产品完成上线。本文将详细说明视频场景氛围模式技术方案。
02
氛围模式通过在播放内核视频后处理通道(FilterChain)添加一个AmbientFilter滤镜实现,其核心思路:通过AmbientFilter滤镜先将视频帧数据从GPU下载到CPU,然后将视频帧数据按块进行区域划分,划分完成后再通过颜色量化算法提取每个区域主色调,最后将各个区域主色调传给平台层,平台层拿到主色调进行绘制视频四周氛围效果。整体方案流程大致如下图所示:
2.1 视频帧采样
1. FBO 压缩:先将视频帧渲染到较低分辨率的 FBO(例如将 1080p 压缩到 108p),大幅减少待传输的数据量。
2. PBO 异步传输:利用 PBO 异步将帧数据从 GPU 下载到 CPU,从而避免阻塞主渲染线程。
2.2 主色调提取
2.2.1 视频帧区域划分
2.2.2 提取主色调
1. 中值切割法(Median Cut):将颜色空间递归分割成小立方体,取每个立方体的颜色中位数作为调色板颜色。
2. K-means聚类:将颜色按相似性分组,取每组的中心作为调色板颜色。
3. 八叉树算法:通过构建八叉树分层合并颜色,逐层减少叶子节点数量,最终保留高频颜色。
4. 流行色算法(Popularity):统计原图颜色出现的频率,选取高频颜色作为调色板。
2.2.3 中值切割法
a. 首先,将所有颜色视为一个大的颜色盒(即整个颜色空间的一个区域)。
b. 颜色盒包含图像中所有像素的颜色。
a. 在每次迭代中,选择颜色分量(红、绿、蓝)中范围最大的分量作为分割轴。这是为了最大限度地减少颜色空间的不均匀性。
a. 沿着选定的分割轴,根据颜色值的中值,将颜色盒分成两个较小的盒。
b. 这种方法确保每个新盒子中包含的颜色数量尽可能相等。
a. 对每个新的颜色盒重复步骤2和3,直到达到所需的颜色盒数量(通常是所需调色板的大小)。
a. 一旦颜色盒的数量达到预期的数量,对每个盒子计算平均颜色或中值颜色,将其作为代表颜色添加到调色板中。
a. 使用生成的调色板,重新映射原始图像中的每个像素到最接近的调色板颜色。
03
3.1 Android平台
<FrameLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="center"><com.baidu.cyberplayer.sdk.AmbientViewandroid:id="@+id/left_ambient"android:layout_width="xxxdp"android:layout_height="match_parent"/><FrameLayoutandroid:id="@+id/video_container"android:layout_width="wrap_content"android:layout_height="wrap_content"/><com.baidu.cyberplayer.sdk.AmbientViewandroid:id="@+id/right_ambient"android:layout_width="xxxdp"android:layout_height="match_parent"/></FrameLayout>
privatevoidupdateGradient() {mLinearGradient = new LinearGradient(0, 0, 0, getHeight(),mColors, null, Shader.TileMode.CLAMP);mPaint.setShader(mLinearGradient);invalidate();}
privatevoidstartColorAnimator() {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() {@OverridepublicvoidonAnimationUpdate(@NonNull ValueAnimator valueAnimator) {float progress = (float) valueAnimator.getAnimatedValue();interpolateColors(progress, lastColors);updateGradient();}});mColorAnimator.start();}/*** 插值计算color*/privatevoidinterpolateColors(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]);}}
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平台
- (void)setupSubLayers {_gradientLayer = [CAGradientLayer layer];_gradientLayer.frame = self.bounds;[self.layer addSublayer:_gradientLayer];_maskLayer = [CAGradientLayer layer];_maskLayer.frame = self.bounds;[self.layer addSublayer:_maskLayer];}
- (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) * progressgreen:fg + (tg - fg) * progressblue:fb + (tb - fb) * progressalpha:fa + (ta - fa) * progress];}
- (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);}
04
氛围模式已在百度内包括百度App和好看App两款App完成上线,其中百度App主要集中在搜索三方影视场景,好看App所有视频横屏场景(排除广告视频)。同时在视频观看时长、分发、完播率等UBS指标取得了正向收益,说明氛围模式给用户带来了不错的沉浸式观影体验。
下面是百度App和好看App效果展示:
百度App氛围模式
好看App氛围模式
5. 总结
1. 视觉沉浸:氛围模式通过在视频周围添加柔和的背景颜色,使屏幕的边缘与视频内容更好地融合。这种设计使得用户在观看视频时感觉更加沉浸,减少了视频与周围环境之间的视觉割裂
2. 舒适观看:这种模式可以减少长时间观看视频时的眼睛疲劳。通过在视频周围使用柔和的色彩过渡,可以缓解亮度差异带来的视觉刺激,从而提高观看舒适度。
3. 提升观感:氛围模式通过智能地调整背景色彩,使其与视频中的主要色调相匹配,提升整体观感。这使得视频内容更加突出,同时为观看者提供一种更为和谐的视觉体验。
END
推荐阅读