技术背景
我们在做Android平台GB28181设备接入模块、轻量级RTSP服务模块和RTMP推流模块的时候,遇到这样的技术诉求,开发者希望把实时CPU占用、电池信息等叠加在视频界面。
获取CPU占用率
Android平台获取CPU占用情况,可以读取/proc/stat
文件,解析出各个 CPU 时间参数,然后计算出 CPU 的使用率,示例代码如下:
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.util.Log;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class MainActivity extends Activity {
private Handler handler = new Handler(Looper.getMainLooper());
private Runnable runnable;
private long prevTotalCpuTime = 0;
private long prevIdleCpuTime = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
runnable = new Runnable() {
@Override
public void run() {
try {
// 读取 /proc/stat 文件获取 CPU 时间信息
BufferedReader reader = new BufferedReader(new FileReader("/proc/stat"));
String line = reader.readLine();
reader.close();
String[] tokens = line.split("\\s+");
long user = Long.parseLong(tokens[2]);
long nice = Long.parseLong(tokens[3]);
long system = Long.parseLong(tokens[4]);
long idle = Long.parseLong(tokens[5]);
long iowait = Long.parseLong(tokens[6]);
long irq = Long.parseLong(tokens[7]);
long softirq = Long.parseLong(tokens[8]);
long totalCpuTime = user + nice + system + idle + iowait + irq + softirq;
long idleCpuTime = idle;
// 计算 CPU 使用率
if (prevTotalCpuTime!= 0 && prevIdleCpuTime!= 0) {
long diffTotalCpuTime = totalCpuTime - prevTotalCpuTime;
long diffIdleCpuTime = idleCpuTime - prevIdleCpuTime;
float cpuUsage = ((diffTotalCpuTime - diffIdleCpuTime) / (float) diffTotalCpuTime) * 100;
Log.d("CPU_USAGE", "CPU Usage: " + cpuUsage + "%");
}
prevTotalCpuTime = totalCpuTime;
prevIdleCpuTime = idleCpuTime;
} catch (IOException e) {
e.printStackTrace();
}
// 延迟一段时间后再次执行
handler.postDelayed(this, 1000);
}
};
// 启动获取 CPU 使用率的任务
handler.post(runnable);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 停止任务
handler.removeCallbacks(runnable);
}
}
使用BatteryManager类获取电池电量
在 Android 中,可以使用BatteryManager
类来获取电池电量信息。以下是具体步骤:
一、注册广播接收器
在你的 Android 组件(如 Activity 或 Service)中注册一个广播接收器来监听电池状态变化的广播。可以在onCreate
方法中进行注册。
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(batteryReceiver, filter);
这里创建了一个IntentFilter
来指定要监听的广播为电池状态变化广播(Intent.ACTION_BATTERY_CHANGED
),然后使用registerReceiver
方法注册广播接收器。
二、创建广播接收器
创建一个广播接收器类来处理电池状态变化的广播,如下所示:
private BroadcastReceiver batteryReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
int batteryPercentage = (level / (float)scale) * 100;
// 这里可以根据获取到的电池电量信息进行相应的处理
}
};
在广播接收器的onReceive
方法中,可以从广播意图中获取电池电量的级别(BatteryManager.EXTRA_LEVEL
)和总刻度(BatteryManager.EXTRA_SCALE
),然后计算出电池电量的百分比。
三、取消注册广播接收器
在适当的时候,如组件的onDestroy
方法中,取消注册广播接收器,以避免资源泄漏:
unregisterReceiver(batteryReceiver);
这样,当电池状态发生变化时,你的广播接收器将会收到通知,并可以获取到电池电量信息进行相应的处理。
需要注意的是,获取电池电量信息可能需要相应的权限。在 AndroidManifest.xml 文件中添加以下权限声明:
<uses-permission android:name="android.permission.BATTERY_STATS" />
场景应用
以大牛直播SDK的GB28181设备接入模块为例,摄像头实时视频采集,可以叠加上实时CPU占用和实时电量信息,亦或实时时间信息,会对场景带来很大益处,比如CPU占用率或电量信息,简单来说可以通过bitmap把文字读取下来,投递到底层比如jni层即可,非常方便。下面就文字和图片水印做个简短的技术普及。
文字水印
文字水印不再赘述,主要注意的是文字的大小、颜色、位置。
private int postText1Layer(int index, int left, int top) {
Bitmap text_bitmap = makeTextBitmap("文本水印一", getFontSize()+8,
Color.argb(255, 200, 250, 0),
false, 0,false);
if (null == text_bitmap)
return 0;
ByteBuffer buffer = ByteBuffer.allocateDirect(text_bitmap.getByteCount());
text_bitmap.copyPixelsToBuffer(buffer);
libPublisher.PostLayerImageRGBA8888ByteBuffer(handle_, index, left, top, buffer, 0,
text_bitmap.getRowBytes(), text_bitmap.getWidth(), text_bitmap.getHeight(),
0, 0, 0, 0, 0,0);
int ret = text_bitmap.getHeight();
text_bitmap.recycle();
return ret;
}
png水印
png水印,除了常规的位置需要注意之外,还涉及到logo水印的大小问题,为此,我们添加了缩放效果,可以缩放后,再贴到图层,确保以更合适的比例展示在图层期望位置。
private int postPictureLayer(int index, int left, int top) {
Bitmap bitmap = getAssetsBitmap();
if (null == bitmap) {
Log.e(TAG, "postPitcureLayer getAssetsBitmap is null");
return 0;
}
if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) {
Log.e(TAG, "postPitcureLayer config is not ARGB_8888, config:" + Bitmap.Config.ARGB_8888);
return 0;
}
ByteBuffer buffer = ByteBuffer.allocateDirect(bitmap.getByteCount());
bitmap.copyPixelsToBuffer(buffer);
final int w = bitmap.getWidth();
final int h = bitmap.getHeight();
if ( w < 2 || h < 2 )
return 0;
int scale_w = 0, scale_h = 0, scale_filter_mode = 0;
final float r_w = width_ - left; // 有可能负数
final float r_h = height_ - top; // 有可能负数
if (w > r_w || h > r_h) {
float s_w = w;
float s_h = h;
// 0.85的10次方是0.19687, 缩放到0.2倍差不多了
for ( int i = 0; i < 10; ++i) {
s_w *= 0.85f;
s_h *= 0.85f;
if (s_w < r_w && s_h < r_h )
break;
}
if (s_w > r_w || s_h > r_h)
return 0;
// 如果小于16就算了,太小看也看不见
if (s_w < 16.0f || s_h < 16.0f)
return 0;
scale_w = align((int)(s_w + 0.5f), 2);
scale_h = align( (int)(s_h + 0.5f), 2);
scale_filter_mode = 3;
}
/*
if ( scale_w > 0 && scale_h > 0)
Log.i(TAG, "postTextLayer scale_w:" + scale_w + ", scale_h:" + scale_h + " w:" + w + ", h:" + h) ; */
libPublisher.PostLayerImageRGBA8888ByteBuffer(handle_, index, left, top, buffer, 0, bitmap.getRowBytes(), w, h,
0, 0, scale_w, scale_h, scale_filter_mode,0);
int ret = scale_h > 0 ? scale_h : bitmap.getHeight();
bitmap.recycle();
return ret;
}