mirror of https://github.com/OpenVidu/openvidu.git
openvidu-test-e2e: run ffmpeg and ffprobe in a pinned Docker image, never the host's
Assisted-by: claude-code - Anthropic - Claude Opus 5.5pull/915/head
parent
0e8628189d
commit
14fedb3962
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* (C) Copyright 2017-2026 OpenVidu (https://openvidu.io)
|
||||||
|
*
|
||||||
|
* 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 io.openvidu.test.browsers.utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs ffmpeg and ffprobe as a Docker container
|
||||||
|
*/
|
||||||
|
public final class Ffmpeg {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static ffmpeg build with libdav1d, libvpx, libx264, libaom and libopus, its
|
||||||
|
* multi-arch digest pinned: an image moved under the same tag could decode
|
||||||
|
* differently. Bump the tag and the digest together. -DFFMPEG_IMAGE overrides
|
||||||
|
* it with an image of the same layout (/ffmpeg and /ffprobe), e.g.
|
||||||
|
* mwader/static-ffmpeg:latest.
|
||||||
|
*/
|
||||||
|
public static final String IMAGE = System.getProperty("FFMPEG_IMAGE",
|
||||||
|
"mwader/static-ffmpeg:9.0.2@sha256:7d9bdaaf887f7e6ce6151f67325c344074b5ff1fb75316011c3376503e449a7b");
|
||||||
|
|
||||||
|
private static String user;
|
||||||
|
|
||||||
|
private Ffmpeg() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command line that runs {@code tool} ("ffmpeg" or "ffprobe") with
|
||||||
|
* {@code args} in a container of {@link #IMAGE}.
|
||||||
|
*/
|
||||||
|
public static String[] command(String tool, String... args) {
|
||||||
|
List<String> command = new ArrayList<>(List.of("docker", "run", "--rm", "--quiet", "--user", user()));
|
||||||
|
for (String dir : mounts(Arrays.asList(args))) {
|
||||||
|
command.addAll(List.of("-v", dir + ":" + dir));
|
||||||
|
}
|
||||||
|
command.addAll(List.of("--entrypoint", "/" + tool, IMAGE));
|
||||||
|
command.addAll(Arrays.asList(args));
|
||||||
|
return command.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@link #command(String, String...)}, with the tool first in the list. */
|
||||||
|
public static String[] command(List<String> toolAndArgs) {
|
||||||
|
return command(toolAndArgs.get(0), toolAndArgs.subList(1, toolAndArgs.size()).toArray(new String[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as one /bin/sh command line, for {@link CommandLineExecutor}:
|
||||||
|
* {@code args} is what would follow the tool in a shell.
|
||||||
|
*/
|
||||||
|
public static String shellCommand(String tool, String args) {
|
||||||
|
StringBuilder command = new StringBuilder("docker run --rm --quiet --user ").append(user());
|
||||||
|
for (String dir : mounts(Arrays.asList(args.trim().split("\\s+")))) {
|
||||||
|
command.append(" -v '").append(dir).append(":").append(dir).append("'");
|
||||||
|
}
|
||||||
|
return command.append(" --entrypoint /").append(tool).append(" ").append(IMAGE).append(" ").append(args)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pulls {@link #IMAGE} up front: a missing image would otherwise be pulled by
|
||||||
|
* the first command, inside its timeout.
|
||||||
|
*/
|
||||||
|
public static void pullImage() throws IOException, InterruptedException {
|
||||||
|
Process pull = new ProcessBuilder("docker", "pull", "--quiet", IMAGE).redirectErrorStream(true).start();
|
||||||
|
String output = new String(pull.getInputStream().readAllBytes());
|
||||||
|
if (!pull.waitFor(600, TimeUnit.SECONDS) || pull.exitValue() != 0) {
|
||||||
|
throw new IOException("docker pull " + IMAGE + " failed: " + output.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> mounts(List<String> args) {
|
||||||
|
Set<String> dirs = new LinkedHashSet<>();
|
||||||
|
for (String arg : args) {
|
||||||
|
if (!arg.startsWith("/")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Path path = Path.of(arg).normalize();
|
||||||
|
Path dir = Files.isDirectory(path) ? path : path.getParent();
|
||||||
|
if (dir != null && dir.getParent() != null && Files.isDirectory(dir)) {
|
||||||
|
dirs.add(dir.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static synchronized String user() {
|
||||||
|
if (user == null) {
|
||||||
|
try {
|
||||||
|
Path probe = Files.createTempFile("ffmpeg-user", ".tmp");
|
||||||
|
try {
|
||||||
|
user = Files.getAttribute(probe, "unix:uid") + ":" + Files.getAttribute(probe, "unix:gid");
|
||||||
|
} finally {
|
||||||
|
Files.deleteIfExists(probe);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Could not read the current user's uid and gid", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -151,7 +151,7 @@ public class MultimediaFileMetadata {
|
||||||
|
|
||||||
private JsonObject executeFfprobeCommand(String filePath) {
|
private JsonObject executeFfprobeCommand(String filePath) {
|
||||||
log.info("Running ffprobe command on '{}'", filePath);
|
log.info("Running ffprobe command on '{}'", filePath);
|
||||||
String cmd = "ffprobe -v quiet -print_format json -show_format -show_streams " + filePath;
|
String cmd = Ffmpeg.shellCommand("ffprobe", "-v quiet -print_format json -show_format -show_streams " + filePath);
|
||||||
return JsonParser.parseString(this.executer.executeCommand(cmd, 60)).getAsJsonObject();
|
return JsonParser.parseString(this.executer.executeCommand(cmd, 60)).getAsJsonObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,7 +161,7 @@ public class MultimediaFileMetadata {
|
||||||
String pathCopy = null;
|
String pathCopy = null;
|
||||||
pathCopy = java.nio.file.Files.move(source, source.resolveSibling("COPY." + extension)).toString();
|
pathCopy = java.nio.file.Files.move(source, source.resolveSibling("COPY." + extension)).toString();
|
||||||
log.warn("Fixing file '{}' with ffmpeg", filePath);
|
log.warn("Fixing file '{}' with ffmpeg", filePath);
|
||||||
String cmd = "ffmpeg -i " + pathCopy + " -vcodec copy -acodec copy " + filePath;
|
String cmd = Ffmpeg.shellCommand("ffmpeg", "-i " + pathCopy + " -vcodec copy -acodec copy " + filePath);
|
||||||
this.executer.executeCommand(cmd, 60);
|
this.executer.executeCommand(cmd, 60);
|
||||||
new File(pathCopy).delete();
|
new File(pathCopy).delete();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ import io.openvidu.test.browsers.EdgeUser;
|
||||||
import io.openvidu.test.browsers.FirefoxUser;
|
import io.openvidu.test.browsers.FirefoxUser;
|
||||||
import io.openvidu.test.browsers.utils.BrowserNames;
|
import io.openvidu.test.browsers.utils.BrowserNames;
|
||||||
import io.openvidu.test.browsers.utils.CommandLineExecutor;
|
import io.openvidu.test.browsers.utils.CommandLineExecutor;
|
||||||
|
import io.openvidu.test.browsers.utils.Ffmpeg;
|
||||||
import livekit.LivekitIngress.IngressInfo;
|
import livekit.LivekitIngress.IngressInfo;
|
||||||
import livekit.LivekitModels.Room;
|
import livekit.LivekitModels.Room;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
|
|
@ -162,15 +163,13 @@ public class OpenViduTestE2e {
|
||||||
protected static String LIVEKIT_HTTP_URL;
|
protected static String LIVEKIT_HTTP_URL;
|
||||||
protected static OkHttpClient LK_HTTP_CLIENT;
|
protected static OkHttpClient LK_HTTP_CLIENT;
|
||||||
|
|
||||||
protected static void checkFfmpegInstallation() {
|
protected static void pullFfmpegImage() {
|
||||||
String ffmpegOutput = commandLine.executeCommand("which ffmpeg", 60);
|
try {
|
||||||
if (ffmpegOutput == null || ffmpegOutput.isEmpty()) {
|
Ffmpeg.pullImage();
|
||||||
log.error("ffmpeg package is not installed in the host machine");
|
} catch (Exception e) {
|
||||||
Assertions.fail();
|
Assertions.fail("Could not pull the ffmpeg image " + Ffmpeg.IMAGE + ": " + e.getMessage());
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
log.info("ffmpeg is installed and accesible");
|
|
||||||
}
|
}
|
||||||
|
log.info("ffmpeg and ffprobe run in Docker image {}", Ffmpeg.IMAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private GenericContainer<?> chromeContainer(String image, long shmSize, int maxBrowserSessions, boolean headless) {
|
private GenericContainer<?> chromeContainer(String image, long shmSize, int maxBrowserSessions, boolean headless) {
|
||||||
|
|
@ -598,8 +597,8 @@ public class OpenViduTestE2e {
|
||||||
for (Map.Entry<String, String> layer : layerSizes.entrySet()) {
|
for (Map.Entry<String, String> layer : layerSizes.entrySet()) {
|
||||||
Path videoFile = mediaDir
|
Path videoFile = mediaDir
|
||||||
.resolve("test-video-" + layer.getValue() + "." + ("h264".equals(codec) ? "h264" : "ivf"));
|
.resolve("test-video-" + layer.getValue() + "." + ("h264".equals(codec) ? "h264" : "ivf"));
|
||||||
commandLine.executeCommand("ffmpeg -y -f lavfi -i testsrc=size=" + layer.getValue()
|
commandLine.executeCommand(Ffmpeg.shellCommand("ffmpeg", "-y -f lavfi -i testsrc=size="
|
||||||
+ ":rate=30 -t 5 -pix_fmt yuv420p " + encoder + " " + videoFile, 120);
|
+ layer.getValue() + ":rate=30 -t 5 -pix_fmt yuv420p " + encoder + " " + videoFile), 120);
|
||||||
if (!Files.exists(videoFile) || Files.size(videoFile) == 0) {
|
if (!Files.exists(videoFile) || Files.size(videoFile) == 0) {
|
||||||
Assertions.fail("ffmpeg could not generate the " + codec + " test file " + videoFile);
|
Assertions.fail("ffmpeg could not generate the " + codec + " test file " + videoFile);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import io.minio.BucketExistsArgs;
|
||||||
import io.minio.DownloadObjectArgs;
|
import io.minio.DownloadObjectArgs;
|
||||||
import io.minio.MinioClient;
|
import io.minio.MinioClient;
|
||||||
import io.openvidu.test.browsers.BrowserUser;
|
import io.openvidu.test.browsers.BrowserUser;
|
||||||
|
import io.openvidu.test.browsers.utils.Ffmpeg;
|
||||||
|
|
||||||
import static org.openqa.selenium.OutputType.BASE64;
|
import static org.openqa.selenium.OutputType.BASE64;
|
||||||
|
|
||||||
|
|
@ -1180,8 +1181,8 @@ public class AbstractOpenViduTestappE2eTest extends OpenViduTestE2e {
|
||||||
String container = "h264".equals(codec) ? "mp4" : "webm";
|
String container = "h264".equals(codec) ? "mp4" : "webm";
|
||||||
Assertions.assertTrue(file.toString().endsWith("." + container), "Wrong file extension: " + file);
|
Assertions.assertTrue(file.toString().endsWith("." + container), "Wrong file extension: " + file);
|
||||||
|
|
||||||
JsonObject probe = JsonParser.parseString(runMediaCommand("ffprobe", "-v", "error", "-show_format",
|
JsonObject probe = JsonParser.parseString(runMediaCommand(Ffmpeg.command("ffprobe", "-v", "error", "-show_format",
|
||||||
"-show_streams", "-count_packets", "-of", "json", file.toString())[0]).getAsJsonObject();
|
"-show_streams", "-count_packets", "-of", "json", file.toString()))[0]).getAsJsonObject();
|
||||||
JsonObject format = probe.getAsJsonObject("format");
|
JsonObject format = probe.getAsJsonObject("format");
|
||||||
JsonArray streams = probe.getAsJsonArray("streams");
|
JsonArray streams = probe.getAsJsonArray("streams");
|
||||||
log.info("Track egress recording {}: {}, {}", file.getFileName(), format, streams);
|
log.info("Track egress recording {}: {}, {}", file.getFileName(), format, streams);
|
||||||
|
|
@ -1203,8 +1204,8 @@ public class AbstractOpenViduTestappE2eTest extends OpenViduTestE2e {
|
||||||
// Starts with a keyframe, and its timestamps never go back. A simulcast layer
|
// Starts with a keyframe, and its timestamps never go back. A simulcast layer
|
||||||
// switch gives the keyframe of the new layer the timestamp of the frame it
|
// switch gives the keyframe of the new layer the timestamp of the frame it
|
||||||
// replaces, so a timestamp may repeat on a keyframe of a multi-layer track only
|
// replaces, so a timestamp may repeat on a keyframe of a multi-layer track only
|
||||||
List<String[]> packets = runMediaCommand("ffprobe", "-v", "error", "-select_streams", "v:0",
|
List<String[]> packets = runMediaCommand(Ffmpeg.command("ffprobe", "-v", "error", "-select_streams", "v:0",
|
||||||
"-show_entries", "packet=dts_time,flags", "-of", "csv=p=0", file.toString())[0].lines()
|
"-show_entries", "packet=dts_time,flags", "-of", "csv=p=0", file.toString()))[0].lines()
|
||||||
.map(l -> l.trim().split(",")).filter(p -> p.length == 2 && !"N/A".equals(p[0])).toList();
|
.map(l -> l.trim().split(",")).filter(p -> p.length == 2 && !"N/A".equals(p[0])).toList();
|
||||||
Assertions.assertTrue(packets.get(0)[1].startsWith("K"), "The recording must start with a keyframe");
|
Assertions.assertTrue(packets.get(0)[1].startsWith("K"), "The recording must start with a keyframe");
|
||||||
for (int i = 1; i < packets.size(); i++) {
|
for (int i = 1; i < packets.size(); i++) {
|
||||||
|
|
@ -1226,7 +1227,7 @@ public class AbstractOpenViduTestappE2eTest extends OpenViduTestE2e {
|
||||||
}
|
}
|
||||||
command.addAll(List.of("-i", file.toString(), "-map", "0:v", "-vf", "showinfo", "-fps_mode", "passthrough",
|
command.addAll(List.of("-i", file.toString(), "-map", "0:v", "-vf", "showinfo", "-fps_mode", "passthrough",
|
||||||
"-enc_time_base", "demux", "-f", "null", "-"));
|
"-enc_time_base", "demux", "-f", "null", "-"));
|
||||||
String decodeLog = runMediaCommand(command.toArray(new String[0]))[1];
|
String decodeLog = runMediaCommand(Ffmpeg.command(command))[1];
|
||||||
// Errors of the demuxer and the decoder: the null muxer's own complaints about a
|
// Errors of the demuxer and the decoder: the null muxer's own complaints about a
|
||||||
// repeated timestamp are covered above
|
// repeated timestamp are covered above
|
||||||
List<String> errors = decodeLog.lines().filter(l -> l.contains("[error]") || l.contains("[fatal]"))
|
List<String> errors = decodeLog.lines().filter(l -> l.contains("[error]") || l.contains("[fatal]"))
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import io.livekit.server.RoomCreate;
|
||||||
import io.livekit.server.RoomRecord;
|
import io.livekit.server.RoomRecord;
|
||||||
import io.minio.DownloadObjectArgs;
|
import io.minio.DownloadObjectArgs;
|
||||||
import io.minio.MinioClient;
|
import io.minio.MinioClient;
|
||||||
|
import io.openvidu.test.browsers.utils.Ffmpeg;
|
||||||
import livekit.LivekitModels.ParticipantInfo;
|
import livekit.LivekitModels.ParticipantInfo;
|
||||||
import livekit.LivekitModels.TrackInfo;
|
import livekit.LivekitModels.TrackInfo;
|
||||||
import livekit.LivekitModels.TrackSource;
|
import livekit.LivekitModels.TrackSource;
|
||||||
|
|
@ -89,8 +90,8 @@ import okhttp3.Response;
|
||||||
*
|
*
|
||||||
* Needs the Compose deployment (egress + MinIO) running openvidu/egress-pro,
|
* Needs the Compose deployment (egress + MinIO) running openvidu/egress-pro,
|
||||||
* which exits at startup without a valid OpenVidu PRO license (the CI workflow
|
* which exits at startup without a valid OpenVidu PRO license (the CI workflow
|
||||||
* "OpenVidu Pro E2E Egress Pro tests" sets both up), and ffmpeg/ffprobe on the
|
* "OpenVidu Pro E2E Egress Pro tests" sets both up). ffmpeg and ffprobe run in
|
||||||
* host, with libvpx and libdav1d.
|
* the Docker image of {@link Ffmpeg}, never the host's.
|
||||||
*
|
*
|
||||||
* @author Pablo Fuente (pablofuenteperez@gmail.com)
|
* @author Pablo Fuente (pablofuenteperez@gmail.com)
|
||||||
*/
|
*/
|
||||||
|
|
@ -235,7 +236,7 @@ public class OpenViduTestAppE2eEgressProTest extends AbstractOpenViduTestappE2eT
|
||||||
|
|
||||||
@BeforeAll()
|
@BeforeAll()
|
||||||
protected static void setupAll() throws Exception {
|
protected static void setupAll() throws Exception {
|
||||||
checkFfmpegInstallation();
|
pullFfmpegImage();
|
||||||
loadEnvironmentVariables();
|
loadEnvironmentVariables();
|
||||||
setUpLiveKitClient();
|
setUpLiveKitClient();
|
||||||
syncMediaScript = Files.readString(Paths.get("src/test/resources/participant-passthrough-sync-media.js"));
|
syncMediaScript = Files.readString(Paths.get("src/test/resources/participant-passthrough-sync-media.js"));
|
||||||
|
|
@ -3041,8 +3042,8 @@ public class OpenViduTestAppE2eEgressProTest extends AbstractOpenViduTestappE2eT
|
||||||
}
|
}
|
||||||
|
|
||||||
private void probe() throws Exception {
|
private void probe() throws Exception {
|
||||||
Exec probe = Exec.run(60, "ffprobe", "-v", "error", "-show_format", "-show_streams", "-of", "json",
|
Exec probe = Exec.run(60, Ffmpeg.command("ffprobe", "-v", "error", "-show_format", "-show_streams", "-of",
|
||||||
file.toString());
|
"json", file.toString()));
|
||||||
Assertions.assertEquals(0, probe.exitCode, "ffprobe failed on " + file + ": " + probe.stderr);
|
Assertions.assertEquals(0, probe.exitCode, "ffprobe failed on " + file + ": " + probe.stderr);
|
||||||
JsonObject json = JsonParser.parseString(probe.stdout).getAsJsonObject();
|
JsonObject json = JsonParser.parseString(probe.stdout).getAsJsonObject();
|
||||||
JsonObject format = json.getAsJsonObject("format");
|
JsonObject format = json.getAsJsonObject("format");
|
||||||
|
|
@ -3060,8 +3061,8 @@ public class OpenViduTestAppE2eEgressProTest extends AbstractOpenViduTestappE2eT
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readPackets() throws Exception {
|
private void readPackets() throws Exception {
|
||||||
Exec packets = Exec.run(120, "ffprobe", "-v", "error", "-show_entries", "packet=stream_index,pts_time",
|
Exec packets = Exec.run(120, Ffmpeg.command("ffprobe", "-v", "error", "-show_entries",
|
||||||
"-of", "csv=p=0", file.toString());
|
"packet=stream_index,pts_time", "-of", "csv=p=0", file.toString()));
|
||||||
Assertions.assertEquals(0, packets.exitCode, "ffprobe could not read the packets: " + packets.stderr);
|
Assertions.assertEquals(0, packets.exitCode, "ffprobe could not read the packets: " + packets.stderr);
|
||||||
for (String line : packets.stdout.split("\n")) {
|
for (String line : packets.stdout.split("\n")) {
|
||||||
String[] fields = line.trim().split(",");
|
String[] fields = line.trim().split(",");
|
||||||
|
|
@ -3078,8 +3079,8 @@ public class OpenViduTestAppE2eEgressProTest extends AbstractOpenViduTestappE2eT
|
||||||
private void decode() throws Exception {
|
private void decode() throws Exception {
|
||||||
// passthrough timestamps in the demuxer's time base: frames 33 +/- 1 ms apart
|
// passthrough timestamps in the demuxer's time base: frames 33 +/- 1 ms apart
|
||||||
// must not collide in a 1/30 s encoder time base
|
// must not collide in a 1/30 s encoder time base
|
||||||
Exec decode = Exec.run(300, "ffmpeg", "-nostdin", "-v", "error", "-i", file.toString(), "-map", "0",
|
Exec decode = Exec.run(300, Ffmpeg.command("ffmpeg", "-nostdin", "-v", "error", "-i", file.toString(), "-map",
|
||||||
"-fps_mode", "passthrough", "-enc_time_base", "demux", "-f", "null", "-");
|
"0", "-fps_mode", "passthrough", "-enc_time_base", "demux", "-f", "null", "-"));
|
||||||
decodeErrors = decode.exitCode == 0 ? decode.stderr.trim()
|
decodeErrors = decode.exitCode == 0 ? decode.stderr.trim()
|
||||||
: "exit " + decode.exitCode + ": " + decode.stderr;
|
: "exit " + decode.exitCode + ": " + decode.stderr;
|
||||||
}
|
}
|
||||||
|
|
@ -3103,7 +3104,7 @@ public class OpenViduTestAppE2eEgressProTest extends AbstractOpenViduTestappE2eT
|
||||||
command.addAll(List.of("-i", file.toString(), "-map", "0:" + s.index, "-fps_mode", "passthrough", "-vf",
|
command.addAll(List.of("-i", file.toString(), "-map", "0:" + s.index, "-fps_mode", "passthrough", "-vf",
|
||||||
"crop=iw:2:0:trunc(ih/4)*2,scale=" + BAR_ROW_WIDTH + ":1:flags=area,format=gray,showinfo",
|
"crop=iw:2:0:trunc(ih/4)*2,scale=" + BAR_ROW_WIDTH + ":1:flags=area,format=gray,showinfo",
|
||||||
"-f", "rawvideo", rows.toString()));
|
"-f", "rawvideo", rows.toString()));
|
||||||
Exec frames = Exec.run(300, command.toArray(new String[0]));
|
Exec frames = Exec.run(300, Ffmpeg.command(command));
|
||||||
Assertions.assertEquals(0, frames.exitCode, "ffmpeg could not read the video: " + frames.stderr);
|
Assertions.assertEquals(0, frames.exitCode, "ffmpeg could not read the video: " + frames.stderr);
|
||||||
Matcher pts = SHOWINFO_PTS.matcher(frames.stderr);
|
Matcher pts = SHOWINFO_PTS.matcher(frames.stderr);
|
||||||
while (pts.find()) {
|
while (pts.find()) {
|
||||||
|
|
@ -3145,8 +3146,9 @@ public class OpenViduTestAppE2eEgressProTest extends AbstractOpenViduTestappE2eT
|
||||||
}
|
}
|
||||||
s.bar.sort((a, b) -> Double.compare(a[0], b[0]));
|
s.bar.sort((a, b) -> Double.compare(a[0], b[0]));
|
||||||
countBrokenPictures(s, barRuns);
|
countBrokenPictures(s, barRuns);
|
||||||
Exec sizes = Exec.run(300, "ffprobe", "-v", "error", "-select_streams", String.valueOf(s.index),
|
Exec sizes = Exec.run(300, Ffmpeg.command("ffprobe", "-v", "error", "-select_streams",
|
||||||
"-show_entries", "frame=pts_time,width,height", "-of", "csv=p=0", file.toString());
|
String.valueOf(s.index), "-show_entries", "frame=pts_time,width,height", "-of", "csv=p=0",
|
||||||
|
file.toString()));
|
||||||
Assertions.assertEquals(0, sizes.exitCode, "ffprobe could not read the frame sizes: " + sizes.stderr);
|
Assertions.assertEquals(0, sizes.exitCode, "ffprobe could not read the frame sizes: " + sizes.stderr);
|
||||||
for (String line : sizes.stdout.split("\n")) {
|
for (String line : sizes.stdout.split("\n")) {
|
||||||
String[] fields = line.trim().split(",");
|
String[] fields = line.trim().split(",");
|
||||||
|
|
@ -3279,8 +3281,8 @@ public class OpenViduTestAppE2eEgressProTest extends AbstractOpenViduTestappE2eT
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readAudio(FileTrack s) throws Exception {
|
private void readAudio(FileTrack s) throws Exception {
|
||||||
Exec beeps = Exec.run(300, "ffmpeg", "-nostdin", "-copyts", "-i", file.toString(), "-map", "0:" + s.index,
|
Exec beeps = Exec.run(300, Ffmpeg.command("ffmpeg", "-nostdin", "-copyts", "-i", file.toString(), "-map",
|
||||||
"-af", "silencedetect=n=-30dB:d=0.3", "-f", "null", "-");
|
"0:" + s.index, "-af", "silencedetect=n=-30dB:d=0.3", "-f", "null", "-"));
|
||||||
Assertions.assertEquals(0, beeps.exitCode, "ffmpeg could not read the audio: " + beeps.stderr);
|
Assertions.assertEquals(0, beeps.exitCode, "ffmpeg could not read the audio: " + beeps.stderr);
|
||||||
// A beep is sound between a silence_end and the next silence_start. One cut
|
// A beep is sound between a silence_end and the next silence_start. One cut
|
||||||
// short (the track resumed, after silence, in the middle of a beep) does not
|
// short (the track resumed, after silence, in the middle of a beep) does not
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ public class OpenViduTestAppE2eServerSdkTest extends AbstractOpenViduTestappE2eT
|
||||||
|
|
||||||
@BeforeAll()
|
@BeforeAll()
|
||||||
protected static void setupAll() throws Exception {
|
protected static void setupAll() throws Exception {
|
||||||
|
pullFfmpegImage();
|
||||||
loadEnvironmentVariables();
|
loadEnvironmentVariables();
|
||||||
setUpLiveKitClient();
|
setUpLiveKitClient();
|
||||||
CompletableFuture.runAsync(OpenViduTestAppE2eServerSdkTest::pullRemoteBrowserImages);
|
CompletableFuture.runAsync(OpenViduTestAppE2eServerSdkTest::pullRemoteBrowserImages);
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ import io.minio.errors.InvalidResponseException;
|
||||||
import io.minio.errors.ServerException;
|
import io.minio.errors.ServerException;
|
||||||
import io.minio.errors.XmlParserException;
|
import io.minio.errors.XmlParserException;
|
||||||
import io.minio.messages.Item;
|
import io.minio.messages.Item;
|
||||||
|
import io.openvidu.test.browsers.utils.Ffmpeg;
|
||||||
import livekit.LivekitIngress.IngressInfo;
|
import livekit.LivekitIngress.IngressInfo;
|
||||||
import livekit.LivekitIngress.IngressState;
|
import livekit.LivekitIngress.IngressState;
|
||||||
import livekit.LivekitModels.ConnectionQuality;
|
import livekit.LivekitModels.ConnectionQuality;
|
||||||
|
|
@ -110,7 +111,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
||||||
|
|
||||||
@BeforeAll()
|
@BeforeAll()
|
||||||
protected static void setupAll() throws Exception {
|
protected static void setupAll() throws Exception {
|
||||||
checkFfmpegInstallation();
|
pullFfmpegImage();
|
||||||
loadEnvironmentVariables();
|
loadEnvironmentVariables();
|
||||||
setUpLiveKitClient();
|
setUpLiveKitClient();
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
|
|
@ -2165,11 +2166,11 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
||||||
// Generate a WAV file with 5s tone + 5s silence (10 seconds total).
|
// Generate a WAV file with 5s tone + 5s silence (10 seconds total).
|
||||||
// Chrome will loop this file as the fake audio capture source.
|
// Chrome will loop this file as the fake audio capture source.
|
||||||
String dtxAudioPath = "/opt/openvidu/dtx_test_audio.wav";
|
String dtxAudioPath = "/opt/openvidu/dtx_test_audio.wav";
|
||||||
String ffmpegCmd = "ffmpeg -y"
|
String ffmpegCmd = Ffmpeg.shellCommand("ffmpeg", "-y"
|
||||||
+ " -f lavfi -i sine=frequency=440:duration=5"
|
+ " -f lavfi -i sine=frequency=440:duration=5"
|
||||||
+ " -f lavfi -i anullsrc=r=48000:cl=mono"
|
+ " -f lavfi -i anullsrc=r=48000:cl=mono"
|
||||||
+ " -filter_complex \"[1]atrim=duration=5[silence];[0][silence]concat=n=2:v=0:a=1[out]\""
|
+ " -filter_complex \"[1]atrim=duration=5[silence];[0][silence]concat=n=2:v=0:a=1[out]\""
|
||||||
+ " -map \"[out]\" -ar 48000 -ac 1 " + dtxAudioPath;
|
+ " -map \"[out]\" -ar 48000 -ac 1 " + dtxAudioPath);
|
||||||
String ffmpegOutput = commandLine.executeCommand(ffmpegCmd, 30);
|
String ffmpegOutput = commandLine.executeCommand(ffmpegCmd, 30);
|
||||||
log.info("ffmpeg output: {}", ffmpegOutput);
|
log.info("ffmpeg output: {}", ffmpegOutput);
|
||||||
java.io.File dtxAudioFile = new java.io.File(dtxAudioPath);
|
java.io.File dtxAudioFile = new java.io.File(dtxAudioPath);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue