How to combine multiple mp4 videos into one video using jcodec in Java?

1.7k views Asked by At

I have multiple mp4 files which are parts of a whole mp4 file. They have been just split up to smaller files.

I want to combine those files programmatically into one mp4 file using jcodec in Java. Currently, I'm using the following code to do this:

import org.jcodec.common.DemuxerTrack;
import org.jcodec.common.MuxerTrack;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.model.Packet;
import org.jcodec.containers.mp4.Brand;
import org.jcodec.containers.mp4.demuxer.MP4Demuxer;
import org.jcodec.containers.mp4.muxer.MP4Muxer;

import java.io.File;
import java.util.List;

import static org.jcodec.common.io.NIOUtils.readableChannel;
import static org.jcodec.common.io.NIOUtils.writableChannel;

public class Main {

    public static void main(String[] args) throws Exception {
        File outputFile = new File("file_path_output.mp4");
        outputFile.mkdirs();
        outputFile.delete();

        File[] inputFiles = new File[]{
                new File("file_path_1.mp4"),
                new File("file_path_2.mp4"),
                new File("file_path_3.mp4"),
        };

        SeekableByteChannel output = writableChannel(outputFile);
        MP4Muxer mp4Muxer = MP4Muxer.createMP4Muxer(output, Brand.MP4);

        MuxerTrack mixedVideoTrack = null;
        MuxerTrack mixedAudioTrack = null;
        for (File input : inputFiles) {
            SeekableByteChannel byteChannel = readableChannel(input);
            MP4Demuxer demuxer = MP4Demuxer.createMP4Demuxer(byteChannel);
            List<DemuxerTrack> videoTracks = demuxer.getVideoTracks();
            List<DemuxerTrack> audioTracks = demuxer.getAudioTracks();
            if (mixedVideoTrack == null) {
                mixedVideoTrack = mp4Muxer.addVideoTrack(videoTracks.get(0).getMeta().getCodec(), videoTracks.get(0).getMeta().getVideoCodecMeta());
            }
            if (mixedAudioTrack == null) {
                mixedAudioTrack = mp4Muxer.addAudioTrack(audioTracks.get(0).getMeta().getCodec(), audioTracks.get(0).getMeta().getAudioCodecMeta());
            }
            for (DemuxerTrack videoTrack : videoTracks) {
                Packet packet;
                while ((packet = videoTrack.nextFrame()) != null) {
                    mixedVideoTrack.addFrame(packet);
                }
            }
            for (DemuxerTrack audioTrack : audioTracks) {
                Packet packet;
                while ((packet = audioTrack.nextFrame()) != null) {
                    mixedAudioTrack.addFrame(packet);
                }
            }
            demuxer.close();
        }

        mp4Muxer.finish();
        output.close();
    }
}

Unfortunately, it does not work as the video generated does not contain audio files nor correct frames. For example, all visual frames I get are like one below:

enter image description here

What is the problem and how can I fix it? Thank you very much.

2

There are 2 answers

2
dani-vta On

I don't really know jcodec, but I've used MP4parser in the past. If you need to merge mp4 files, regardless of the library, then I suggest you this one. I'm just pitching it to you because I don't know if you're having problems in merging the files due to the complexity of the library. It might be worth giving it a shot. MP4Parser is quite easy and straightforward. I'll also leave you the link to the maven repository.

https://search.maven.org/artifact/com.googlecode.mp4parser/isoparser/1.1.22/jar

Here there's an implementation I've done to show you how to merge 2 files from the same original video (to replicate your exact case). Basically, you have a Movie class, which represents your mp4 file, containing a List of Track (only 2); one representing the video while the other the audio.

  1. First, I've collected from the movies all the video and audio tracks in two separate arrays.

  2. Then, I've created a List with the merged audio Track and the merged video Track in order to set it to the output file.

  3. Finally, I've built the Movie inside a Container and saved it with a FileOutputStream.

public class Main {

    public static void main(String[] args) throws IOException {
        MovieCreator mc = new MovieCreator();
        Movie movie1 = mc.build("./test1.mp4");
        Movie movie2 = mc.build("./test2.mp4");

        //Fetching the video tracks from the movies and storing them into an array
        Track[] vetTrackVideo = new Track[0];
        vetTrackVideo = Stream.of(movie1, movie2)
                .flatMap(movie -> movie.getTracks().stream())
                .filter(movie -> movie.getHandler().equals("vide"))
                .collect(Collectors.toList())
                .toArray(vetTrackVideo);

        //Fetching the audio tracks from the movies and storing them into an array
        Track[] vetTrackAudio = new Track[0];
        vetTrackAudio = Stream.of(movie1, movie2)
                .flatMap(movie -> movie.getTracks().stream())
                .filter(movie -> movie.getHandler().equals("soun"))
                .collect(Collectors.toList())
                .toArray(vetTrackAudio);

        //Creating the output movie by setting a list with both video and audio tracks
        Movie movieOutput = new Movie();
        List<Track> listTracks = new ArrayList<>(List.of(new AppendTrack(vetTrackVideo), new AppendTrack(vetTrackAudio)));
        movieOutput.setTracks(listTracks);

        //Building the output movie and storing it into a Container
        DefaultMp4Builder mp4Builder = new DefaultMp4Builder();
        Container c = mp4Builder.build(movieOutput);

        //Writing the output file
        FileOutputStream fos = new FileOutputStream("output.mp4");
        c.writeContainer(fos.getChannel());
        fos.close();
    }
}

Here, I'll also leave you a link to my GitHub repository with the test mp4 files so that you can test the merging sample.

https://github.com/daniele-aveta/StackOverflow-MP4Merger

0
ΓDΛ On

You can do this with ffmpeg.

  1. First you will need to write the file paths to a txt file

txt file with :

  • file '/sdcard/video/video1.mp4'
  • file '/sdcard/video/video2.mp4'
  • file '/sdcard/video/video3.mp4'
  • file '/sdcard/video/video4.mp4'

You can use following command.

ffmpeg -f concat -i mp4list.txt -c:v copy -c:a copy merging.mp4
  1. You can write a wrapper over the Java code. Additionally, you can use the code below.

Resource to help : link

String[] cmd = new String[]{"-f", "concat", "-safe", "0", "-i", "mp4list.txt", "-c", "copy", "-preset", "ultrafast", outFile};

ffmpeg.execute(cmd, new ExecuteBinaryResponseHandler(){
    
        @Override
        public void onFailure(String message){
        }
        @Override
        public void onSuccess(String message){
        }
        @Override
        public void onProgress(String message){
        }
    
        @Override
        public void onStart(){
        }
    
        @Override
        public void onFinish(){
        }
    }