MediaMuxer.java revision 1797dc93fb203762eecbc9c026b77263e4e4a4f5
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;
20import dalvik.system.CloseGuard;
21
22import java.io.FileDescriptor;
23import java.io.IOException;
24import java.io.RandomAccessFile;
25import java.nio.ByteBuffer;
26import 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
64final 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