android视频编码优化方法

9 篇文章 0 订阅
2 篇文章 0 订阅

概述

视频编码会影响串流质量的多个指标,包括画质、流畅度和延迟。除了编码参数,采集和图像处理也对最终的编码效果产生影响。它们通常会综合影响多个质量指标。本文将从视频采集、图像处理和编码参数几个方面来分享相关信息。

1、编码参数

编码参数直接影响到视频效果,主要参数有:编码格式、分辨率、码率、码率模式、帧率、I帧间隔、帧内刷新、量化参数、去模糊、Profile和Level、补帧。

1.1 编码格式

H.265通常会提供更好的画质。这是因为H.265使用更高效的压缩算法,能够以更少的比特率实现相同或更好的画质,或者以相同比特率提供更高的画质。

但要注意的是,这并不是绝对的规则。编码画质不仅取决于编码器本身,还取决于视频内容的特性。一些视频可能在H.264下表现良好,而另一些则在H.265下表现良好,这取决于编码算法对特定内容的适应能力。

format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC);//设置为H264
format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_HEVC);//设置为H265
1.2 分辨率

一般情况下,编码分辨率与采集的画面分辨率相同,可保持最好的清晰度;但是,当比特率非常低的情况下,降低分辨率通常会产生更好的画质。这是因为比特率限制了可用的数据量,低比特率下保留高分辨率会导致视频压缩伪影、模糊和失真,从而影响画质。通过降低分辨率,你可以在有限的比特率下更好地保持图像质量。

format.setInteger(MediaFormat.KEY_WIDTH, width);
format.setInteger(MediaFormat.KEY_HEIGHT, height);
1.3 码率

在其它参数相同的情况下,码率越高,清晰度越高,高比特率可以保留更多图像细节,特别是在变化剧烈或复杂场景中,画面更为清晰,但在码率相当高的情况下,再一味的提高码率对清晰度的提升不会太明显。且码率越高延迟会相应的增加,因此需要设置一个比较合适的码率在延迟和画质中保持一个相对好的效果。

format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
1.4 码率模式

一般来说,现在大部分Android设备支持CQ、VBR、CBR,实际上设备的硬件和软件能力以及制造商的设计决策会造成不同的方案对码率模式的支持稍有不同,Android SDK中定义的码率模式有以下几种:

/** Constant quality mode */
public static final int BITRATE_MODE_CQ = 0;
/** Variable bitrate mode */
public static final int BITRATE_MODE_VBR = 1;
/** Constant bitrate mode */
public static final int BITRATE_MODE_CBR = 2;
/** Constant bitrate mode with frame drops */
public static final int BITRATE_MODE_CBR_FD =  3;

经验证我们的设备默认是不支持CQ和CBR_FD(另外一种叫法是CBR_VFR),后来对修改了系统支持了VFR,但也不是标准值3,而是4,这几种模式的特点如下:

CBR(Constant Bit Rate):恒定比特率

  • 视频编码器会以固定的比特率来编码视频。在不设置QP的情况下,每秒传输的比特数是恒定的;即使设置了QP,码率也相对于VBR更平稳,在传输时码率波动小,会比较平滑。

VBR(Variable Bit Rate):可变比特率

  • 允许编码器根据视频内容的复杂性来动态调整比特率。在视频中,某些部分可能需要更多的比特率以保持高质量,而其他部分可能可以使用较少的比特率。VBR模式下码率波动较大,在背景持续变化或较复杂的场景下码率较高,特别是不同的场景切换或静止到运动时会造成码率瞬时冲高,容易导致卡顿。VBR在相对静止或变化不剧烈的场景码率会明显变小,这种情况下延迟可能会小于CBR,一般情况下VBR的整体码率会小于CBR。

CQ(Constant Quality):恒定质量

  • 不设置特定的比特率,而是尝试在恒定的质量水平下编码视频。编码器会动态分配比特率以保持一致的视觉质量。

