pom文件
XML
<dependency>
<groupId>org.mp4parser</groupId>
<artifactId>isoparser</artifactId>
<version>1.9.36</version>
</dependency>
<dependency>
<groupId>org.mp4parser</groupId>
<artifactId>muxer</artifactId>
<version>1.9.36</version>
</dependency>
实现代码
java
package com.test.vodio;
import org.mp4parser.BasicContainer;
import org.mp4parser.Container;
import org.mp4parser.IsoFile;
import org.mp4parser.boxes.iso14496.part12.CompositionTimeToSample;
import org.mp4parser.boxes.iso14496.part12.SampleDependencyTypeBox;
import org.mp4parser.boxes.iso14496.part12.SampleDescriptionBox;
import org.mp4parser.boxes.iso14496.part12.SubSampleInformationBox;
import org.mp4parser.boxes.sampleentry.SampleEntry;
import org.mp4parser.boxes.samplegrouping.GroupEntry;
import org.mp4parser.muxer.*;
import org.mp4parser.muxer.builder.DefaultMp4Builder;
import org.mp4parser.muxer.container.mp4.MovieCreator;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author JR
* @version 1.0
* @description:
* @date 2025年09月11日 17:01
*/
public class Test {
// 4.3G的字节数 (1G = 1024^3字节)
private static final long MAX_FILE_SIZE_BYTES = (long) (4.3 * 1024 * 1024 * 1024);
/**
* 按时长切割MP4文件,大于4.3G时自动计算切割次数
* @param inputFilePath 输入文件路径
* @param outputDir 输出目录
* @throws IOException 处理过程中可能抛出的IO异常
*/
public void splitMP4ByDuration(String inputFilePath, String outputDir) throws IOException {
File outputDirectory = new File(outputDir);
if (!outputDirectory.exists()) {
outputDirectory.mkdirs();
}
File inputFile = new File(inputFilePath);
long fileSize = inputFile.length();
// 转换文件大小为GB
double fileSizeGB = (double) fileSize / (1024 * 1024 * 1024);
System.out.printf("文件大小: %.2f GB%n", fileSizeGB);
// 如果文件小于等于4.3G,不处理
if (fileSize <= MAX_FILE_SIZE_BYTES) {
System.out.println("文件小于等于4.3G,不需要切割");
return;
}
// 计算需要切割的次数(向上取整)
int splitCount = (int) Math.ceil(fileSizeGB / 4.3);
System.out.printf("文件大于4.3G,需要切割为 %d 个片段%n", splitCount);
// 读取原始视频
Movie movie = MovieCreator.build(inputFilePath);
List<Track> tracks = movie.getTracks();
// 获取视频总时长(微秒)- 修正:使用1.9.41版本支持的方式
long totalDurationUs = getTotalDurationUs(tracks);
// 计算每个片段的时长(微秒)
long segmentDurationUs = totalDurationUs / splitCount;
// 切割并保存每个片段
for (int i = 0; i < splitCount; i++) {
long startTimeUs = i * segmentDurationUs;
// 最后一个片段包含剩余的所有时长
long endTimeUs = (i == splitCount - 1) ? totalDurationUs : (i + 1) * segmentDurationUs;
List<Track> segmentTracks = new ArrayList<>();
for (Track track : tracks) {
Track segmentTrack = new CroppedTrack(track, startTimeUs, endTimeUs);
segmentTracks.add(segmentTrack);
}
Movie segmentMovie = new Movie();
for (Track track : segmentTracks) {
segmentMovie.addTrack(track);
}
DefaultMp4Builder builder = new DefaultMp4Builder();
BasicContainer build = (BasicContainer) builder.build(segmentMovie);
String outputFilePath = outputDir + File.separator +
getFileNameWithoutExtension(inputFile) + "_part" + (i + 1) + ".mp4";
try (FileOutputStream fos = new FileOutputStream(outputFilePath);
FileChannel fc = fos.getChannel()) {
build.writeContainer(fc);
}
System.out.printf("已生成片段 %d: %s%n", i + 1, outputFilePath);
}
}
/**
* 修正:计算视频总时长(微秒),使用1.9.41版本的TrackMetaData
*/
private long getTotalDurationUs(List<Track> tracks) {
long maxDuration = 0;
for (Track track : tracks) {
// 1.9.41版本通过TrackMetaData获取时间信息
TrackMetaData trackMetaData = track.getTrackMetaData();
if (trackMetaData != null) {
// 时长 = 轨道持续时间 / 时间尺度 * 1000000(转换为微秒)
long duration = (long)(track.getDuration() * 1000000.0 / trackMetaData.getTimescale());
if (duration > maxDuration) {
maxDuration = duration;
}
}
}
return maxDuration;
}
private String getFileNameWithoutExtension(File file) {
String fileName = file.getName();
int lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex > 0) {
return fileName.substring(0, lastDotIndex);
}
return fileName;
}
/**
* 完整实现Track接口的裁剪轨道类
*/
private static class CroppedTrack implements Track {
private final Track source;
private final long startTimeUs;
private final long endTimeUs;
private List<Sample> samples;
private TrackMetaData trackMetaData;
private List<Long> sampleDurationsList;
private long[] sampleDurations;
private List<CompositionTimeToSample.Entry> compositionTimeEntries;
private long[] syncSamples;
private List<SampleDependencyTypeBox.Entry> sampleDependencies;
private SubSampleInformationBox subSampleInformationBox;
private String name;
private List<Edit> edits;
private Map<GroupEntry, long[]> sampleGroups;
public CroppedTrack(Track source, long startTimeUs, long endTimeUs) {
this.source = source;
this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs;
this.trackMetaData = source.getTrackMetaData();
this.sampleDurationsList = new ArrayList<>();
calculateTrackData();
}
/**
* 计算裁剪后轨道的所有核心数据
*/
private void calculateTrackData() {
samples = new ArrayList<>();
sampleDurationsList.clear();
long currentTimeUs = 0;
long timeScale = trackMetaData.getTimescale();
List<Sample> sourceSamples = source.getSamples();
List<Long> sourceSampleDurationsList = Arrays.stream(source.getSampleDurations())
.boxed()
.collect(Collectors.toList());
// 1. 筛选样本并记录时长
for (int i = 0; i < sourceSamples.size(); i++) {
Sample sample = sourceSamples.get(i);
long sampleDuration = sourceSampleDurationsList.get(i);
long sampleDurationUs = (long)(sampleDuration * 1000000.0 / timeScale);
if (currentTimeUs + sampleDurationUs > startTimeUs && currentTimeUs < endTimeUs) {
samples.add(sample);
sampleDurationsList.add(sampleDuration);
}
currentTimeUs += sampleDurationUs;
if (currentTimeUs >= endTimeUs) break;
}
// 2. 转换样本时长为数组
sampleDurations = sampleDurationsList.stream().mapToLong(Long::longValue).toArray();
// 3. 处理合成时间条目
compositionTimeEntries = source.getCompositionTimeEntries();
// 4. 筛选同步样本(关键帧等)
syncSamples = filterSyncSamples(source.getSyncSamples());
sampleDependencies = source.getSampleDependencies();
// 6. 子样本信息
subSampleInformationBox = source.getSubsampleInformationBox();
// 7. 轨道名称
name = source.getName();
// 8. 编辑信息
edits = source.getEdits();
// 9. 样本组信息
sampleGroups = source.getSampleGroups();
}
/**
* 筛选出在裁剪样本范围内的同步样本索引
*/
private long[] filterSyncSamples(long[] sourceSyncSamples) {
if (sourceSyncSamples == null || sourceSyncSamples.length == 0) {
return new long[0];
}
List<Long> filtered = new ArrayList<>();
int sourceSampleCount = source.getSamples().size();
int targetSampleCount = samples.size();
// 只保留在裁剪后样本范围内的同步点
for (long syncSample : sourceSyncSamples) {
if (syncSample > 0 && syncSample <= sourceSampleCount &&
syncSample <= targetSampleCount) {
filtered.add(syncSample);
}
}
return filtered.stream().mapToLong(Long::longValue).toArray();
}
// === 实现Track接口的所有方法 ===
@Override
public List<SampleEntry> getSampleEntries() {
return source.getSampleEntries();
}
@Override
public long[] getSampleDurations() {
return sampleDurations;
}
@Override
public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
return compositionTimeEntries;
}
@Override
public long[] getSyncSamples() {
return syncSamples;
}
@Override
public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
return sampleDependencies;
}
@Override
public SubSampleInformationBox getSubsampleInformationBox() {
return subSampleInformationBox;
}
@Override
public String getName() {
return name;
}
@Override
public List<Edit> getEdits() {
return edits;
}
@Override
public Map<GroupEntry, long[]> getSampleGroups() {
return sampleGroups;
}
@Override
public List<Sample> getSamples() {
return samples;
}
@Override
public TrackMetaData getTrackMetaData() {
return trackMetaData;
}
@Override
public String getHandler() {
return source.getHandler();
}
@Override
public long getDuration() {
return (long)((endTimeUs - startTimeUs) * trackMetaData.getTimescale() / 1000000.0);
}
@Override
public void close() throws IOException {
source.close();
}
}
public static void main(String[] args) {
Test splitter = new Test();
try {
splitter.splitMP4ByDuration("C:\\Users\\Administrator\\Desktop\\魔童闹海.mp4", "output_segments");
System.out.println("切割完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}