7

While using MediaRecorder, we don't have pause/resume for API level below 24. So there can be a way to do this is:

  1. On pause event stop the recorder and create the recorded file.
  2. And on resume start recording again and create another file and keep doing so until user presses stop.
  3. And at last merge all files.

Many people asked this question on SO, but couldn't find anyway to solve this. People talk about creating multiple media files by stopping recording on pause action and restarting on resume. So my question is How can we merge/join all media file programmatically?

Note: in my case MPEG4 container - m4a for audio and mp4 for video.

I tried using SequenceInputStream to merge multiple InputStream of respective generated recorded files. But it always results the first file only.

Code Snippet:

Enumeration<InputStream> enu = Collections.enumeration(inputStreams);
        SequenceInputStream sqStream = new SequenceInputStream(enu);
        while ((oneByte = sqStream.read(buffer)) != -1) {
            fileOutputStream.write(buffer, 0, oneByte);

        }
        sqStream.close();
        while (enu.hasMoreElements()) {
            InputStream element = enu.nextElement();
            element.close();
        }
        fileOutputStream.flush();
        fileOutputStream.close();
Nitin Mathur
  • 501
  • 5
  • 16
  • Possible duplicate of [Pause and Resume Audio recording in Android](https://stackoverflow.com/questions/5743165/pause-and-resume-audio-recording-in-android) – soshial Oct 12 '17 at 08:32

2 Answers2

13

I could solve this problem using mp4parser library. Thanks much to author of this library :)

Add below dependency in your gradle file:

compile 'com.googlecode.mp4parser:isoparser:1.0.2'

The solution is to stop recorder when user pause and start again on resume as already mentioned in many other answers in stackoverflow. Store all the audio/video files generated in an array and use below method to merge all media files. The example is also taken from mp4parser library and modified little bit as per my need.

public static boolean mergeMediaFiles(boolean isAudio, String sourceFiles[], String targetFile) {
        try {
            String mediaKey = isAudio ? "soun" : "vide";
            List<Movie> listMovies = new ArrayList<>();
            for (String filename : sourceFiles) {
                listMovies.add(MovieCreator.build(filename));
            }
            List<Track> listTracks = new LinkedList<>();
            for (Movie movie : listMovies) {
                for (Track track : movie.getTracks()) {
                    if (track.getHandler().equals(mediaKey)) {
                        listTracks.add(track);
                    }
                }
            }
            Movie outputMovie = new Movie();
            if (!listTracks.isEmpty()) {
                outputMovie.addTrack(new AppendTrack(listTracks.toArray(new Track[listTracks.size()])));
            }
            Container container = new DefaultMp4Builder().build(outputMovie);
            FileChannel fileChannel = new RandomAccessFile(String.format(targetFile), "rw").getChannel();
            container.writeContainer(fileChannel);
            fileChannel.close();
            return true;
        }
        catch (IOException e) {
            Log.e(LOG_TAG, "Error merging media files. exception: "+e.getMessage());
            return false;
        }
    }

Use flag isAudio as true for Audio files and false for Video files.

Nitin Mathur
  • 501
  • 5
  • 16
0

Another solution is merging with FFmpeg

Add this line to your app build.gradle

implementation 'com.writingminds:FFmpegAndroid:0.3.2'

And use below code to merge videos.

String textFile = "";
try {
    textFile = getTextFile().getAbsolutePath();
} catch (IOException e) {
    e.printStackTrace();
}
String[] cmd = new String[]{
        "-y",
        "-f",
        "concat",
        "-safe",
        "0",
        "-i",
        textFile,
        "-c",
        "copy",
        "-preset",
        "ultrafast",
        getVideoFilePath()};

mergeVideos(cmd);

getTextFile()

 private File getTextFile() throws IOException {
        videoFiles = new String[]{firstPath, secondPath, thirdPatch};
        File file = new File(getActivity().getExternalFilesDir(null), System.currentTimeMillis() + "inputFiles.txt");
        FileOutputStream out = new FileOutputStream(file, false);
        PrintWriter writer = new PrintWriter(out);
        StringBuilder builder = new StringBuilder();
        for (String path : videoFiles) {
            if (path != null) {
            builder.append("file ");
            builder.append("\'");
            builder.append(path);
            builder.append("\'\n");
            }
        }
        builder.deleteCharAt(builder.length() - 1);
        String text = builder.toString();
        writer.print(text);
        writer.close();
        out.close();
        return file;
    }

getVideoFilePath()

 private String getVideoFilePath() {
        final File dir = getActivity().getExternalFilesDir(null);
        return (dir == null ? "" : (dir.getAbsolutePath() + "/"))
                + System.currentTimeMillis() + ".mp4";
    }

mergeVideos()

private void mergeVideos(String[] cmd) {
        FFmpeg ffmpeg = FFmpeg.getInstance(getActivity());
        try {
            ffmpeg.execute(cmd, new ExecuteBinaryResponseHandler() {

                @Override
                public void onStart() {
                    startTime = System.currentTimeMillis();
                }

                @Override
                public void onProgress(String message) {

                }

                @Override
                public void onFailure(String message) {
                    Toast.makeText(getActivity(), "Failed " + message, Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onSuccess(String message) {

                }

                @Override
                public void onFinish() {
                    Toast.makeText(getActivity(), "Videos are merged", Toast.LENGTH_SHORT).show();
                }
            });
        } catch (FFmpegCommandAlreadyRunningException e) {
            // Handle if FFmpeg is already running
        }
    }

Run this code before merging

private void checkFfmpegSupport() {
        FFmpeg ffmpeg = FFmpeg.getInstance(this);
        try {
            ffmpeg.loadBinary(new LoadBinaryResponseHandler() {

                @Override
                public void onStart() {

                }

                @Override
                public void onFailure() {
                    Toast.makeText(VouchActivity.this, "FFmpeg not supported on this device :(", Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onSuccess() {

                }

                @Override
                public void onFinish() {

                }
            });
        } catch (FFmpegNotSupportedException e) {
            // Handle if FFmpeg is not supported by device
        }
    }
Levon Petrosyan
  • 5,558
  • 7
  • 40
  • 53