Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.media;
     18 
     19 import android.media.MediaCodec.BufferInfo;
     20 import dalvik.system.CloseGuard;
     21 
     22 import java.io.FileDescriptor;
     23 import java.io.IOException;
     24 import java.io.RandomAccessFile;
     25 import java.nio.ByteBuffer;
     26 import java.util.Map;
     27 
     28 /**
     29  * MediaMuxer facilitates muxing elementary streams. Currently only supports an
     30  * mp4 file as the output and at most one audio and/or one video elementary
     31  * stream.
     32  * <p>
     33  * It is generally used like this:
     34  *
     35  * <pre>
     36  * MediaMuxer muxer = new MediaMuxer("temp.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4);
     37  * // More often, the MediaFormat will be retrieved from MediaCodec.getOutputFormat()
     38  * // or MediaExtractor.getTrackFormat().
     39  * MediaFormat audioFormat = new MediaFormat(...);
     40  * MediaFormat videoFormat = new MediaFormat(...);
     41  * int audioTrackIndex = muxer.addTrack(audioFormat);
     42  * int videoTrackIndex = muxer.addTrack(videoFormat);
     43  * ByteBuffer inputBuffer = ByteBuffer.allocate(bufferSize);
     44  * boolean finished = false;
     45  * BufferInfo bufferInfo = new BufferInfo();
     46  *
     47  * muxer.start();
     48  * while(!finished) {
     49  *   // getInputBuffer() will fill the inputBuffer with one frame of encoded
     50  *   // sample from either MediaCodec or MediaExtractor, set isAudioSample to
     51  *   // true when the sample is audio data, set up all the fields of bufferInfo,
     52  *   // and return true if there are no more samples.
     53  *   finished = getInputBuffer(inputBuffer, isAudioSample, bufferInfo);
     54  *   if (!finished) {
     55  *     int currentTrackIndex = isAudioSample ? audioTrackIndex : videoTrackIndex;
     56  *     muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo);
     57  *   }
     58  * };
     59  * muxer.stop();
     60  * muxer.release();
     61  * </pre>
     62  */
     63 
     64 final public class MediaMuxer {
     65 
     66     static {
     67         System.loadLibrary("media_jni");
     68     }
     69 
     70     /**
     71      * Defines the output format. These constants are used with constructor.
     72      */
     73     public static final class OutputFormat {
     74         /* Do not change these values without updating their counterparts
     75          * in include/media/stagefright/MediaMuxer.h!
     76          */
     77         private OutputFormat() {}
     78         /** MPEG4 media file format*/
     79         public static final int MUXER_OUTPUT_MPEG_4 = 0;
     80         public static final int MUXER_OUTPUT_WEBM   = 1;
     81     };
     82 
     83     // All the native functions are listed here.
     84     private static native long nativeSetup(FileDescriptor fd, int format);
     85     private static native void nativeRelease(long nativeObject);
     86     private static native void nativeStart(long nativeObject);
     87     private static native void nativeStop(long nativeObject);
     88     private static native int nativeAddTrack(long nativeObject, String[] keys,
     89             Object[] values);
     90     private static native void nativeSetOrientationHint(long nativeObject,
     91             int degrees);
     92     private static native void nativeSetLocation(long nativeObject, int latitude, int longitude);
     93     private static native void nativeWriteSampleData(long nativeObject,
     94             int trackIndex, ByteBuffer byteBuf,
     95             int offset, int size, long presentationTimeUs, int flags);
     96 
     97     // Muxer internal states.
     98     private static final int MUXER_STATE_UNINITIALIZED  = -1;
     99     private static final int MUXER_STATE_INITIALIZED    = 0;
    100     private static final int MUXER_STATE_STARTED        = 1;
    101     private static final int MUXER_STATE_STOPPED        = 2;
    102 
    103     private int mState = MUXER_STATE_UNINITIALIZED;
    104 
    105     private final CloseGuard mCloseGuard = CloseGuard.get();
    106     private int mLastTrackIndex = -1;
    107 
    108     private long mNativeObject;
    109 
    110     /**
    111      * Constructor.
    112      * Creates a media muxer that writes to the specified path.
    113      * @param path The path of the output media file.
    114      * @param format The format of the output media file.
    115      * @see android.media.MediaMuxer.OutputFormat
    116      * @throws IOException if failed to open the file for write
    117      */
    118     public MediaMuxer(String path, int format) throws IOException {
    119         if (path == null) {
    120             throw new IllegalArgumentException("path must not be null");
    121         }
    122         if (format != OutputFormat.MUXER_OUTPUT_MPEG_4 &&
    123                 format != OutputFormat.MUXER_OUTPUT_WEBM) {
    124             throw new IllegalArgumentException("format is invalid");
    125         }
    126         // Use RandomAccessFile so we can open the file with RW access;
    127         // RW access allows the native writer to memory map the output file.
    128         RandomAccessFile file = null;
    129         try {
    130             file = new RandomAccessFile(path, "rws");
    131             FileDescriptor fd = file.getFD();
    132             mNativeObject = nativeSetup(fd, format);
    133             mState = MUXER_STATE_INITIALIZED;
    134             mCloseGuard.open("release");
    135         } finally {
    136             if (file != null) {
    137                 file.close();
    138             }
    139         }
    140     }
    141 
    142     /**
    143      * Sets the orientation hint for output video playback.
    144      * <p>This method should be called before {@link #start}. Calling this
    145      * method will not rotate the video frame when muxer is generating the file,
    146      * but add a composition matrix containing the rotation angle in the output
    147      * video if the output format is
    148      * {@link OutputFormat#MUXER_OUTPUT_MPEG_4} so that a video player can
    149      * choose the proper orientation for playback. Note that some video players
    150      * may choose to ignore the composition matrix in a video during playback.
    151      * By default, the rotation degree is 0.</p>
    152      * @param degrees the angle to be rotated clockwise in degrees.
    153      * The supported angles are 0, 90, 180, and 270 degrees.
    154      */
    155     public void setOrientationHint(int degrees) {
    156         if (degrees != 0 && degrees != 90  && degrees != 180 && degrees != 270) {
    157             throw new IllegalArgumentException("Unsupported angle: " + degrees);
    158         }
    159         if (mState == MUXER_STATE_INITIALIZED) {
    160             nativeSetOrientationHint(mNativeObject, degrees);
    161         } else {
    162             throw new IllegalStateException("Can't set rotation degrees due" +
    163                     " to wrong state.");
    164         }
    165     }
    166 
    167     /**
    168      * Set and store the geodata (latitude and longitude) in the output file.
    169      * This method should be called before {@link #start}. The geodata is stored
    170      * in udta box if the output format is
    171      * {@link OutputFormat#MUXER_OUTPUT_MPEG_4}, and is ignored for other output
    172      * formats. The geodata is stored according to ISO-6709 standard.
    173      *
    174      * @param latitude Latitude in degrees. Its value must be in the range [-90,
    175      * 90].
    176      * @param longitude Longitude in degrees. Its value must be in the range
    177      * [-180, 180].
    178      * @throws IllegalArgumentException If the given latitude or longitude is out
    179      * of range.
    180      * @throws IllegalStateException If this method is called after {@link #start}.
    181      */
    182     public void setLocation(float latitude, float longitude) {
    183         int latitudex10000  = (int) (latitude * 10000 + 0.5);
    184         int longitudex10000 = (int) (longitude * 10000 + 0.5);
    185 
    186         if (latitudex10000 > 900000 || latitudex10000 < -900000) {
    187             String msg = "Latitude: " + latitude + " out of range.";
    188             throw new IllegalArgumentException(msg);
    189         }
    190         if (longitudex10000 > 1800000 || longitudex10000 < -1800000) {
    191             String msg = "Longitude: " + longitude + " out of range";
    192             throw new IllegalArgumentException(msg);
    193         }
    194 
    195         if (mState == MUXER_STATE_INITIALIZED && mNativeObject != 0) {
    196             nativeSetLocation(mNativeObject, latitudex10000, longitudex10000);
    197         } else {
    198             throw new IllegalStateException("Can't set location due to wrong state.");
    199         }
    200     }
    201 
    202     /**
    203      * Starts the muxer.
    204      * <p>Make sure this is called after {@link #addTrack} and before
    205      * {@link #writeSampleData}.</p>
    206      */
    207     public void start() {
    208         if (mNativeObject == 0) {
    209             throw new IllegalStateException("Muxer has been released!");
    210         }
    211         if (mState == MUXER_STATE_INITIALIZED) {
    212             nativeStart(mNativeObject);
    213             mState = MUXER_STATE_STARTED;
    214         } else {
    215             throw new IllegalStateException("Can't start due to wrong state.");
    216         }
    217     }
    218 
    219     /**
    220      * Stops the muxer.
    221      * <p>Once the muxer stops, it can not be restarted.</p>
    222      */
    223     public void stop() {
    224         if (mState == MUXER_STATE_STARTED) {
    225             nativeStop(mNativeObject);
    226             mState = MUXER_STATE_STOPPED;
    227         } else {
    228             throw new IllegalStateException("Can't stop due to wrong state.");
    229         }
    230     }
    231 
    232     @Override
    233     protected void finalize() throws Throwable {
    234         try {
    235             if (mCloseGuard != null) {
    236                 mCloseGuard.warnIfOpen();
    237             }
    238             if (mNativeObject != 0) {
    239                 nativeRelease(mNativeObject);
    240                 mNativeObject = 0;
    241             }
    242         } finally {
    243             super.finalize();
    244         }
    245     }
    246 
    247     /**
    248      * Adds a track with the specified format.
    249      * @param format The media format for the track.
    250      * @return The track index for this newly added track, and it should be used
    251      * in the {@link #writeSampleData}.
    252      */
    253     public int addTrack(MediaFormat format) {
    254         if (format == null) {
    255             throw new IllegalArgumentException("format must not be null.");
    256         }
    257         if (mState != MUXER_STATE_INITIALIZED) {
    258             throw new IllegalStateException("Muxer is not initialized.");
    259         }
    260         if (mNativeObject == 0) {
    261             throw new IllegalStateException("Muxer has been released!");
    262         }
    263         int trackIndex = -1;
    264         // Convert the MediaFormat into key-value pairs and send to the native.
    265         Map<String, Object> formatMap = format.getMap();
    266 
    267         String[] keys = null;
    268         Object[] values = null;
    269         int mapSize = formatMap.size();
    270         if (mapSize > 0) {
    271             keys = new String[mapSize];
    272             values = new Object[mapSize];
    273             int i = 0;
    274             for (Map.Entry<String, Object> entry : formatMap.entrySet()) {
    275                 keys[i] = entry.getKey();
    276                 values[i] = entry.getValue();
    277                 ++i;
    278             }
    279             trackIndex = nativeAddTrack(mNativeObject, keys, values);
    280         } else {
    281             throw new IllegalArgumentException("format must not be empty.");
    282         }
    283 
    284         // Track index number is expected to incremented as addTrack succeed.
    285         // However, if format is invalid, it will get a negative trackIndex.
    286         if (mLastTrackIndex >= trackIndex) {
    287             throw new IllegalArgumentException("Invalid format.");
    288         }
    289         mLastTrackIndex = trackIndex;
    290         return trackIndex;
    291     }
    292 
    293     /**
    294      * Writes an encoded sample into the muxer.
    295      * <p>The application needs to make sure that the samples are written into
    296      * the right tracks. Also, it needs to make sure the samples for each track
    297      * are written in chronological order (e.g. in the order they are provided
    298      * by the encoder.)</p>
    299      * @param byteBuf The encoded sample.
    300      * @param trackIndex The track index for this sample.
    301      * @param bufferInfo The buffer information related to this sample.
    302      * MediaMuxer uses the flags provided in {@link MediaCodec.BufferInfo},
    303      * to signal sync frames.
    304      */
    305     public void writeSampleData(int trackIndex, ByteBuffer byteBuf,
    306             BufferInfo bufferInfo) {
    307         if (trackIndex < 0 || trackIndex > mLastTrackIndex) {
    308             throw new IllegalArgumentException("trackIndex is invalid");
    309         }
    310 
    311         if (byteBuf == null) {
    312             throw new IllegalArgumentException("byteBuffer must not be null");
    313         }
    314 
    315         if (bufferInfo == null) {
    316             throw new IllegalArgumentException("bufferInfo must not be null");
    317         }
    318         if (bufferInfo.size < 0 || bufferInfo.offset < 0
    319                 || (bufferInfo.offset + bufferInfo.size) > byteBuf.capacity()
    320                 || bufferInfo.presentationTimeUs < 0) {
    321             throw new IllegalArgumentException("bufferInfo must specify a" +
    322                     " valid buffer offset, size and presentation time");
    323         }
    324 
    325         if (mNativeObject == 0) {
    326             throw new IllegalStateException("Muxer has been released!");
    327         }
    328 
    329         if (mState != MUXER_STATE_STARTED) {
    330             throw new IllegalStateException("Can't write, muxer is not started");
    331         }
    332 
    333         nativeWriteSampleData(mNativeObject, trackIndex, byteBuf,
    334                 bufferInfo.offset, bufferInfo.size,
    335                 bufferInfo.presentationTimeUs, bufferInfo.flags);
    336     }
    337 
    338     /**
    339      * Make sure you call this when you're done to free up any resources
    340      * instead of relying on the garbage collector to do this for you at
    341      * some point in the future.
    342      */
    343     public void release() {
    344         if (mState == MUXER_STATE_STARTED) {
    345             stop();
    346         }
    347         if (mNativeObject != 0) {
    348             nativeRelease(mNativeObject);
    349             mNativeObject = 0;
    350             mCloseGuard.close();
    351         }
    352         mState = MUXER_STATE_UNINITIALIZED;
    353     }
    354 }
    355