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