Захват экрана в отдельном потоке делает приложение Java медленным/не отвечающим

Я работаю над приложением, которое записывает экран пользователя, веб-камеру и микрофон, когда он/она выполняет определенные действия. Он будет использоваться в исследовательских целях. Приложение было успешно протестировано в Windows, но в Mac OS X (Maverick с Java 7.0.45) приложение становится медленным и не отвечает при запуске записи.

Вот почему мне трудно это понять:

  • Запись выполняется в отдельном потоке, так как же это может повлиять на отзывчивость другого потока? Тем более, что после каждого прогона вызывается либо Thread.yield(), либо Thread.sleep(...).
  • Журналы показывают, что при попытке записи с частотой 15 FPS результирующая частота кадров составила 2 FPS. Таким образом, кажется, что код, который захватывает один кадр, может быть слишком медленным. Но почему тогда он отлично работает в Windows?

Небольшое замечание: приложение было успешно протестировано множеством пользователей Windows, но мне удалось протестировать его только на одном Mac. Однако тот был только что отформатирован и получил чистую установку OS X Maverick, Java (и Netbeans).

Ниже вы найдете код, который записывает экран и записывает его в видео с помощью Xuggler. Код для записи веб-камеры аналогичен, и я сомневаюсь, что запись звука имеет к этому какое-то отношение. Мой вопрос:

Что может быть причиной того, что приложение перестает отвечать на запросы? и

Как сделать код более эффективным и тем самым повысить FPS?

IMediaWriter writer = ToolFactory.makeWriter(file.getAbsolutePath());
Dimension size = Globals.sessionFrame.getBounds().getSize();
Rectangle screenRect;
BufferedImage capture;
BufferedImage mousePointImg;


writer.addVideoStream(0, 0, ICodec.ID.CODEC_ID_H264, size.width, size.height);

int i = 0;

while (stop == false) {

    // Get mouse cursor to draw over screen image.
    PointerInfo mousePointer = MouseInfo.getPointerInfo();
    Point mousePoint = mousePointer.getLocation();
    Point screenPoint = new Point((int) (mousePoint.getX() - 
        Globals.sessionFrame.getBounds().getX()), (int) (mousePoint.getY() - 
        Globals.sessionFrame.getBounds().getY()));

    // Get the screen image.
    try {
        screenRect = new Rectangle(Globals.sessionFrame.getBounds());
        capture = new Robot().createScreenCapture(screenRect);
    } catch ( ... ) { ... }

    // Convert and resize the screen image.
    BufferedImage image = ConverterFactory.convertToType(capture, 
        BufferedImage.TYPE_3BYTE_BGR);
    IConverter converter = ConverterFactory.createConverter(image, 
        IPixelFormat.Type.YUV420P);

    // Draw the mouse cursor if necessary.
    if (mouseWithinScreen()) {
        Graphics g = image.getGraphics();
        g.drawImage(mousePointImg, (int) screenPoint.getX(), 
            (int) screenPoint.getY(), null);
    }

    // Prepare the frame.
    IVideoPicture frame = converter.toPicture(image, (System.currentTimeMillis() - 
        startTimeMillis()) * 1000);
    frame.setKeyFrame(i % (getDesiredFPS() * getDesiredKeyframeSec()) == 0);

    // Write to the video
    writer.encodeVideo(0, frame);

    // Delay the next capture if we are at the desired FPS.
    try {
        if (atDesiredFPS()) {
            Thread.yield();
        } else {
            Thread.sleep(1000 / getDesiredFPS());
        }
    } catch ( ... ) { ... }

    i++;
}

writer.close();

person Luke    schedule 13.11.2013    source источник
comment
У вас достаточно ядер для потоков, верно? Занят ли ЦП, когда это происходит?   -  person maaartinus    schedule 13.11.2013
comment
У процессора @maaartinus, похоже, полно работы с приложением, да. Существует около 8 потоков, поэтому обычно потоков больше, чем ядер.   -  person Luke    schedule 13.11.2013
comment
Используя идеи из ответа @TwoThe, вы можете легко отключить часть обработки и посмотреть, что произойдет.   -  person maaartinus    schedule 13.11.2013


Ответы (1)


В вашем коде я вижу несколько архитектурных проблем:

Во-первых, если вы хотите выполнять что-то с фиксированной скоростью, используйте ScheduledThreadPoolExecutor.scheduleAtFixedRate(...). Это сделает всю вашу часть кода задержки устаревшей, а также гарантирует, что определенные проблемы синхронизации ОС не будут мешать вашему планированию.

Затем, чтобы ускорить процесс, вам нужно немного разобрать код. Насколько я вижу, у вас есть 3 задачи: захват, рисование/конвертация мышью и запись потока. Если вы поместите часть захвата в запланированный Runnable, преобразование в многопараллельное выполнение как Callables в Executor, а затем в третьем потоке берете результаты из списка результатов и записываете их в поток, вы можете полностью использовать мульти- ядра.

Псевдокод:

Глобальные объявления (или передать их различным классам):

final static Executor converterExecutor = Executors.newFixedThreadPoolExecutor(Runtime.getRuntime().availableProcessors());
final static LinkedBlockingQueue<Future<IVideoPicture>> imageQueue = new LinkedBlockingQueue<>();
// ...

Capture Runnable (по расписанию с фиксированной ставкой):

capture = captureScreen();
final Converter converter = new Converter(capture);
final Future<IVideoPicture> conversionResult = converterExecutor.submit(converter);
imageQueue.offer(conversionResult); // returns false if queue is full

Преобразование, вызываемое:

class Converter implements Callable<IVideoPicture> {
  // ... variables and constructor

  public IVideoPicture call() {
    return convert(this.image);
  }
}

Писатель

IVideoPicture frame;
while (this.done == false) {
  frame = imageQueue.get();
  writer.encodeVideo(0, frame);
}

Вы можете убедиться, что imageQueue не переполняется изображениями для рендеринга, если ЦП слишком медленный, ограничив размер этой очереди, см. конструктор LinkedBlockingQueue.

person TwoThe    schedule 13.11.2013
comment
Это некоторые правильные указатели. Большое спасибо. Я погружусь прямо в это. - person Luke; 13.11.2013
comment
Если частота кадров установлена ​​выше, чем может выдержать ЦП, не сделает ли scheduleAtFixedRate приложение медленным и неотзывчивым, пытаясь не отставать от невозможного темпа? - person Luke; 13.11.2013