Videos in Java: transkodieren, Größe ändern, Wasserzeichen setzen
In der heutigen digitalen Landschaft sind Videoinhalte entscheidend. Ob Sie eine Medienanwendung entwickeln oder eine Videoplattform betreiben: Videos effizient zu verarbeiten ist unerlässlich. Dieser Leitfaden führt Sie durch das Transkodieren, die Größenänderung und das Einfügen von Wasserzeichen bei Videos in Java mithilfe der Open-Source-Bibliothek JavaCV, die Java-Wrapper für FFmpeg bereitstellt.
Voraussetzungen
Bevor wir beginnen, stellen Sie sicher, dass Sie Folgendes installiert haben:
- Java Development Kit (JDK): Version 11 oder höher
- Maven: zum Verwalten von Projektabhängigkeiten
- native FFmpeg- und OpenCV-Bibliotheken, eingebunden durch die unten stehende Abhängigkeit
javacv-platform
Fügen Sie Ihrer Datei pom.xml diese Abhängigkeiten und die Konfiguration des Test-Plugins hinzu:
<dependencies>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.11</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
</plugin>
</plugins>
</build>
Videos in Java transkodieren
Nachfolgend finden Sie ein Beispiel dafür, wie Sie ein Video mit JavaCV von einem Format in ein anderes transkodieren. Der Code codiert Video in H.264 und Audio in AAC in einem MP4-Container. Diese Beispiele setzen ein lesbares lokales Video und einen neuen Ausgabepfad voraus; vorhandene Dateien werden niemals überschrieben. Verarbeitet werden der erste Video- und der erste Audiostream, während Untertitel und weitere Spuren nicht kopiert werden.
Alle drei H.264-Beispiele fordern eine Video-Bitrate von 5 Mbps an, statt sich auf den Standardwert von 400 kbps in JavaCV zu verlassen. Passen Sie dieses Budget an Ihre Auflösung und Ihre Qualitätsanforderungen an; die tatsächliche Bitrate der Ausgabe hängt vom Encoder und vom Inhalt ab. Die Standard-Binaries von JavaCV verwenden OpenH264, daher setzen diese Beispiele nicht auf die x264-spezifische CRF-Option.
import static org.bytedeco.ffmpeg.global.avcodec.AV_CODEC_ID_AAC;
import static org.bytedeco.ffmpeg.global.avcodec.AV_CODEC_ID_H264;
import static org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_YUV420P;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import java.nio.file.Files;
import java.nio.file.Path;
public class VideoTranscoder {
public static void transcode(String inputFile, String outputFile) throws Exception {
if (!Files.isRegularFile(Path.of(inputFile))) {
throw new IllegalArgumentException("Video file not found");
}
Path output = Files.createFile(Path.of(outputFile));
boolean completed = false;
try {
try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile)) {
grabber.start();
if (grabber.getImageWidth() <= 0 || grabber.getImageHeight() <= 0) {
throw new IllegalArgumentException("Input must contain a video stream");
}
try (FFmpegFrameRecorder recorder =
new FFmpegFrameRecorder(
outputFile,
grabber.getImageWidth(),
grabber.getImageHeight(),
grabber.getAudioChannels())) {
recorder.setVideoCodec(AV_CODEC_ID_H264);
recorder.setVideoBitrate(5_000_000);
recorder.setFormat("mp4");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setPixelFormat(AV_PIX_FMT_YUV420P);
if (grabber.getAudioChannels() > 0) {
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setAudioCodec(AV_CODEC_ID_AAC);
recorder.setSampleRate(grabber.getSampleRate());
}
recorder.start();
Frame frame;
while ((frame = grabber.grab()) != null) {
if (frame.image != null) recorder.setTimestamp(frame.timestamp);
recorder.record(frame);
}
}
}
completed = true;
} finally {
if (!completed) Files.deleteIfExists(output);
}
}
}
Größe von Videos mit JavaCV ändern
Um die Größe von Videos zu ändern, verarbeiten Sie jeden Frame und passen dessen Abmessungen an. Dieses Beispiel nutzt OpenCV zusammen mit JavaCV, um die Größe der Videoframes zu ändern und dabei sicherzustellen, dass Ressourcen ordnungsgemäß freigegeben werden.
import static org.bytedeco.ffmpeg.global.avcodec.AV_CODEC_ID_AAC;
import static org.bytedeco.ffmpeg.global.avcodec.AV_CODEC_ID_H264;
import static org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_YUV420P;
import static org.bytedeco.opencv.global.opencv_imgproc.resize;
import org.bytedeco.javacv.*;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.Size;
import java.nio.file.Files;
import java.nio.file.Path;
public class VideoResizer {
public static void resizeVideo(String inputFile, String outputFile, int width, int height)
throws Exception {
if (!Files.isRegularFile(Path.of(inputFile))) {
throw new IllegalArgumentException("Video file not found");
}
if (width <= 0 || height <= 0 || width % 2 != 0 || height % 2 != 0) {
throw new IllegalArgumentException("H.264 dimensions must be positive and even");
}
Path output = Files.createFile(Path.of(outputFile));
boolean completed = false;
try {
try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile);
OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
Mat resizedMat = new Mat();
Size targetSize = new Size(width, height)) {
grabber.start();
if (grabber.getImageWidth() <= 0 || grabber.getImageHeight() <= 0) {
throw new IllegalArgumentException("Input must contain a video stream");
}
try (FFmpegFrameRecorder recorder =
new FFmpegFrameRecorder(
outputFile, width, height, grabber.getAudioChannels())) {
recorder.setVideoCodec(AV_CODEC_ID_H264);
recorder.setVideoBitrate(5_000_000);
recorder.setFormat("mp4");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setPixelFormat(AV_PIX_FMT_YUV420P);
if (grabber.getAudioChannels() > 0) {
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setAudioCodec(AV_CODEC_ID_AAC);
recorder.setSampleRate(grabber.getSampleRate());
}
recorder.start();
Frame frame;
while ((frame = grabber.grab()) != null) {
if (frame.image != null) {
resize(converter.convert(frame), resizedMat, targetSize);
recorder.setTimestamp(frame.timestamp);
recorder.record(converter.convert(resizedMat));
} else {
recorder.record(frame);
}
}
}
}
completed = true;
} finally {
if (!completed) Files.deleteIfExists(output);
}
}
}
Wasserzeichen in Videos einfügen
Ein Text-Wasserzeichen lässt sich über jeden Frame legen, indem Sie mit den Funktionen von OpenCV zeichnen. Das folgende Beispiel zeigt, wie Sie einem Video mit JavaCV ein einfaches Text-Wasserzeichen hinzufügen.
import static org.bytedeco.ffmpeg.global.avcodec.AV_CODEC_ID_AAC;
import static org.bytedeco.ffmpeg.global.avcodec.AV_CODEC_ID_H264;
import static org.bytedeco.ffmpeg.global.avutil.AV_PIX_FMT_YUV420P;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
import org.bytedeco.javacv.*;
import org.bytedeco.opencv.opencv_core.*;
import java.nio.file.Files;
import java.nio.file.Path;
public class VideoWatermarker {
public static void addWatermark(String inputFile, String outputFile, String watermarkText)
throws Exception {
if (!Files.isRegularFile(Path.of(inputFile))) {
throw new IllegalArgumentException("Video file not found");
}
Path output = Files.createFile(Path.of(outputFile));
boolean completed = false;
try {
try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile);
OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
Scalar color = new Scalar(255, 255, 255, 0);
Point position = new Point(50, 50)) {
grabber.start();
if (grabber.getImageWidth() <= 0 || grabber.getImageHeight() <= 0) {
throw new IllegalArgumentException("Input must contain a video stream");
}
try (FFmpegFrameRecorder recorder =
new FFmpegFrameRecorder(
outputFile,
grabber.getImageWidth(),
grabber.getImageHeight(),
grabber.getAudioChannels())) {
recorder.setVideoCodec(AV_CODEC_ID_H264);
recorder.setVideoBitrate(5_000_000);
recorder.setFormat("mp4");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setPixelFormat(AV_PIX_FMT_YUV420P);
if (grabber.getAudioChannels() > 0) {
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setAudioCodec(AV_CODEC_ID_AAC);
recorder.setSampleRate(grabber.getSampleRate());
}
recorder.start();
int font = FONT_HERSHEY_SIMPLEX;
Frame frame;
while ((frame = grabber.grab()) != null) {
if (frame.image != null) {
Mat mat = converter.convert(frame);
putText(
mat,
watermarkText,
position,
font,
1.0,
color,
2,
LINE_AA,
false);
Frame watermarkedFrame = converter.convert(mat);
recorder.setTimestamp(frame.timestamp);
recorder.record(watermarkedFrame);
} else {
recorder.record(frame);
}
}
}
}
completed = true;
} finally {
if (!completed) Files.deleteIfExists(output);
}
}
}
Speicherverwaltung und Performance-Optimierung
Effiziente Videoverarbeitung erfordert sowohl optimierte Performance als auch eine effektive Speicherverwaltung. Im folgenden Beispiel nutzen wir den ExecutorService von Java, um mehrere Videos gleichzeitig zu verarbeiten und dabei alle verfügbaren Prozessorkerne auszunutzen.
import java.util.*;
import java.util.concurrent.*;
public class VideoProcessingService {
private static final int NUM_THREADS = Runtime.getRuntime().availableProcessors();
private final ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
public void processVideosInParallel(List<String> videoFiles) throws Exception {
List<Future<?>> futures = new ArrayList<>();
for (String file : videoFiles) {
futures.add(
executor.submit(
() -> {
try {
processVideo(file);
} catch (Exception e) {
throw new RuntimeException(
"Failed to process video: " + file, e);
}
}));
}
// Wait for each task to complete
for (Future<?> future : futures) {
future.get();
}
}
private void processVideo(String file) throws Exception {
VideoTranscoder.transcode(file, file + ".processed.mp4");
}
public void shutdown() {
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
Videoverarbeitung testen
Implementieren Sie Tests, um zu überprüfen, ob Ihr Code zur Videoverarbeitung wie erwartet
funktioniert. Die folgenden JUnit-Tests prüfen, ob ein Video fehlerfrei transkodiert wird und ob bei
ungültigen Eingabedateien die passenden Exceptions ausgelöst werden. Die obige Maven-Konfiguration
enthält JUnit Jupiter und Surefire. Legen Sie diese Klasse unter src/test/java/VideoProcessingTest.java ab, hinterlegen Sie ein
kleines H.264/AAC-Fixture unter test.mp4
und führen Sie mvn test aus. Rufen Sie shutdown() in einem finally-Block auf, wenn Sie VideoProcessingService verwenden.
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
public class VideoProcessingTest {
@TempDir File temporaryDirectory;
@Test
public void testVideoTranscoding() {
File input = new File("test.mp4");
File output = new File(temporaryDirectory, "output.mp4");
assertDoesNotThrow(
() -> {
VideoTranscoder.transcode(input.getPath(), output.getPath());
assertTrue(output.exists());
assertTrue(output.length() > 0);
});
}
@Test
public void testInvalidVideoFile() {
assertThrows(
IllegalArgumentException.class,
() -> {
VideoTranscoder.transcode(
new File(temporaryDirectory, "nonexistent.mp4").getPath(),
new File(temporaryDirectory, "output.mp4").getPath());
});
}
}
Fazit
Die Videoverarbeitung in Java mit JavaCV bietet leistungsstarke Möglichkeiten zum Transkodieren, zur Größenänderung und zum Einfügen von Wasserzeichen. Die obigen Beispiele zeigen grundlegende Techniken wie sauberes Ressourcenmanagement, Fehlerbehandlung und Performance-Optimierung. Für die Videoverarbeitung mit hohem Volumen oder auf Enterprise-Ebene lohnt sich ein Blick auf den Service Video-Encoding von Transloadit, der skalierbare und effiziente Lösungen bietet.