CBR_FD(Constant Bit Rate with Frame Dropping):恒定比特率帧丢失

  • CBR_FD 是一种变体的 CBR 模式,又称CBR_VFR模式,在码率有限的情况下使用。与标准的 CBR 不同,CBR_FD 允许在码率不足时丢弃视频帧,以便在固定的比特率情况下维持一定的清晰度。丢帧会造成主观感觉不流畅。

format.setInteger(MediaFormat.KEY_BITRATE_MODE, rateControlMode);
1.5 帧率

较高的帧率通常会提供更平滑的体验,减少图像卡顿感。通常,60帧每秒(FPS)被认为是很好的目标帧率。不同类型的游戏和应用对帧率的需求有所不同。例如,射击游戏通常需要更高的帧率以获得更快的响应时间,而策略游戏可能可以使用较低的帧率。高帧率通常需要更多的带宽来传输。

format.setInteger(MediaFormat.KEY_FRAME_RATE, maxFps);
format.setFloat(MediaFormat.KEY_MAX_FPS_TO_ENCODER, maxFps);//限制Surface输入的最大帧率
1.6 I帧间隔

I帧通常比P帧和B帧更大,较短的I帧间隔有助于提高视频质量,但会增加比特率和带宽要求,码率会低时频繁的I帧会造成与I帧同步的闪烁。

format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval);//单位:秒
1.7 帧内刷新

帧内刷新可以在视频编码中的指定周期内,刷新整个帧,而不是像传统关键帧那样插入完整的I帧,相当于把I帧均分到多个帧。帧内刷新在码率足够的情况下可以提升画质,但在码率非常不足的情况下会造成背景噪点闪烁。

format.setInteger(MediaFormat.KEY_INTRA_REFRESH_PERIOD, maxFps*30);
1.8 量化参数

量化参数(Quantization Parameter),用于控制视频编码的压缩质量和比特率,最终达到调节画面质量的作用。QP值和比特率成反比,QP值越小画面质量越高;反之QP值越大,画面质量越低。而且随着视频源复杂度,这种反比的关系会更加明显。QP调节是改变画面质量最常用的手段之一。QP 的取值范围通常在 0 到 51 之间,值为 0 表示最高质量(无损),而值为 51 表示最低质量。设置QP参数时可以根据I帧,P帧,B帧分别设置,关闭B帧的情况下不需要设置B帧。

最小量化步长(minQP),限制最好的图像质量(重点在静止画面),minQP越小,静止时候码率越大,质量越好。

最大量化步长(maxQP),限制最差的画面(重点在运动的时候),maxQP越小,运动时候码率就越大,质量相对越好。

Android标准接口设置QP(不一定支持):

format.setInteger(MediaFormat.KEY_VIDEO_QP_I_MIN, minQP);
format.setInteger(MediaFormat.KEY_VIDEO_QP_I_MAX, maxQP);
​
format.setInteger(MediaFormat.KEY_VIDEO_QP_P_MIN, minQP);
format.setInteger(MediaFormat.KEY_VIDEO_QP_P_MAX, maxQP);

高通865芯片设置QP参数方法:

format.setInteger("vendor.qti-ext-enc-initial-qp.qp-i", defaultQP);
format.setInteger("vendor.qti-ext-enc-initial-qp.qp-i-enable", 1);
format.setInteger("vendor.qti-ext-enc-qp-range.qp-i-min", minQP);
format.setInteger("vendor.qti-ext-enc-qp-range.qp-i-max", maxQP);
​
format.setInteger("vendor.qti-ext-enc-initial-qp.qp-p", defaultQP);
format.setInteger("vendor.qti-ext-enc-initial-qp.qp-p-enable", 1);
format.setInteger("vendor.qti-ext-enc-qp-range.qp-p-min", minQP);
format.setInteger("vendor.qti-ext-enc-qp-range.qp-p-max", maxQP);
1.9 去模糊

Android MediaCodec中没有这个参数,这个是高通865的视频编码器中的参数,禁用模糊效果时能减少场景切换时的模糊,可通过MediaCodec透传到OpenMax:

