MediaMuxer.java revision effc9b4839f3cc109fe3d8244022f3c898cd080b
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(...);
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(...);
44 * muxer.start();
45 * while(inputBuffer has new data) {
46 *   if (new data is audio sample) {
47 *     muxer.writeSampleData(audioTrackIndex, inputBuffer, ...);
48 *   } else if (new data is video sample) {
49 *     muxer.writeSampleData(videoTrackIndex, inputBuffer, ...);
50 *   }
51 * }
52 * muxer.stop();
53 * muxer.release();
54 * </pre>
55 */
56
57final public class MediaMuxer {
58
59    private int mNativeContext;
60
61    static {
62        System.loadLibrary("media_jni");
63    }
64
65    /**
66     * Defines the output format. These constants are used with constructor.
67     */
68    public static final class OutputFormat {
69        /* Do not change these values without updating their counterparts
70         * in include/media/stagefright/MediaMuxer.h!
71         */
72        private OutputFormat() {}
73        /** MPEG4 media file format*/
74        public static final int MUXER_OUTPUT_MPEG_4 = 0;
75    };
76
77    /**
78     * The sample is a sync sample, which does not require other video samples
79     * to decode. This flag is used in {@link #writeSampleData} to indicate
80     * which sample is a sync sample.
81     */
82    /* Keep this flag in sync with its equivalent in
83     * include/media/stagefright/MediaMuxer.h.
84     */
85    public static final int SAMPLE_FLAG_SYNC = 1;
86
87    // All the native functions are listed here.
88    private static native int nativeSetup(FileDescriptor fd, int format);
89    private static native void nativeRelease(int nativeObject);
90    private static native void nativeStart(int nativeObject);
91    private static native void nativeStop(int nativeObject);
92    private static native int nativeAddTrack(int nativeObject, String[] keys,
93            Object[] values);
94    private static native void nativeSetOrientationHint(int nativeObject,
95            int degrees);
96    private static native void nativeWriteSampleData(int nativeObject,
97            int trackIndex, ByteBuffer byteBuf,
98            int offset, int size, long presentationTimeUs, int flags);
99
100    // Muxer internal states.
101    private static final int MUXER_STATE_UNINITIALIZED  = -1;
102    private static final int MUXER_STATE_INITIALIZED    = 0;
103    private static final int MUXER_STATE_STARTED        = 1;
104    private static final int MUXER_STATE_STOPPED        = 2;
105
106    private int mState = MUXER_STATE_UNINITIALIZED;
107
108    private final CloseGuard mCloseGuard = CloseGuard.get();
109    private int mLastTrackIndex = -1;
110
111    private int mNativeObject;
112
113    /**
114     * Constructor.
115     * Creates a media muxer that writes to the specified path.
116     * @param path The path of the output media file.
117     * @param format The format of the output media file.
118     * @see android.media.MediaMuxer.OutputFormat
119     * @throws IOException if failed to open the file for write
120     */
121    public MediaMuxer(String path, int format) throws IOException {
122        if (path == null) {
123            throw new IllegalArgumentException("path must not be null");
124        }
125        if (format != OutputFormat.MUXER_OUTPUT_MPEG_4) {
126            throw new IllegalArgumentException("format is invalid");
127        }
128        FileOutputStream fos = null;
129        try {
130            File file = new File(path);
131            fos = new FileOutputStream(file);
132            FileDescriptor fd = fos.getFD();
133            mNativeObject = nativeSetup(fd, format);
134            mState = MUXER_STATE_INITIALIZED;
135            mCloseGuard.open("release");
136        } finally {
137            if (fos != null) {
138                fos.close();
139            }
140        }
141    }
142
143    /**
144     * Sets the orientation hint for output video playback.
145     * <p>This method should be called before {@link #start}. Calling this
146     * method will not rotate the video frame when muxer is generating the file,
147     * but add a composition matrix containing the rotation angle in the output
148     * video if the output format is
149     * {@link OutputFormat#MUXER_OUTPUT_MPEG_4} so that a video player can
150     * choose the proper orientation for playback. Note that some video players
151     * may choose to ignore the composition matrix in a video during playback.
152     * By default, the rotation degree is 0.</p>
153     * @param degrees the angle to be rotated clockwise in degrees.
154     * The supported angles are 0, 90, 180, and 270 degrees.
155     */
156    public void setOrientationHint(int degrees) {
157        if (degrees != 0 && degrees != 90  && degrees != 180 && degrees != 270) {
158            throw new IllegalArgumentException("Unsupported angle: " + degrees);
159        }
160        if (mState == MUXER_STATE_INITIALIZED) {
161            nativeSetOrientationHint(mNativeObject, degrees);
162        } else {
163            throw new IllegalStateException("Can't set rotation degrees due" +
164                    " to wrong state.");
165        }
166    }
167
168    /**
169     * Starts the muxer.
170     * <p>Make sure this is called after {@link #addTrack} and before
171     * {@link #writeSampleData}.</p>
172     */
173    public void start() {
174        if (mNativeObject == 0) {
175            throw new IllegalStateException("Muxer has been released!");
176        }
177        if (mState == MUXER_STATE_INITIALIZED) {
178            nativeStart(mNativeObject);
179            mState = MUXER_STATE_STARTED;
180        } else {
181            throw new IllegalStateException("Can't start due to wrong state.");
182        }
183    }
184
185    /**
186     * Stops the muxer.
187     * <p>Once the muxer stops, it can not be restarted.</p>
188     */
189    public void stop() {
190        if (mState == MUXER_STATE_STARTED) {
191            nativeStop(mNativeObject);
192            mState = MUXER_STATE_STOPPED;
193        } else {
194            throw new IllegalStateException("Can't stop due to wrong state.");
195        }
196    }
197
198    @Override
199    protected void finalize() throws Throwable {
200        try {
201            if (mCloseGuard != null) {
202                mCloseGuard.warnIfOpen();
203            }
204            if (mNativeObject != 0) {
205                nativeRelease(mNativeObject);
206                mNativeObject = 0;
207            }
208        } finally {
209            super.finalize();
210        }
211    }
212
213    /**
214     * Adds a track with the specified format.
215     * @param format The media format for the track.
216     * @return The track index for this newly added track, and it should be used
217     * in the {@link #writeSampleData}.
218     */
219    public int addTrack(MediaFormat format) {
220        if (format == null) {
221            throw new IllegalArgumentException("format must not be null.");
222        }
223        if (mState != MUXER_STATE_INITIALIZED) {
224            throw new IllegalStateException("Muxer is not initialized.");
225        }
226        if (mNativeObject == 0) {
227            throw new IllegalStateException("Muxer has been released!");
228        }
229        int trackIndex = -1;
230        // Convert the MediaFormat into key-value pairs and send to the native.
231        Map<String, Object> formatMap = format.getMap();
232
233        String[] keys = null;
234        Object[] values = null;
235        int mapSize = formatMap.size();
236        if (mapSize > 0) {
237            keys = new String[mapSize];
238            values = new Object[mapSize];
239            int i = 0;
240            for (Map.Entry<String, Object> entry : formatMap.entrySet()) {
241                keys[i] = entry.getKey();
242                values[i] = entry.getValue();
243                ++i;
244            }
245            trackIndex = nativeAddTrack(mNativeObject, keys, values);
246        } else {
247            throw new IllegalArgumentException("format must not be empty.");
248        }
249
250        // Track index number is expected to incremented as addTrack succeed.
251        // However, if format is invalid, it will get a negative trackIndex.
252        if (mLastTrackIndex >= trackIndex) {
253            throw new IllegalArgumentException("Invalid format.");
254        }
255        mLastTrackIndex = trackIndex;
256        return trackIndex;
257    }
258
259    /**
260     * Writes an encoded sample into the muxer. The application needs to make
261     * sure that the samples are written into the right tracks.
262     * @param byteBuf The encoded sample.
263     * @param trackIndex The track index for this sample.
264     * @param bufferInfo The buffer information related to this sample.
265     */
266    public void writeSampleData(int trackIndex, ByteBuffer byteBuf,
267            BufferInfo bufferInfo) {
268        if (trackIndex < 0 || trackIndex > mLastTrackIndex) {
269            throw new IllegalArgumentException("trackIndex is invalid");
270        }
271
272        if (byteBuf == null) {
273            throw new IllegalArgumentException("byteBuffer must not be null");
274        }
275
276        if (bufferInfo == null) {
277            throw new IllegalArgumentException("bufferInfo must not be null");
278        }
279        if (bufferInfo.size < 0 || bufferInfo.offset < 0
280                || (bufferInfo.offset + bufferInfo.size) > byteBuf.capacity()
281                || bufferInfo.presentationTimeUs < 0) {
282            throw new IllegalArgumentException("bufferInfo must specify a" +
283                    " valid buffer offset, size and presentation time");
284        }
285
286        if (mNativeObject == 0) {
287            throw new IllegalStateException("Muxer has been released!");
288        }
289
290        if (mState != MUXER_STATE_STARTED) {
291            throw new IllegalStateException("Can't write, muxer is not started");
292        }
293
294        nativeWriteSampleData(mNativeObject, trackIndex, byteBuf,
295                bufferInfo.offset, bufferInfo.size,
296                bufferInfo.presentationTimeUs, bufferInfo.flags);
297    }
298
299    /**
300     * Make sure you call this when you're done to free up any resources
301     * instead of relying on the garbage collector to do this for you at
302     * some point in the future.
303     */
304    public void release() {
305        if (mState == MUXER_STATE_STARTED) {
306            stop();
307        }
308        if (mNativeObject != 0) {
309            nativeRelease(mNativeObject);
310            mNativeObject = 0;
311            mCloseGuard.close();
312        }
313        mState = MUXER_STATE_UNINITIALIZED;
314    }
315}
316