mp4parser

https://github.com/sannies/mp4parser

Java MP4 Parser

A Java API to read, write and create MP4 container. Manipulating containers is different from encoding and decoding videos and audio.

Using the library

The library is published to Maven repositories.

Gradle:

// 方式一:合并版
compile 'com.googlecode.mp4parser:isoparser:1.1.21'
// 方式二:单个版
compile 'org.mp4parser:streaming:1.9.27'
compile 'org.mp4parser:muxer:1.9.27'
compile 'org.mp4parser:isoparser:1.9.27'

或者下载isoparser-1.1.22.jar包,放入libs目录,dependencies添加compile files('libs/isoparser-1.1.22.jar')

What can you do?

Typical tasks for the MP4 Parser are:

  • Muxing audio/video into an MP4 file
  • Append recordings that use same encode settings
  • Adding/Changing metadata
  • Shorten recordings by omitting frames

Muxing Audio/Video

1. You wrap each raw format file into an appropriate Track object.

H264TrackImpl h264Track = new H264TrackImpl(new FileDataSourceImpl("video.h264"));
AACTrackImpl aacTrack = new AACTrackImpl(new FileDataSourceImpl("audio.aac"));

2. These Track object are then added to a Movie object.

Movie movie = new Movie();
movie.addTrack(h264Track);
movie.addTrack(aacTrack);

3. The Movie object is fed into an MP4Builder to create the container.

Container mp4file = new DefaultMp4Builder().build(movie);

4. Write the container to an appropriate sink.

FileChannel fc = new FileOutputStream(new File("output.mp4")).getChannel();
mp4file.writeContainer(fc);
fc.close();

Examples

1. 对Mp4文件进行追加合并

  /**
   * 对Mp4文件集合进行追加合并(按照顺序一个一个拼接起来)
   *
   * @param mp4PathList [输入]Mp4文件路径的集合(支持m4a)(不支持wav)
   * @param outPutPath  [输出]结果文件全部名称包含后缀(比如.mp4)
   * @throws IOException 格式不支持等情况抛出异常
   */
  public static void appendMp4List(List<String> mp4PathList, String outPutPath) throws IOException {
    List<Movie> mp4MovieList = new ArrayList<>();// Movie对象集合[输入]
    for (String mp4Path : mp4PathList) {// 将每个文件路径都构建成一个Movie对象
      mp4MovieList.add(MovieCreator.build(mp4Path));
    }

    List<Track> audioTracks = new LinkedList<>();// 音频通道集合
    List<Track> videoTracks = new LinkedList<>();// 视频通道集合

    for (Movie mp4Movie : mp4MovieList) {// 对Movie对象集合进行循环
      for (Track inMovieTrack : mp4Movie.getTracks()) {
        if ("soun".equals(inMovieTrack.getHandler())) {// 从Movie对象中取出音频通道
          audioTracks.add(inMovieTrack);
        }
        if ("vide".equals(inMovieTrack.getHandler())) {// 从Movie对象中取出视频通道
          videoTracks.add(inMovieTrack);
        }
      }
    }

    Movie resultMovie = new Movie();// 结果Movie对象[输出]
    if (!audioTracks.isEmpty()) {// 将所有音频通道追加合并
      resultMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
    }
    if (!videoTracks.isEmpty()) {// 将所有视频通道追加合并
      resultMovie.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()])));
    }

    Container outContainer = new DefaultMp4Builder().build(resultMovie);// 将结果Movie对象封装进容器
    FileChannel fileChannel = new RandomAccessFile(String.format(outPutPath), "rw").getChannel();
    outContainer.writeContainer(fileChannel);// 将容器内容写入磁盘
    fileChannel.close();
  }

调用工具来实现功能

/**
 * 执行MP4的追加合成
 */
private void doMp4Append() {
  try {
    List<String> mp4PathList = new ArrayList<>();
    mp4PathList.add(rootPath + "/resource/" + "video1" + ".mp4");
    mp4PathList.add(rootPath + "/resource/" + "video2" + ".mp4");
    String outPutPath = rootPath + "/output/" + "outVideo" + ".mp4";
    Mp4ParseUtil.appendMp4List(mp4PathList, outPutPath);
    LogUtil.e("合并追加完成");
  } catch (IOException e) {
    e.printStackTrace();
    LogUtil.e("异常");
  }
}