format.setInteger("vendor.qti-ext-enc-blurinfo.info", 2);//means disable both internal and external blur
1.10 Profile和Level

Profile必须与Level一同设置,它们是用来描述和标识视频编码标准的重要参数,H.264/AVC标准有多个配置文件,如Baseline、Main、High等,它们支持不同的功能集,High配置文件通常支持更高级的功能;Level越高,支持的分辨率,帧率,码率越高。

if(mimeType.equals(MediaFormat.MIMETYPE_VIDEO_AVC)) {
    format.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh);
}else if(mimeType.equals(MediaFormat.MIMETYPE_VIDEO_HEVC)){
    format.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.HEVCProfileMain);
}
format.setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel31);
1.11 补帧

当画面停止刷新超过指定的时间后,重复最后一帧,此方法不影响界面显示,但可以提高编码帧率指标,对实际体验没有意义,会造成码率略有增加。

format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, LIMIT_FRAME_DELAY_MS);

2 图像处理

编码前图像处理,可以通过优化被采集的视频源来提高画质,主要方法有锐化、色彩增强(亮度、对比度、饱和度调节);这些方法是通过GPU对每一帧视频图像进行处理,因此会消耗较多的GPU资源,使用时需要验证是否影响游戏帧率,建议可以在客户端解码后处理。

2.1 锐化

锐化算法有多种,目前验证效果比较好的算法如下(通过sharpLevel控制锐化强度,锐化过度会有锯齿感):

"varying vec2 vTextureCoord;\n"+
"uniform samplerExternalOES uTexture;\n"+
"uniform vec2 mTextureSize;\n"+
"uniform float sharpLevel;\n"+
"void main() {\n"+
"    vec2 offset0 = vec2(1.0, 1.0) / mTextureSize;\n"+
"    vec2 offset1 = vec2(0.0, 1.0) / mTextureSize;\n"+
"    vec2 offset2 = vec2(-1.0, 1.0) / mTextureSize;\n"+
"    vec2 offset3 = vec2(1.0, 0.0) / mTextureSize;\n"+
"    vec4 cTemp0 = texture2D(uTexture, vTextureCoord + offset0);\n"+
"    vec4 cTemp1 = texture2D(uTexture, vTextureCoord + offset1);\n"+
"    vec4 cTemp2 = texture2D(uTexture, vTextureCoord + offset2);\n"+
"    vec4 cTemp3 = texture2D(uTexture, vTextureCoord + offset3);\n"+
"    vec4 cTemp4 = texture2D(uTexture, vTextureCoord);\n"+
"    vec4 cTemp5 = texture2D(uTexture, vTextureCoord - offset3);\n"+
"    vec4 cTemp6 = texture2D(uTexture, vTextureCoord - offset2);\n"+
"    vec4 cTemp7 = texture2D(uTexture, vTextureCoord - offset1);\n"+
"    vec4 cTemp8 = texture2D(uTexture, vTextureCoord - offset0);\n"+
"    vec4 sum = cTemp4 + (cTemp4-(cTemp0+cTemp2+cTemp6+cTemp8+(cTemp1+cTemp3+cTemp5+cTemp7)*2.0+cTemp4*4.0)/16.0)*sharpLevel;\n"+
"    gl_FragColor = vec4(sum.rgb, 1.0);\n"+
"}\n";
2.2 色彩增强

通过对亮度、对比度、饱和度的调节可达到色彩增强的效果,不同风格的场景效果差异较大,需要分别适配,否则可能使用画面效果更差,算法如下(通过uBrightness、uContrast、uSaturation三个参数分别控制亮度、对比度、饱和度):

