org.jitsi.meet.test.PSNRTest.java Source code

Java tutorial

Introduction

Here is the source code for org.jitsi.meet.test.PSNRTest.java

Source

/*
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jitsi.meet.test;

import junit.framework.*;
import org.openqa.selenium.*;

import java.io.*;
import java.util.*;
import java.util.concurrent.*;

/**
 * A test that will run 1 minute (configurable) and will perform a PSNR test
 * on the received video stream.
 *
 * @author George Politis
 */
public class PSNRTest extends TestCase {
    /**
     * The PSNR script that produces PSNR results for every frame that we've
     * captured.
     */
    private static final String PSNR_SCRIPT = "resources/psnr-test.sh";

    /**
     * The directory where we save the captured frames.
     */
    private static final String OUTPUT_FRAME_DIR = "test-reports/psnr/captured-frames/";

    /**
     * The directory where we get the input frames.
     */
    public static final String INPUT_FRAME_DIR = "resources/psnr/stamped/";

    /**
     * The directory to use for frame resizing.
     */
    private static final String RESIZED_FRAME_DIR = "test-reports/psnr/resized-frames/";

    /**
     * The minimum PSNR value that we will accept before failing. PSNR above 20
     * is pretty indicative of good similarity. For example: Downscaling a 720p
     * image to 360p gives a PSNR of 27.2299. Downscaling a 720p image to 180p
     * gives a PSNR of 21.8882. Downscaling a 720p image to 90p gives a PSNR of
     * 20.1337
     */
    private static final float MIN_PSNR = 22f;

