From e176ee1222931d67aaea0a3df96262728cc31031 Mon Sep 17 00:00:00 2001 From: Hangyu Kuang Date: Tue, 3 Jan 2017 17:25:50 -0800 Subject: [PATCH] media: Add new API to set next output file. Only support recording to MP4 file now. When the recorded file is approcaching file size limit, application will receive MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING callback from mediarecorder. Application could use setNextOutputFile to set the next output file before or after receiving this callback. Upon reaching filesize limit, recorder will swap the output file internally and notify application with MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED. Test: Hack camera app to record more than filesize limit. Bug: 28150626 Change-Id: I2daf8f798fe3631d6b7ef48ebea3a64ab4566f2d --- api/current.txt | 4 ++ api/system-current.txt | 4 ++ api/test-current.txt | 4 ++ media/java/android/media/MediaRecorder.java | 76 +++++++++++++++++++-- media/jni/android_media_MediaRecorder.cpp | 25 ++++++- 5 files changed, 106 insertions(+), 7 deletions(-) diff --git a/api/current.txt b/api/current.txt index b017f9d2f5b94..4da92f52314e4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -21784,6 +21784,8 @@ package android.media { method public void setLocation(float, float); method public void setMaxDuration(int) throws java.lang.IllegalArgumentException; method public void setMaxFileSize(long) throws java.lang.IllegalArgumentException; + method public void setNextOutputFile(java.io.FileDescriptor) throws java.io.IOException, java.lang.IllegalStateException; + method public void setNextOutputFile(java.lang.String) throws java.io.IOException, java.lang.IllegalStateException; method public void setOnErrorListener(android.media.MediaRecorder.OnErrorListener); method public void setOnInfoListener(android.media.MediaRecorder.OnInfoListener); method public void setOrientationHint(int); @@ -21802,7 +21804,9 @@ package android.media { field public static final int MEDIA_ERROR_SERVER_DIED = 100; // 0x64 field public static final int MEDIA_RECORDER_ERROR_UNKNOWN = 1; // 0x1 field public static final int MEDIA_RECORDER_INFO_MAX_DURATION_REACHED = 800; // 0x320 + field public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING = 802; // 0x322 field public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED = 801; // 0x321 + field public static final int MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED = 803; // 0x323 field public static final int MEDIA_RECORDER_INFO_UNKNOWN = 1; // 0x1 } diff --git a/api/system-current.txt b/api/system-current.txt index a5534deebd93e..067fcf961b43b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -23322,6 +23322,8 @@ package android.media { method public void setLocation(float, float); method public void setMaxDuration(int) throws java.lang.IllegalArgumentException; method public void setMaxFileSize(long) throws java.lang.IllegalArgumentException; + method public void setNextOutputFile(java.io.FileDescriptor) throws java.io.IOException, java.lang.IllegalStateException; + method public void setNextOutputFile(java.lang.String) throws java.io.IOException, java.lang.IllegalStateException; method public void setOnErrorListener(android.media.MediaRecorder.OnErrorListener); method public void setOnInfoListener(android.media.MediaRecorder.OnInfoListener); method public void setOrientationHint(int); @@ -23340,7 +23342,9 @@ package android.media { field public static final int MEDIA_ERROR_SERVER_DIED = 100; // 0x64 field public static final int MEDIA_RECORDER_ERROR_UNKNOWN = 1; // 0x1 field public static final int MEDIA_RECORDER_INFO_MAX_DURATION_REACHED = 800; // 0x320 + field public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING = 802; // 0x322 field public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED = 801; // 0x321 + field public static final int MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED = 803; // 0x323 field public static final int MEDIA_RECORDER_INFO_UNKNOWN = 1; // 0x1 } diff --git a/api/test-current.txt b/api/test-current.txt index 5db134a289646..0817a02f48597 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -21871,6 +21871,8 @@ package android.media { method public void setLocation(float, float); method public void setMaxDuration(int) throws java.lang.IllegalArgumentException; method public void setMaxFileSize(long) throws java.lang.IllegalArgumentException; + method public void setNextOutputFile(java.io.FileDescriptor) throws java.io.IOException, java.lang.IllegalStateException; + method public void setNextOutputFile(java.lang.String) throws java.io.IOException, java.lang.IllegalStateException; method public void setOnErrorListener(android.media.MediaRecorder.OnErrorListener); method public void setOnInfoListener(android.media.MediaRecorder.OnInfoListener); method public void setOrientationHint(int); @@ -21889,7 +21891,9 @@ package android.media { field public static final int MEDIA_ERROR_SERVER_DIED = 100; // 0x64 field public static final int MEDIA_RECORDER_ERROR_UNKNOWN = 1; // 0x1 field public static final int MEDIA_RECORDER_INFO_MAX_DURATION_REACHED = 800; // 0x320 + field public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING = 802; // 0x322 field public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED = 801; // 0x321 + field public static final int MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED = 803; // 0x323 field public static final int MEDIA_RECORDER_INFO_UNKNOWN = 1; // 0x1 } diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 64576eca25169..e62dfaab26b5d 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -800,6 +800,28 @@ public class MediaRecorder mFd = fd; } + /** + * Sets the next output file descriptor to be used when the maximum filesize is reached + * on the prior output {@link #setOutputFile} or {@link #setNextOutputFile}). File descriptor + * must be seekable and in read-write mode. After setting the next output file, application + * should not use the file referenced by this file descriptor until {@link #stop}. Application + * must call this after receiving on the {@link android.media.MediaRecorder.OnInfoListener} a + * "what" code of {@link #MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING} and before receiving + * a "what" code of {@link #MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED}. The file is not used + * until switching to that output. Application will receive + * {@link #MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED} when the next output file is used. + * Application will not be able to set a new output file if the previous one has not been used. + * Application is responsible for cleaning up unused files after {@link #stop} is called. + * + * @param fd an open file descriptor to be written into. + * @throws IllegalStateException if it is called before prepare(). + * @throws IOException if setNextOutputFile fails otherwise. + */ + public void setNextOutputFile(FileDescriptor fd) throws IllegalStateException, IOException + { + _setNextOutputFile(fd); + } + /** * Sets the path of the output file to be produced. Call this after * setOutputFormat() but before prepare(). @@ -814,9 +836,38 @@ public class MediaRecorder mPath = path; } + /** + * Sets the next output file path to be used when the maximum filesize is reached + * on the prior output {@link #setOutputFile} or {@link #setNextOutputFile}). File should + * be seekable. After setting the next output file, application should not use the file + * referenced by this file descriptor until {@link #stop}. Application must call this + * after receiving on the {@link android.media.MediaRecorder.OnInfoListener} a "what" code of + * {@link #MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING} and before receiving a "what" code of + * {@link #MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED}. The file is not used until switching to + * that output. Application will receive {@link #MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED} + * when the next output file is used. Application will not be able to set a new output file if + * the previous one has not been used. Application is responsible for cleaning up unused files + * after {@link #stop} is called. + * + * @param path The pathname to use. + * @throws IllegalStateException if it is called before prepare(). + * @throws IOException if setNextOutputFile fails otherwise. + */ + public void setNextOutputFile(String path) throws IllegalStateException, IOException + { + if (path != null) { + RandomAccessFile file = new RandomAccessFile(path, "rws"); + try { + _setNextOutputFile(file.getFD()); + } finally { + file.close(); + } + } + } + // native implementation - private native void _setOutputFile(FileDescriptor fd, long offset, long length) - throws IllegalStateException, IOException; + private native void _setOutputFile(FileDescriptor fd) throws IllegalStateException, IOException; + private native void _setNextOutputFile(FileDescriptor fd) throws IllegalStateException, IOException; private native void _prepare() throws IllegalStateException, IOException; /** @@ -833,12 +884,12 @@ public class MediaRecorder if (mPath != null) { RandomAccessFile file = new RandomAccessFile(mPath, "rws"); try { - _setOutputFile(file.getFD(), 0, 0); + _setOutputFile(file.getFD()); } finally { file.close(); } } else if (mFd != null) { - _setOutputFile(mFd, 0, 0); + _setOutputFile(mFd); } else { throw new IOException("No valid output file"); } @@ -980,9 +1031,26 @@ public class MediaRecorder */ public static final int MEDIA_RECORDER_INFO_MAX_DURATION_REACHED = 800; /** A maximum filesize had been setup and has now been reached. + * Note: This event will not be sent if application already set + * next output file through {@link #setNextOutputFile}. * @see android.media.MediaRecorder.OnInfoListener */ public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED = 801; + /** A maximum filesize had been setup and current recorded file size + * has reached 90% of the limit. This is sent once per file upon + * reaching/passing the 90% limit. To continue the recording, applicaiton + * should use {@link #setNextOutputFile} to set the next output file. + * Otherwise, recording will stop when reaching maximum file size. + * @see android.media.MediaRecorder.OnInfoListener + */ + public static final int MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING = 802; + /** A maximum filesize had been reached and MediaRecorder has switched + * output to a new file set by application {@link #setNextOutputFile}. + * For best practice, application should use this event to keep track + * of whether the file previously set has been used or not. + * @see android.media.MediaRecorder.OnInfoListener + */ + public static final int MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED = 803; /** informational events for individual tracks, for testing purpose. * The track informational event usually contains two parts in the ext1 diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 6c79ab790a3de..7c509d297dbfc 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -290,7 +290,7 @@ android_media_MediaRecorder_setParameter(JNIEnv *env, jobject thiz, jstring para } static void -android_media_MediaRecorder_setOutputFileFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length) +android_media_MediaRecorder_setOutputFileFD(JNIEnv *env, jobject thiz, jobject fileDescriptor) { ALOGV("setOutputFile"); if (fileDescriptor == NULL) { @@ -303,7 +303,25 @@ android_media_MediaRecorder_setOutputFileFD(JNIEnv *env, jobject thiz, jobject f jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } - status_t opStatus = mr->setOutputFile(fd, offset, length); + status_t opStatus = mr->setOutputFile(fd); + process_media_recorder_call(env, opStatus, "java/io/IOException", "setOutputFile failed."); +} + +static void +android_media_MediaRecorder_setNextOutputFileFD(JNIEnv *env, jobject thiz, jobject fileDescriptor) +{ + ALOGV("setNextOutputFile"); + if (fileDescriptor == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); + sp mr = getMediaRecorder(env, thiz); + if (mr == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + status_t opStatus = mr->setNextOutputFile(fd); process_media_recorder_call(env, opStatus, "java/io/IOException", "setOutputFile failed."); } @@ -617,7 +635,8 @@ static const JNINativeMethod gMethods[] = { {"setVideoEncoder", "(I)V", (void *)android_media_MediaRecorder_setVideoEncoder}, {"setAudioEncoder", "(I)V", (void *)android_media_MediaRecorder_setAudioEncoder}, {"setParameter", "(Ljava/lang/String;)V", (void *)android_media_MediaRecorder_setParameter}, - {"_setOutputFile", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaRecorder_setOutputFileFD}, + {"_setOutputFile", "(Ljava/io/FileDescriptor;)V", (void *)android_media_MediaRecorder_setOutputFileFD}, + {"_setNextOutputFile", "(Ljava/io/FileDescriptor;)V", (void *)android_media_MediaRecorder_setNextOutputFileFD}, {"setVideoSize", "(II)V", (void *)android_media_MediaRecorder_setVideoSize}, {"setVideoFrameRate", "(I)V", (void *)android_media_MediaRecorder_setVideoFrameRate}, {"setMaxDuration", "(I)V", (void *)android_media_MediaRecorder_setMaxDuration},