Android高帧率手机引发的省电需求

随着高帧率手机的普及,在很多APP内需要限制手机的帧率来进行省电优化。今天的例子就是视频(直播/点播)播放的软件。

推流优化

首先我们说,在视频采集时,有两种采集方式:屏幕采集和camera采集。

camera采集

这里我们可以直接限制camera的采集频率。有几个优化方案供参考

android.hardware.camera2.params.StreamConfigurationMap

现在的很多手机采集一般为50HZ-60HZ,部分低端机前置为30HZ( 也有部分安卓机器的Camera接口无法实现30FPS及以上的数据采集频率,导致FPS始终为20,这里可以灵活判断当前帧率采取优化 ),所以这里需要手动设置StreamConfigurationMap的采集帧率为30HZ,注意:这里设置采集范围的时,fpsMax需要大于fpsMin,且均需要是30的倍数,当fpsMin=fpsMax,取其值。

不仅需要优化帧率,在采集视频时推荐采集低分辨率的视频。CameraX 会自动选择内部 Camera2 界面分辨率,默认目标分辨率设置为 640x480,Camera最大支持1080P的视频输出。( 如果你希望采集的视频值大于640x480时,那么你需要参考使用 setTargetResolution 或者 settargetaspectratio 这两个方法 )。这里推荐你使用360*640或更低的分辨率,设置方法为

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(640, 360))
    .build()

另外,你可以在你的推流SDK中打开硬件支持,如果你是手动推流,你可以自己打开硬件支持(硬件加速APP中是默认开启的,但是推流的SDK貌似都没有开启)

//下面三种方式任选其一即可。
//在清单文件的application或者activityzhong
<application android:hardwareAccelerated="true" ...>
//你的activity中
window.setFlags(
       WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
       WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)
//在你的view中
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)

或者你可以使用MediaCodec,具体参考这篇文章,写的很不错:Android 用MediaCodec实现视频硬解码

MediaCodec官方文档:https://developer.android.com/reference/android/media/MediaCodec

屏幕采集:

这里我就简单说一下通过MediaProjectionManager采集视频的方案,简单多了:

        //设置音频来源
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        //设置视频来源
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        //输出的录屏文件格式
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        //录屏文件路径
        mMediaRecorder.setOutputFile( mRecordFilePath );
        //视频尺寸
        mMediaRecorder.setVideoSize(mRecordWidth, mRecordHeight);
        //音视频编码器
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        //比特率
        mMediaRecorder.setVideoEncodingBitRate((int) (mRecordWidth * mRecordHeight * 3.6));
        //视频帧率
        mMediaRecorder.setVideoFrameRate(20);

这是MediaProjectionManager的一些属性,在开始录制之前你设置其中的一些属性即可。例如分辨率,比特率,以及帧率。

一般如果是人直播,可以设置为25FPS,游戏直播可以设置为35-45,具体可以自行判断。比特率是影响速度的一大因素,也是直接影响画质的一大因素。如果是画面变化快的游戏或者人物直播,可以适当调低。

播放优化

视频播放就简单多了。如果是你自定义的播放view,我们只需要限制view的帧率即可。在上面的方法中,我们看到mMediaRecorder.setVideoFrameRate(20);这样限制帧率的方法存在,那么在我们自定义的view中有没有这样的方法?答案是有

推荐setFrameRate()方法

首先,这是一个API29以上的方法…,如果你的兼容用户低于30,那么请暂时忽略这部分。

  1. Surface.setFrameRate()
  2. SurfaceControl.Transaction.setFrameRate()
  3. ANativeWindow_setFrameRate()
  4. ASurfaceTransaction_setFrameRate()

使用方法:

1 public void setFrameRate (float frameRate, int compatibility)

2 public SurfaceControl.Transaction setFrameRate (SurfaceControl sc, float frameRate, int compatibility)

///------------------NDK------------------///

3 int32_t ANativeWindow_setFrameRate(
  ANativeWindow *window,
  float frameRate,
  int8_t compatibility
)

4 void ASurfaceTransaction_setFrameRate(
  ASurfaceTransaction *transaction,
  ASurfaceControl *surface_control,
  float frameRate,
  int8_t compatibility
)

c++中(有问题,在改)


SDL_Event event;

static int time = 0;//时间记录
static float fps=20;//你需要的fps

//time = SDL_GetTicks() 或者你给一个初始值,在开始之前设置最好

while(running){

        while (SDL_PollEvent(&event)) 
        {
            SDL_WaitEvent(&event);
            OnEvent(&event);
            time = SDL_GetTicks();
        }

        if(SDL_GetTicks()-time>(1000/fps))
        {
            time = SDL_GetTicks();
            OnUpdate();
            OnRender();
        }

}

native中

话不多说,直接上代码:

//自定义surfaceview,然后在绘制方法中
long time = System.currentTimeMillis();
drow或者其他的绘制方法
//这里的30是1000/30约等于33帧的意思
while((System.currentTimeMillis() - time) <= 30) {    
    Thread.yield();  //yield我个人感觉是最佳选择,但是没法控制,类似你一直Thread.Sleep(0)一样,精细的同学可以考虑Thread.sleep(30-(System.currentTimeMillis() - time));不过因为这里是动态方法,你需要考虑到可能造成超时的问题,我的方案是直接在catch中也写上刷新方法…
}

以上就是大致的意思。总的来说,无非就集中方案,就是分用处去限制罢了。