2. 对AAC文件集合进行追加合并

/**
 * 对AAC文件集合进行追加合并(按照顺序一个一个拼接起来)
 *
 * @param aacPathList [输入]AAC文件路径的集合(不支持wav)
 * @param outPutPath  [输出]结果文件全部名称包含后缀(比如.aac)
 * @throws IOException 格式不支持等情况抛出异常
 */
public static void appendAacList(List<String> aacPathList, String outPutPath) throws IOException {
  List<Track> audioTracks = new LinkedList<>();// 音频通道集合
  for (int i = 0; i < aacPathList.size(); i++) {// 将每个文件路径都构建成一个AACTrackImpl对象
    audioTracks.add(new AACTrackImpl(new FileDataSourceImpl(aacPathList.get(i))));
  }

  Movie resultMovie = new Movie();// 结果Movie对象[输出]
  if (!audioTracks.isEmpty()) {// 将所有音频通道追加合并
    resultMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
  }

  Container outContainer = new DefaultMp4Builder().build(resultMovie);// 将结果Movie对象封装进容器
  FileChannel fileChannel = new RandomAccessFile(String.format(outPutPath), "rw").getChannel();
  outContainer.writeContainer(fileChannel);// 将容器内容写入磁盘
  fileChannel.close();
}

3. 视频替换音轨

/**
 * 将 AAC 和 MP4 进行混合[替换了视频的音轨]
 *
 * @param aacPath .aac
 * @param mp4Path .mp4
 * @param outPath .mp4
 */
public static boolean muxAacMp4(String aacPath, String mp4Path, String outPath) {
  try {
    AACTrackImpl aacTrack = new AACTrackImpl(new FileDataSourceImpl(aacPath));
    Movie videoMovie = MovieCreator.build(mp4Path);
    Track videoTracks = null;// 获取视频的单纯视频部分
    for (Track videoMovieTrack : videoMovie.getTracks()) {
      if ("vide".equals(videoMovieTrack.getHandler())) {
        videoTracks = videoMovieTrack;
      }
    }

    Movie resultMovie = new Movie();
    resultMovie.addTrack(videoTracks);// 视频部分
    resultMovie.addTrack(aacTrack);// 音频部分

    Container out = new DefaultMp4Builder().build(resultMovie);
    FileOutputStream fos = new FileOutputStream(new File(outPath));
    out.writeContainer(fos.getChannel());
    fos.close();
  } catch (Exception e) {
    e.printStackTrace();
  }
  return true;
}

/**
 * 将 M4A 和 MP4 进行混合[替换了视频的音轨]
 *
 * @param m4aPath .m4a[同样可以使用.mp4]
 * @param mp4Path .mp4
 * @param outPath .mp4
 */
public static void muxM4AMp4(String m4aPath, String mp4Path, String outPath) throws IOException {
  Movie audioMovie = MovieCreator.build(m4aPath);
  Track audioTracks = null;// 获取视频的单纯音频部分
  for (Track audioMovieTrack : audioMovie.getTracks()) {
    if ("soun".equals(audioMovieTrack.getHandler())) {
      audioTracks = audioMovieTrack;
    }
  }

  Movie videoMovie = MovieCreator.build(mp4Path);
  Track videoTracks = null;// 获取视频的单纯视频部分
  for (Track videoMovieTrack : videoMovie.getTracks()) {
    if ("vide".equals(videoMovieTrack.getHandler())) {
      videoTracks = videoMovieTrack;
    }
  }

  Movie resultMovie = new Movie();
  resultMovie.addTrack(videoTracks);// 视频部分
  resultMovie.addTrack(audioTracks);// 音频部分

  Container out = new DefaultMp4Builder().build(resultMovie);
  FileOutputStream fos = new FileOutputStream(new File(outPath));
  out.writeContainer(fos.getChannel());
  fos.close();
}

4. 音视频分离

/**
 * 将 Mp4 的音频和视频分离
 *
 * @param mp4Path .mp4
 * @param outPath .mp4
 */
