Découpage vidéo efficace en Java avec FFmpeg et JavaCV
Le découpage d’une vidéo doit préserver son audio et la synchronisation de sa présentation. Compter
les appels à FFmpegFrameGrabber.grab() ne mesure pas la durée de la vidéo : un appel peut
renvoyer un bloc audio ou une image vidéo. Les fréquences d’images variables rendent aussi peu
fiable tout calcul fondé sur le nombre d’images.
Cet exemple utilise JavaCV pour inspecter l’entrée et son exécutable FFmpeg intégré pour découper les paquets compressés. FFmpeg gère les horodatages et l’entrelacement audio. Aucun shell n’interprète le nom du fichier d’entrée.
Pourquoi découper des vidéos ?
Les segments sont utiles pour le montage, la distribution de téléchargements et le traitement indépendant. Des segments MP4 autonomes ne constituent pas une présentation de streaming adaptatif ; HLS ou DASH nécessite aussi un manifeste et des renditions encodées de manière appropriée.
Configurer votre environnement Java
Pour la configuration Linux testée, utilisez le JDK 21.0.12.1 et Maven 3.9.16. Enregistrez ceci sous pom.xml, puis placez
VideoSplitter.java ci-dessous dans src/main/java/. Ces versions épinglées utilisent JavaCV 1.5.14 et
FFmpeg 8.1.2-1.5.14.
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>video-splitter</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<javacpp.platform>macosx-arm64</javacpp.platform>
</properties>
<dependencies>
<dependency>
<groupId>org.bytedeco</groupId><artifactId>javacv</artifactId><version>1.5.14</version>
<exclusions><exclusion><groupId>*</groupId><artifactId>*</artifactId></exclusion></exclusions>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId><artifactId>javacpp</artifactId><version>1.5.14</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId><artifactId>javacpp</artifactId><version>1.5.14</version>
<classifier>${javacpp.platform}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId><artifactId>ffmpeg</artifactId><version>8.1.2-1.5.14</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId><artifactId>ffmpeg</artifactId><version>8.1.2-1.5.14</version>
<classifier>${javacpp.platform}</classifier>
</dependency>
</dependencies>
<build><plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId><artifactId>maven-dependency-plugin</artifactId>
<version>3.8.1</version>
</plugin>
</plugins></build>
</project>
Définissez javacpp.platform sur votre plateforme réelle, par exemple linux-x86_64 au lieu de
macosx-arm64. Seuls les artefacts natifs nécessaires sont ainsi téléchargés. Consultez les
instructions d’installation de JavaCV pour connaître les plateformes prises en charge.
Comprendre ffmpegframegrabber et ffmpegframerecorder
FFmpegFrameGrabber peut décoder des images et des échantillons. FFmpegFrameRecorder.record(frame) encode
à nouveau ces images décodées, même si vous sélectionnez le codec d’entrée. Sélectionner un codec
n’équivaut pas à copier le flux.
Pour découper sans modification visuelle, le sous-processus ci-dessous utilise le chemin de paquets
-c copy de FFmpeg. Il préserve le premier flux vidéo et, s’il existe, le premier flux audio. Les
pistes audio supplémentaires, les sous-titres, les chapitres, les métadonnées de rotation et les
métadonnées arbitraires du conteneur sortent du périmètre de préservation de cet exemple.
Vérifiez-les séparément avant d’étendre le mappage des flux.
Guide pas à pas pour découper des vidéos
Enregistrez cette classe complète sous VideoSplitter.java. Le répertoire de sortie ne doit pas déjà exister,
afin qu’une exécution échouée ne puisse pas écraser un résultat antérieur. Si FFmpeg échoue, les
fichiers partiels y restent pour le diagnostic. Utilisez des entrées intactes et validez les
segments comme indiqué ci-dessous avant d’utiliser le répertoire de sortie. L’extracteur d’images
vérifie les métadonnées, et la copie de paquets ne décode pas entièrement le média pour le valider.
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import org.bytedeco.javacpp.Loader;
import org.bytedeco.javacv.FFmpegFrameGrabber;
public final class VideoSplitter {
public static void split(Path input, Path output, double seconds) throws Exception {
if (!Double.isFinite(seconds) || seconds <= 0) {
throw new IllegalArgumentException("Segment duration must be positive and finite");
}
Path source = input.toRealPath();
if (!Files.isRegularFile(source)) throw new IOException("Input must be a regular file");
try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(source.toFile())) {
grabber.start();
if (grabber.getImageWidth() <= 0) throw new IOException("Input has no video stream");
}
Files.createDirectory(output);
Path destination = output.toRealPath();
String executable = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);
Process process = new ProcessBuilder(
executable, "-hide_banner", "-nostdin", "-v", "error", "-xerror", "-n",
"-i", source.toString(), "-map", "0:v:0", "-map", "0:a:0?",
"-c", "copy", "-f", "segment", "-segment_format", "mp4",
"-segment_time", BigDecimal.valueOf(seconds).toPlainString(), "-reset_timestamps", "1",
"segment-%03d.mp4"
).directory(destination.toFile()).redirectErrorStream(true)
.redirectOutput(destination.resolve("ffmpeg.log").toFile()).start();
try {
if (!process.waitFor(10, TimeUnit.MINUTES)) throw new IOException("FFmpeg timed out");
if (process.exitValue() != 0) throw new IOException("FFmpeg failed; inspect ffmpeg.log");
} finally {
if (process.isAlive()) {
process.destroyForcibly();
process.waitFor();
}
}
}
public static void main(String[] args) throws Exception {
if (args.length != 3) throw new IllegalArgumentException("input output-directory seconds");
split(Path.of(args[0]), Path.of(args[1]), Double.parseDouble(args[2]));
}
}
Sous macOS ou Linux, compilez-le et exécutez-le avec :
mvn compile dependency:copy-dependencies &&
java -cp 'target/classes:target/dependency/*' VideoSplitter input.mp4 segments 10
FFmpeg commence un segment à une image clé vidéo située à la limite demandée ou après celle-ci. Une
demande de 10 secondes est une cible, pas une coupe exacte. Le dernier segment peut être
plus court, et des images clés très espacées peuvent produire des segments beaucoup plus longs. Les
paquets audio conservent leur synchronisation relative ; leurs limites ne coïncident pas
nécessairement exactement avec les images vidéo. Un réencodage avec des images clés placées
délibérément est nécessaire pour obtenir des limites éditoriales exactes. Consultez la
documentation du muxer segment.
Mettre en œuvre le multithreading pour de meilleures performances
Traitez le flux de paquets de chaque entrée de manière séquentielle. Lancer plusieurs décodeurs et
compter des images audio/vidéo mélangées peut faire perdre de l’audio ou dupliquer du contenu aux
limites de positionnement. Pour des fichiers d’entrée indépendants, utilisez un exécuteur borné,
attribuez à chaque tâche son propre répertoire de sortie et appelez Future.get() pour chaque tâche
soumise afin que les échecs remontent jusqu’à l’appelant. Mesurez le débit avant d’augmenter la
concurrence : la copie de paquets est souvent limitée par le stockage.
Conseils d’optimisation des performances
La copie de flux évite le travail d’encodage et la perte de génération. Elle requiert des codecs compatibles avec le conteneur de sortie ; les codecs non pris en charge devraient échouer de manière visible plutôt que de perdre silencieusement un flux. Pour le redimensionnement, l’ajout de filigrane ou les changements de codec, utilisez un flux de travail de transcodage distinct et testez également son comportement audio et temporel.
Bonnes pratiques de gestion des erreurs
L’exemple rejette les durées non positives ou non finies, les entrées manquantes et les répertoires
de sortie existants. Les durées utilisent des décimales simples indépendantes de la locale, y compris
de petites valeurs comme 0.0001 secondes. La précision et la plage de durée de FFmpeg
s’appliquent toujours ; un double Java positif et fini ne garantit pas à lui seul une durée
représentable par FFmpeg. Les erreurs et les délais d’expiration se propagent sous forme d’échecs.
L’option -xerror de FFmpeg arrête le traitement sur les erreurs détectées,
y compris les paquets corrompus signalés par le démultiplexeur. Un retour réussi nécessite tout de
même des vérifications de la sortie : la copie de flux peut laisser passer des dommages que seul un
décodeur détecte.
Ne divulguez pas le contenu de ffmpeg.log, car il peut contenir des chemins de fichiers.
Une frontière de processus n’est pas un bac à sable : exécutez les médias non fiables avec des
restrictions sur le système de fichiers, le réseau, le CPU et la mémoire imposées par
l’environnement de vos processus de traitement.
Conseils pratiques pour optimiser le traitement vidéo
Inspectez les sorties réelles, y compris pour des entrées avec audio, à fréquence d’images variable
et sans audio. Avec ffmpeg et ffprobe installés séparément, exécutez ceci depuis le répertoire du
projet pour inspecter et décoder chaque segment. Le sous-shell s’arrête à la première vérification
échouée :
(
set -e
for segment in segments/segment-*.mp4; do
ffprobe -v error -show_streams -show_format -of json "$segment"
ffmpeg -v error -xerror -err_detect explode -i "$segment" \
-map 0:v:0 -map '0:a:0?' -f null -
done
)
Examinez les diagnostics même lorsqu’une commande réussit. Comparez le nombre total de paquets vidéo/audio et leurs charges utiles avec l’entrée, et vérifiez la fin attendue par rapport à une source complète connue. Tenez compte des longueurs de segment alignées sur les images clés et des décalages d’horodatage du conteneur ; vérifier seulement que les fichiers existent ne permet pas d’établir la préservation.
Conclusion et ressources complémentaires
Utilisez JavaCV pour l’inspection et le segmenteur de paquets de FFmpeg pour un découpage efficace sans réencodage. Pour le streaming adaptatif, consultez le service d’encodage vidéo de Transloadit et le Robot 🤖 /video/adaptive (English).
