使用 Android MediaRecorder 暂停和恢复(API 级别 < 24)
Pause & Resume with Android MediaRecorder (API level < 24)
在使用 MediaRecorder
时,我们没有 pause/resume 级别低于 24 的 API。
所以有一种方法可以做到这一点:
- 在暂停事件中停止记录器并创建记录文件。
- 并且在恢复时再次开始录制并创建另一个文件并继续这样做直到用户按下停止。
- 最后合并所有文件。
很多人在SO上问过这个问题,但无论如何都找不到解决办法。人们谈论通过在暂停操作时停止录制并在恢复时重新启动来创建多个媒体文件。所以我的问题是我们如何以编程方式 merge/join 所有媒体文件?
注意: 在我的例子中是 MPEG4 容器 - m4a 用于音频,mp4 用于视频。
我尝试使用SequenceInputStream
合并各个生成的记录文件的多个InputStream。但它总是只产生第一个文件。
代码段:
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();
我可以使用 mp4parser 库解决这个问题。非常感谢这个库的作者 :)
在您的 gradle 文件中添加以下依赖项:
compile 'com.googlecode.mp4parser:isoparser:1.0.2'
解决方案是在用户暂停时停止记录器并在恢复时重新开始,正如 Whosebug 中许多其他答案中已经提到的那样。将生成的所有 audio/video 文件存储在一个数组中,并使用以下方法合并所有媒体文件。该示例也取自 mp4parser 库,并根据我的需要进行了一些修改。
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;
}
}
将标志 isAudio 设置为音频文件的 true 和视频文件的 false。
另一种解决方案正在与 FFmpeg
合并
将此行添加到您的应用程序 build.gradle
implementation 'com.writingminds:FFmpegAndroid:0.3.2'
并使用下面的代码合并视频。
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
}
}
运行合并前的这段代码
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
}
}
在使用 MediaRecorder
时,我们没有 pause/resume 级别低于 24 的 API。
所以有一种方法可以做到这一点:
- 在暂停事件中停止记录器并创建记录文件。
- 并且在恢复时再次开始录制并创建另一个文件并继续这样做直到用户按下停止。
- 最后合并所有文件。
很多人在SO上问过这个问题,但无论如何都找不到解决办法。人们谈论通过在暂停操作时停止录制并在恢复时重新启动来创建多个媒体文件。所以我的问题是我们如何以编程方式 merge/join 所有媒体文件?
注意: 在我的例子中是 MPEG4 容器 - m4a 用于音频,mp4 用于视频。
我尝试使用SequenceInputStream
合并各个生成的记录文件的多个InputStream。但它总是只产生第一个文件。
代码段:
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();
我可以使用 mp4parser 库解决这个问题。非常感谢这个库的作者 :)
在您的 gradle 文件中添加以下依赖项:
compile 'com.googlecode.mp4parser:isoparser:1.0.2'
解决方案是在用户暂停时停止记录器并在恢复时重新开始,正如 Whosebug 中许多其他答案中已经提到的那样。将生成的所有 audio/video 文件存储在一个数组中,并使用以下方法合并所有媒体文件。该示例也取自 mp4parser 库,并根据我的需要进行了一些修改。
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;
}
}
将标志 isAudio 设置为音频文件的 true 和视频文件的 false。
另一种解决方案正在与 FFmpeg
合并将此行添加到您的应用程序 build.gradle
implementation 'com.writingminds:FFmpegAndroid:0.3.2'
并使用下面的代码合并视频。
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
}
}
运行合并前的这段代码
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
}
}