    /**
     * A test where we read some configurations or fallback to default values
     * and we expect a conference to be already established (by SetupConference)
     * and we keep checking whether this is still the case, and if something
     * is not working we fail.
     */
    public void testPSNR() {
        File inputFrameDir = new File(INPUT_FRAME_DIR);
        if (!inputFrameDir.exists()) {
            // Skip the PSNR tests because we don't have any PSNR
            // resources.
            return;
        }
        // Create the output directory for captured frames.
        File outputFrameDir = new File(OUTPUT_FRAME_DIR);
        if (!outputFrameDir.exists()) {
            outputFrameDir.mkdirs();
        }

        String timeToRunInMin = System.getProperty("psnr.duration");

        // default is 1 minute
        if (timeToRunInMin == null || timeToRunInMin.length() == 0)
            timeToRunInMin = "1";

        final int minutesToRun = Integer.valueOf(timeToRunInMin);

        final CountDownLatch waitSignal = new CountDownLatch(1);

        // execute every 1 sec.
        final Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            long lastRun = System.currentTimeMillis();

            int millsToRun = minutesToRun * 60 * 1000;

            CountDownLatch ownerDownloadSignal = new CountDownLatch(3);
            CountDownLatch secondPDownloadSignal = new CountDownLatch(3);

            @Override
            public void run() {
                try {
                    System.err.println("Checking at " + new Date() + " / to finish: " + millsToRun + " ms.");

                    if (!ConferenceFixture.isIceConnected(ConferenceFixture.getOwner())) {
                        assertAndQuit("Owner ice is not connected.");
                        return;
                    }

                    if (!ConferenceFixture.isInMuc(ConferenceFixture.getOwner())) {
                        assertAndQuit("Owner is not in the muc.");
                        return;
                    }

                    if (!ConferenceFixture.isIceConnected(ConferenceFixture.getSecondParticipant())) {
                        assertAndQuit("Second participant ice is not connected.");
                        return;
                    }

                    if (!ConferenceFixture.isInMuc(ConferenceFixture.getSecondParticipant())) {
                        assertAndQuit("The second participant is not in the muc.");
                        return;
                    }

                    long downloadOwner = ConferenceFixture.getDownloadBitrate(ConferenceFixture.getOwner());
                    long downloadParticipant = ConferenceFixture
                            .getDownloadBitrate(ConferenceFixture.getSecondParticipant());

                    if (downloadOwner <= 0) {
                        System.err.println("Owner no download bitrate");
                        ownerDownloadSignal.countDown();
                    } else
                        ownerDownloadSignal = new CountDownLatch(3);

                    if (ownerDownloadSignal.getCount() <= 0) {
                        assertAndQuit("Owner download bitrate less than 0");
                        return;
                    }

                    if (downloadParticipant <= 0) {
                        System.err.println("Second participant no download bitrate");
                        secondPDownloadSignal.countDown();
                    } else
                        secondPDownloadSignal = new CountDownLatch(3);

                    if (secondPDownloadSignal.getCount() <= 0) {
                        assertAndQuit("Second participant download rate less than 0");
                        return;
                    }

                    if (!ConferenceFixture.isXmppConnected(ConferenceFixture.getOwner())) {
                        assertAndQuit("Owner xmpp connection is not connected");
                        return;
                    }

                    if (!ConferenceFixture.isXmppConnected(ConferenceFixture.getSecondParticipant())) {
                        assertAndQuit("The second participant xmpp " + "connection is not connected");
                        return;
                    }

                    WebDriver driver = ConferenceFixture.getOwner();
                    if (driver instanceof JavascriptExecutor) {
                        JavascriptExecutor js = ((JavascriptExecutor) driver);

                        List<WebElement> remoteThumb = driver
                                .findElements(By.xpath("//video[starts-with(@id, 'remoteVideo_')]"));

                        for (WebElement webElement : remoteThumb) {
                            //FIXME This needs to be optimized. We run this
                            // every second. It encodes an image in base64 and
                            // it transfers it over the network (that's how
                            // selenium communicates with the debugger). So this
                            // might work with a few images per second.. But this
                            // will fail miserably if we want to capture 30fps.
                            // The proper solution would be to store the images
                            // in the sandboxed HTML filesystem that modern
                            // browsers provide. And transfer them at the end
                            // of the test. We could follow the same approach
                            // if we want to grab the whole webm/vp8 stream using
                            // the Recorder API.
                            String elmId = webElement.getAttribute("id");
                            Object pngUrl = js.executeScript("var video = document.getElementById(\"" + elmId
                                    + "\");" + "var canvasId = 'canvas-capture';"
                                    + "var canvas = document.getElementById(canvasId);" + "if (canvas == null) {"
                                    + "    canvas = document.createElement('canvas');" + "    canvas.id = canvasId;"
                                    + "    document.body.appendChild(canvas);" + "}"
                                    + "canvas.width = video.videoWidth;" + "canvas.height = video.videoHeight;"
                                    + "var ctx = canvas.getContext('2d');" + "ctx.drawImage(video, 0, 0);"
                                    + "return canvas.toDataURL(\"image/png\");");

                            // Parse the URI to get only the base64 part
                            String strBase64 = pngUrl.toString().substring("data:image/png;base64,".length());

                            // Convert it to binary
                            // Java 8 has a Base64 class.
                            byte[] data = org.apache.commons.codec.binary.Base64.decodeBase64(strBase64);

                            try (OutputStream stream = new FileOutputStream(
                                    OUTPUT_FRAME_DIR + elmId + "-" + lastRun + ".png")) {
                                stream.write(data);
                            }
                        }
                    }

                    long currentTime = System.currentTimeMillis();
                    millsToRun -= (currentTime - lastRun);
                    lastRun = currentTime;

                    if (millsToRun <= 0) {
                        timer.cancel();
                    }
                } catch (Exception e) {
                    e.printStackTrace();

                    assertAndQuit("Unexpected error occurred.");
                }
            }

            /**
             * Clears what is needed and lowers the assert countdown.
             * @param msg
             */
            private void assertAndQuit(String msg) {
                System.err.println(msg);
                waitSignal.countDown();
                timer.cancel();
            }

        }, /* delay */ 1000, /* period */ 1000);

        try {
            waitSignal.await(minutesToRun, TimeUnit.MINUTES);

            if (waitSignal.getCount() == 0)
                assertTrue("A problem with the conf occurred", false);
            else {
                Runtime rt = Runtime.getRuntime();
                String[] commands = { PSNR_SCRIPT, OUTPUT_FRAME_DIR, INPUT_FRAME_DIR, RESIZED_FRAME_DIR };
                Process proc = rt.exec(commands);

                BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));

                BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));

                // read the output from the command
                String s = null;
                while ((s = stdInput.readLine()) != null) {
                    assertTrue(s == null || Float.parseFloat(s.split(" ")[1]) > MIN_PSNR);
                }

                // read any errors from the attempted command
                while ((s = stdError.readLine()) != null) {
                    System.err.println(s);
                }
            }
        } catch (Exception e) {
            assertTrue("An error occurred", false);
        }
    }
}