"varying vec2 vTextureCoord;\n"+
"uniform samplerExternalOES uTexture;\n"+
"uniform float uBrightness;\n" +
"uniform float uContrast;\n" +
"uniform float uSaturation;\n" +
"void main() {\n"+
"   vec4 texColor = texture2D(uTexture, vTextureCoord);\n" +
"   texColor.rgb += uBrightness;\n" +
"   texColor.rgb = (texColor.rgb - 0.5) * max(uContrast, 0.0) + 0.5;\n" +
"   float luminance = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));\n" +
"   texColor.rgb = mix(vec3(luminance), texColor.rgb, uSaturation);\n" +
"   gl_FragColor = texColor;\n" +
"}\n";

3 采集

通过调整屏幕分辨率、刷新帧率、屏密度等可以影响视频编码的最终效果。

3.1 分辨率

采集分辨率与编码分辨率相同时,清晰度最高,但编码过程中调节屏幕分辨率调小会闪一次明显的黑框,而实际应用中调节档位时需要调节编码分辨率,为了避免黑框,可以将采集分辨率设置为最大的编码分辨率,调节方法示例:

wm size 1920x1080
3.2 刷新率

通过修改系统调节vsync可以修改屏幕刷新率,这样刷新的时间间隔就会变小,MOUSE MOVE事件响应就会更及时,从而减小操作延迟。

3.3 屏密度

个别游戏调整屏密度会影响游戏的清晰度,调节方法示例:

wm density 380
3.4 超采

超采是先设置设备分辨率是一个比较大的分辨率,启动游戏后,游戏加载了大分辨的资源,加载完成之后再调回原分辨率,由于游戏没有重新加载资源,因此使用的是一个高分辨率的资源,会更清晰,这些方法对个别游戏有效,比如《原神》的文字和纹理都会变清晰,《逆水寒》的纹理会变清晰,实际就是3.1的叠加使用,调回原分辨率时会闪一下黑框。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!对于在 Android 上使用 Camera2 API 和 FFmpeg 进行视频编码,您可以按照以下步骤操作: 1. 首先,您需要在您的 Android 项目中集成 FFmpeg。您可以通过在项目的 build.gradle 文件中添加 FFmpeg 库的引用来完成此操作。例如: ```groovy implementation 'com.writingminds:FFmpegAndroid:0.3.2' ``` 2. 接下来,您需要设置并打开 Camera2 API。您可以参考 Android 官方文档来了解如何使用 Camera2 API 进行摄像头操作。 3. 在获取到 Camera2 的帧数据后,您可以将其编码为视频文件。为此,您可以使用 FFmpeg 提供的命令行接口。以下是一个示例代码片段,演示如何将 Camera2 的帧数据编码为 H.264 格式的视频文件: ```java // 设置 FFmpeg 命令行参数 String[] ffmpegCmd = new String[] { "-y", // 覆盖输出文件 "-f", "rawvideo", "-vcodec", "rawvideo", "-pix_fmt", "nv21", // 请根据摄像头输出格式进行更改 "-s", previewSize.getWidth() + "x" + previewSize.getHeight(), // 请根据预览尺寸进行更改 "-i", "-", // 使用输入管道获取帧数据 "-c:v", "libx264", "-preset", "ultrafast", // 编码速度,可根据需求进行更改 "-tune", "zerolatency", // 实时性优化,可根据需求进行更改 "-crf", "23", // 视频质量,可根据需求进行更改 "-f", "mp4", outputFile.getAbsolutePath() }; // 创建 FFmpeg 进程并启动编码 Process ffmpegProcess = FFmpeg.getInstance(context).execute(ffmpegCmd, new ExecuteBinaryResponseHandler() { @Override public void onSuccess(String message) { // 编码成功 } @Override public void onFailure(String message) { // 编码失败 } }); // 将 Camera2 的帧数据写入 FFmpeg 的输入管道 OutputStream ffmpegInputStream = ffmpegProcess.getOutputStream(); ffmpegInputStream.write(frameData); ffmpegInputStream.flush(); ffmpegInputStream.close(); ``` 请注意,上述代码仅为示例,您需要根据您的项目需求进行适当的修改。 希望这能对您有所帮助!如有任何进一步的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值