我正在编写一个应用程序,该应用程序将从远程TCP连接实时接收的图像序列呈现到ImageView
元素中。
stream 由以PGM格式编码并以9Hz发送的单帧组成。我坚信,使用背景Service
可以轻松处理这样的非常低的帧速率,该背景将完全解码的位图发送到我的MainActivity
。
这是我的VideoService
(我只发布了run()
方法,因为我认为这只是一些有趣的方法之一)
public void run() {
InetAddress serverAddr = null;
try {
serverAddr = InetAddress.getByName(VIDEO_SERVER_ADDR);
} catch (UnknownHostException e) {
Log.e(getClass().getName(), e.getMessage());
e.printStackTrace();
return;
}
Socket socket = null;
BufferedReader reader = null;
do {
try {
socket = new Socket(serverAddr, VIDEO_SERVER_PORT);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
boolean frameStart = false;
LinkedList<String> frameList = new LinkedList<>();
while (keepRunning) {
final String message = reader.readLine();
if (!frameStart && message.startsWith("F"))
frameStart = true;
else if (frameStart && message.startsWith("EF")) {
frameStart = false;
final Bitmap bitmap = Bitmap.createBitmap(IR_FRAME_WIDTH, IR_FRAME_HEIGHT, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
final String[] data = frameList.toArray(new String[frameList.size()]);
canvas.drawBitmap(bitmap, 0, 0, null);
//Log.d(this.getClass().getName(), "IR FRAME COLLECTED");
if ((data.length - 6) == IR_FRAME_HEIGHT) {
float grayScaleRatio = Float.parseFloat(data[2].trim()) / 255.0f;
for (int y = 0; y < IR_FRAME_HEIGHT; y++) {
final String line = data[y + 3];
final String[] points = line.split("\\s+");
if (points.length == IR_FRAME_WIDTH) {
for (int x = 0; x < IR_FRAME_WIDTH; x++) {
final float grayLevel = Float.parseFloat(points[x]) / grayScaleRatio;
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
final int level = (int)grayLevel;
paint.setColor(Color.rgb(level, level, level));
canvas.drawPoint(x, y, paint);
}
} else
Log.d(this.getClass().getName(), "Malformed line");
}
final Intent messageIntent = new Intent();
messageIntent.setAction(VIDEO_BROADCAST_KEY);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
bitmap.recycle();
messageIntent.putExtra(VIDEO_MESSAGE_KEY, stream.toByteArray());
stream.close();
sendBroadcast(messageIntent);
} else
Log.d(this.getClass().getName(), "Malformed data");
frameList.clear();
} else if (frameStart)
frameList.add(message);
}
Thread.sleep(VIDEO_SERVER_RESPAWN);
} catch (Throwable e) {
Log.e(getClass().getName(), e.getMessage());
e.printStackTrace();
}
} while (keepRunning);
if (socket != null) {
try {
socket.close();
} catch (Throwable e) {
Log.e(getClass().getName(), e.getMessage());
e.printStackTrace();
}
}
}
message
是一行来自以下文本的行:
F
P2
160 120
1226
193 141 158 152 193 186 171 177 186 160 195 182 ... (160 times)
.
. (120 lines)
.
278 248 253 261 257 284 310 304 304 272 227 208 ... (160 times)
EF
在MainActivity中,我通过以下代码来处理这个问题:
class VideoReceiver extends BroadcastReceiver {
final public Queue<Bitmap> imagesQueue = new LinkedList<>();
@Override
public void onReceive(Context context, Intent intent) {
try {
//Log.d(getClass().getName(), "onReceive() called");
final byte[] data = intent.getByteArrayExtra(VideoService.VIDEO_MESSAGE_KEY);
final Bitmap bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
imagesQueue.add(bitmap);
runOnUiThread(updateVideoTask);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
updateVideoTask
任务的定义如下:
updateVideoTask = new Runnable() {
public void run() {
if (videoReceiver == null) return;
if (!videoReceiver.imagesQueue.isEmpty())
{
final Bitmap image = videoReceiver.imagesQueue.poll();
if (image == null) return;
videoView.setImageBitmap(image);
Log.d(this.getClass().getName(), "Images to spool: " + videoReceiver.imagesQueue.size());
}
}
};
不幸的是,当我运行该应用程序时,我发现帧速率非常低并且延迟很大。我不能争论发生了什么。 我从 logcat 获得的唯一提示是这些行:
2019-05-20 16:37:08.817 29566-29580/it.tux.gcs I/art: Background sticky concurrent mark sweep GC freed 88152(3MB) AllocSpace objects, 3(52KB) LOS objects, 22% free, 7MB/10MB, paused 3.937ms total 111.782ms
2019-05-20 16:37:08.832 29566-29587/it.tux.gcs D/skia: Encode PNG Singlethread : 13003 us, width=160, height=120
即使有所有这些延迟的总和(140毫秒),应用程序也应保持5Hz以上的帧频,而变得0.25Hz甚至更糟。
经过一番调查,我发现自己动了:
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
嵌套循环之外的内容阻止了如此频繁地调用GC,我发现此行中的另一个主要延迟源:
final String[] points = line.split("\\s+");
它每次烧掉2毫秒,所以我决定去一些不太聪明但更快的东西:
final String[] points = line.split(" ");
无论如何还是不够的。.之间的代码:
canvas.drawBitmap(bitmap, 0, 0, null);
和
sendBroadcast(messageIntent);
仍然消耗200毫秒以上的时间...我该如何做得更好呢?
答案 0 :(得分:2)
首先,由于某种原因,您正在通过BroadcastReceiver
提交具有新图像更改的结果。您可以显着提高整体速度,但是可以删除此逻辑。并通过bound features将通信替换为Service
。
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
然后接收连接。
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
然后使用Service
绑定程序实例来订阅Activity
,并在Service
中使用callback来发布新的数据字节。