Я пишу приложение, которое отображает последовательность изображений, полученных в режиме реального времени из удаленного TCP-соединения, в элемент ImageView
.Поток состоит из отдельных кадров, закодированных в формате PGM и отправленных с частотой 9 Гц. Я подумал, что очень низкая частота кадров, как эта, должна легко обрабатываться с использованием фона 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 мс) приложение должно поддерживать частоту кадров более 5 Гц, в то время какЯ получаю 0,25 Гц или даже хуже.
После некоторого исследования я обнаружил, что перемещение:
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 мс ... как я могу сделать лучше, чем это?