public static void splitMp4(String mp4Path, String outPath) throws IOException {
  Movie videoMovie = MovieCreator.build(mp4Path);
  Track videoTracks = null;// 获取视频的单纯视频部分
  for (Track videoMovieTrack : videoMovie.getTracks()) {
    if ("vide".equals(videoMovieTrack.getHandler())) {
      videoTracks = videoMovieTrack;
    }
  }

  Movie resultMovie = new Movie();
  resultMovie.addTrack(videoTracks);// 视频部分

  Container out = new DefaultMp4Builder().build(resultMovie);
  FileOutputStream fos = new FileOutputStream(new File(outPath));
  out.writeContainer(fos.getChannel());
  fos.close();
}

5. 添加字幕

/**
 * 对 Mp4 添加字幕
 *
 * @param mp4Path .mp4 添加字幕之前
 * @param outPath .mp4 添加字幕之后
 */
public static void addSubtitles(String mp4Path, String outPath) throws IOException {
  Movie videoMovie = MovieCreator.build(mp4Path);

  TextTrackImpl subTitleEng = new TextTrackImpl();// 实例化文本通道对象
  subTitleEng.getTrackMetaData().setLanguage("eng");// 设置元数据(语言)

  subTitleEng.getSubs().add(new TextTrackImpl.Line(0, 1000, "Five"));// 参数时间毫秒值
  subTitleEng.getSubs().add(new TextTrackImpl.Line(1000, 2000, "Four"));
  subTitleEng.getSubs().add(new TextTrackImpl.Line(2000, 3000, "Three"));
  subTitleEng.getSubs().add(new TextTrackImpl.Line(3000, 4000, "Two"));
  subTitleEng.getSubs().add(new TextTrackImpl.Line(4000, 5000, "one"));
  subTitleEng.getSubs().add(new TextTrackImpl.Line(5001, 5002, " "));// 省略去测试
  videoMovie.addTrack(subTitleEng);// 将字幕通道添加进视频Movie对象中

  Container out = new DefaultMp4Builder().build(videoMovie);
  FileOutputStream fos = new FileOutputStream(new File(outPath));
  out.writeContainer(fos.getChannel());
  fos.close();
}

这个是添加字幕的简单使用,测试中发现,字幕中传入的参数其实就是时间毫秒值,在5000之后的部分一直显示one,所以在之后就添加了空格。还有一个问题就是添加的第一个字幕总是显示不出来。

6. MP4的切割

/**
 * 将 MP4 切割
 *
 * @param mp4Path    .mp4
 * @param fromSample 起始位置
 * @param toSample   结束位置
 * @param outPath    .mp4
 */
public static void cropMp4(String mp4Path, long fromSample, long toSample, String outPath) throws IOException {
  Movie mp4Movie = MovieCreator.build(mp4Path);
  Track videoTracks = null;// 获取视频的单纯视频部分
  for (Track videoMovieTrack : mp4Movie.getTracks()) {
    if ("vide".equals(videoMovieTrack.getHandler())) {
      videoTracks = videoMovieTrack;
    }
  }
  Track audioTracks = null;// 获取视频的单纯音频部分
  for (Track audioMovieTrack : mp4Movie.getTracks()) {
    if ("soun".equals(audioMovieTrack.getHandler())) {
      audioTracks = audioMovieTrack;
    }
  }

  Movie resultMovie = new Movie();
  resultMovie.addTrack(new AppendTrack(new CroppedTrack(videoTracks, fromSample, toSample)));// 视频部分
  resultMovie.addTrack(new AppendTrack(new CroppedTrack(audioTracks, fromSample, toSample)));// 音频部分

  Container out = new DefaultMp4Builder().build(resultMovie);
  FileOutputStream fos = new FileOutputStream(new File(outPath));
  out.writeContainer(fos.getChannel());
  fos.close();
}

参数列表中的起始位置和结束位置并不是时间毫秒值。点击CroppedTrack查看源码注释并没有理解这两个参数的具体含义,根据语境貌似是在说起始和结束的帧。

发布了25 篇原创文章 · 获赞 5 · 访问量 6402

猜你喜欢

转载自blog.csdn.net/qq_35413770/article/details/104519691