MediaVideoItem.java revision e5867ef3f096521c4a7a289d83e75904b3a977c5
1/*
2 * Copyright (C) 2010 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.videoeditor;
18
19import java.io.IOException;
20
21import android.graphics.Bitmap;
22import android.util.Log;
23import android.view.SurfaceHolder;
24
25/**
26 * This class represents a video clip item on the storyboard
27 * {@hide}
28 */
29public class MediaVideoItem extends MediaItem {
30    // Logging
31    private static final String TAG = "MediaVideoItem";
32
33    // Instance variables
34    private final int mWidth;
35    private final int mHeight;
36    private final int mAspectRatio;
37    private final int mFileType;
38    private final int mVideoType;
39    private final int mVideoProfile;
40    private final int mVideoBitrate;
41    private final long mDurationMs;
42    private final int mAudioBitrate;
43    private final int mFps;
44    private final int mAudioType;
45    private final int mAudioChannels;
46    private final int mAudioSamplingFrequency;
47
48    private long mBeginBoundaryTimeMs;
49    private long mEndBoundaryTimeMs;
50    private int mVolumePercentage;
51    private boolean mMuted;
52    private String mAudioWaveformFilename;
53    private PlaybackThread mPlaybackThread;
54
55    /**
56     * This listener interface is used by the MediaVideoItem to emit playback
57     * progress notifications. This callback should be invoked after the
58     * number of frames specified by
59     * {@link #startPlayback(SurfaceHolder surfaceHolder, long fromMs,
60     *           int callbackAfterFrameCount, PlaybackProgressListener listener)}
61     */
62    public interface PlaybackProgressListener {
63        /**
64         * This method notifies the listener of the current time position while
65         * playing a media item
66         *
67         * @param mediaItem The media item
68         * @param timeMs The current playback position (expressed in milliseconds
69         *            since the beginning of the media item).
70         * @param end true if the end of the media item was reached
71         */
72        public void onProgress(MediaVideoItem mediaItem, long timeMs, boolean end);
73    }
74
75    /**
76     * The playback thread
77     */
78    private class PlaybackThread extends Thread {
79        // Instance variables
80        private final static long FRAME_DURATION = 33;
81        private final PlaybackProgressListener mListener;
82        private final int mCallbackAfterFrameCount;
83        private final long mFromMs, mToMs;
84        private boolean mRun;
85        private final boolean mLoop;
86        private long mPositionMs;
87
88        /**
89         * Constructor
90         *
91         * @param fromMs The time (relative to the beginning of the media item)
92         *            at which the playback will start
93         * @param toMs The time (relative to the beginning of the media item) at
94         *            which the playback will stop. Use -1 to play to the end of
95         *            the media item
96         * @param loop true if the playback should be looped once it reaches the
97         *            end
98         * @param callbackAfterFrameCount The listener interface should be
99         *            invoked after the number of frames specified by this
100         *            parameter.
101         * @param listener The listener which will be notified of the playback
102         *            progress
103         */
104        public PlaybackThread(long fromMs, long toMs, boolean loop, int callbackAfterFrameCount,
105                PlaybackProgressListener listener) {
106            mPositionMs = mFromMs = fromMs;
107            if (toMs < 0) {
108                mToMs = mDurationMs;
109            } else {
110                mToMs = toMs;
111            }
112            mLoop = loop;
113            mCallbackAfterFrameCount = callbackAfterFrameCount;
114            mListener = listener;
115            mRun = true;
116        }
117
118        /*
119         * {@inheritDoc}
120         */
121        @Override
122        public void run() {
123            if (Log.isLoggable(TAG, Log.DEBUG)) {
124                Log.d(TAG, "===> PlaybackThread.run enter");
125            }
126            int frameCount = 0;
127            while (mRun) {
128                try {
129                    sleep(FRAME_DURATION);
130                } catch (InterruptedException ex) {
131                    break;
132                }
133                frameCount++;
134                mPositionMs += FRAME_DURATION;
135
136                if (mPositionMs >= mToMs) {
137                    if (!mLoop) {
138                        if (mListener != null) {
139                            mListener.onProgress(MediaVideoItem.this, mPositionMs, true);
140                        }
141                        if (Log.isLoggable(TAG, Log.DEBUG)) {
142                            Log.d(TAG, "PlaybackThread.run playback complete");
143                        }
144                        break;
145                    } else {
146                        // Fire a notification for the end of the clip
147                        if (mListener != null) {
148                            mListener.onProgress(MediaVideoItem.this, mToMs, false);
149                        }
150
151                        // Rewind
152                        mPositionMs = mFromMs;
153                        if (mListener != null) {
154                            mListener.onProgress(MediaVideoItem.this, mPositionMs, false);
155                        }
156                        if (Log.isLoggable(TAG, Log.DEBUG)) {
157                            Log.d(TAG, "PlaybackThread.run playback complete");
158                        }
159                        frameCount = 0;
160                    }
161                } else {
162                    if (frameCount == mCallbackAfterFrameCount) {
163                        if (mListener != null) {
164                            mListener.onProgress(MediaVideoItem.this, mPositionMs, false);
165                        }
166                        frameCount = 0;
167                    }
168                }
169            }
170            if (Log.isLoggable(TAG, Log.DEBUG)) {
171                Log.d(TAG, "===> PlaybackThread.run exit");
172            }
173        }
174
175        /**
176         * Stop the playback
177         *
178         * @return The stop position
179         */
180        public long stopPlayback() {
181            mRun = false;
182            try {
183                join();
184            } catch (InterruptedException ex) {
185            }
186            return mPositionMs;
187        }
188    };
189
190    /**
191     * An object of this type cannot be instantiated with a default constructor
192     */
193    @SuppressWarnings("unused")
194    private MediaVideoItem() throws IOException {
195        this(null, null, RENDERING_MODE_BLACK_BORDER);
196    }
197
198    /**
199     * Constructor
200     *
201     * @param mediaItemId The MediaItem id
202     * @param filename The image file name
203     * @param renderingMode The rendering mode
204     *
205     * @throws IOException if the file cannot be opened for reading
206     */
207    public MediaVideoItem(String mediaItemId, String filename, int renderingMode)
208        throws IOException {
209        this(mediaItemId, filename, renderingMode, 0, END_OF_FILE, 100, false, null);
210    }
211
212    /**
213     * Constructor
214     *
215     * @param mediaItemId The MediaItem id
216     * @param filename The image file name
217     * @param renderingMode The rendering mode
218     * @param beginMs Start time in milliseconds. Set to 0 to extract from the
219     *           beginning
220     * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to
221     *           extract until the end
222     * @param volumePercent in %/. 100% means no change; 50% means half value, 200%
223     *            means double, 0% means silent.
224     * @param muted true if the audio is muted
225     * @param audioWaveformFilename The name of the audio waveform file
226     *
227     * @throws IOException if the file cannot be opened for reading
228     */
229    MediaVideoItem(String mediaItemId, String filename, int renderingMode,
230            long beginMs, long endMs, int volumePercent, boolean muted,
231            String audioWaveformFilename)  throws IOException {
232        super(mediaItemId, filename, renderingMode);
233        // TODO: Set these variables correctly
234        mWidth = 1080;
235        mHeight = 720;
236        mAspectRatio = MediaProperties.ASPECT_RATIO_3_2;
237        mFileType = MediaProperties.FILE_MP4;
238        mVideoType = MediaProperties.VCODEC_H264BP;
239        // Do we have predefined values for this variable?
240        mVideoProfile = 0;
241        // Can video and audio duration be different?
242        mDurationMs = 10000;
243        mVideoBitrate = 800000;
244        mAudioBitrate = 30000;
245        mFps = 30;
246        mAudioType = MediaProperties.ACODEC_AAC_LC;
247        mAudioChannels = 2;
248        mAudioSamplingFrequency = 16000;
249
250        mBeginBoundaryTimeMs = beginMs;
251        mEndBoundaryTimeMs = endMs == END_OF_FILE ? mDurationMs : endMs;
252        mVolumePercentage = volumePercent;
253        mMuted = muted;
254        mAudioWaveformFilename = audioWaveformFilename;
255    }
256
257    /**
258     * Sets the start and end marks for trimming a video media item.
259     * This method will adjust the duration of bounding transitions, effects
260     * and overlays if the current duration of the transactions become greater
261     * than the maximum allowable duration.
262     *
263     * @param beginMs Start time in milliseconds. Set to 0 to extract from the
264     *           beginning
265     * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to
266     *           extract until the end
267     *
268     * @throws IllegalArgumentException if the start time is greater or equal than
269     *           end time, the end time is beyond the file duration, the start time
270     *           is negative
271     */
272    public void setExtractBoundaries(long beginMs, long endMs) {
273        if (beginMs > mDurationMs) {
274            throw new IllegalArgumentException("Invalid start time");
275        }
276        if (endMs > mDurationMs) {
277            throw new IllegalArgumentException("Invalid end time");
278        }
279
280        if (beginMs != mBeginBoundaryTimeMs) {
281            if (mBeginTransition != null) {
282                mBeginTransition.invalidate();
283            }
284        }
285
286        if (endMs != mEndBoundaryTimeMs) {
287            if (mEndTransition != null) {
288                mEndTransition.invalidate();
289            }
290        }
291
292        mBeginBoundaryTimeMs = beginMs;
293        mEndBoundaryTimeMs = endMs;
294
295        adjustElementsDuration();
296    }
297
298    /**
299     * @return The boundary begin time
300     */
301    public long getBoundaryBeginTime() {
302        return mBeginBoundaryTimeMs;
303    }
304
305    /**
306     * @return The boundary end time
307     */
308    public long getBoundaryEndTime() {
309        return mEndBoundaryTimeMs;
310    }
311
312    /*
313     * {@inheritDoc}
314     */
315    @Override
316    public void addEffect(Effect effect) {
317        if (effect instanceof EffectKenBurns) {
318            throw new IllegalArgumentException("Ken Burns effects cannot be applied to MediaVideoItem");
319        }
320        super.addEffect(effect);
321    }
322
323    /*
324     * {@inheritDoc}
325     */
326    @Override
327    public Bitmap getThumbnail(int width, int height, long timeMs) {
328        return null;
329    }
330
331    /*
332     * {@inheritDoc}
333     */
334    @Override
335    public Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs,
336            int thumbnailCount) throws IOException {
337        return null;
338    }
339
340    /*
341     * {@inheritDoc}
342     */
343    @Override
344    public int getAspectRatio() {
345        return mAspectRatio;
346    }
347
348    /*
349     * {@inheritDoc}
350     */
351    @Override
352    public int getFileType() {
353        return mFileType;
354    }
355
356    /*
357     * {@inheritDoc}
358     */
359    @Override
360    public int getWidth() {
361        return mWidth;
362    }
363
364    /*
365     * {@inheritDoc}
366     */
367    @Override
368    public int getHeight() {
369        return mHeight;
370    }
371
372    /**
373     * @return The duration of the video clip
374     */
375    public long getDuration() {
376        return mDurationMs;
377    }
378
379    /**
380     * @return The timeline duration. This is the actual duration in the
381     *      timeline (trimmed duration)
382     */
383    @Override
384    public long getTimelineDuration() {
385        return mEndBoundaryTimeMs - mBeginBoundaryTimeMs;
386    }
387
388    /**
389     * Render a frame according to the playback (in the native aspect ratio) for
390     * the specified media item. All effects and overlays applied to the media
391     * item are ignored. The extract boundaries are also ignored. This method
392     * can be used to playback frames when implementing trimming functionality.
393     *
394     * @param surfaceHolder SurfaceHolder used by the application
395     * @param timeMs time corresponding to the frame to display (relative to the
396     *            the beginning of the media item).
397     * @return The accurate time stamp of the frame that is rendered .
398     * @throws IllegalStateException if a playback, preview or an export is
399     *             already in progress
400     * @throws IllegalArgumentException if time is negative or greater than the
401     *             media item duration
402     */
403    public long renderFrame(SurfaceHolder surfaceHolder, long timeMs) {
404        return timeMs;
405    }
406
407    /**
408     * Start the playback of this media item. This method does not block (does
409     * not wait for the playback to complete). The PlaybackProgressListener
410     * allows to track the progress at the time interval determined by the
411     * callbackAfterFrameCount parameter. The SurfaceHolder has to be created
412     * and ready for use before calling this method.
413     *
414     * @param surfaceHolder SurfaceHolder where the frames are rendered.
415     * @param fromMs The time (relative to the beginning of the media item) at
416     *            which the playback will start
417     * @param toMs The time (relative to the beginning of the media item) at
418     *            which the playback will stop. Use -1 to play to the end of the
419     *            media item
420     * @param loop true if the playback should be looped once it reaches the end
421     * @param callbackAfterFrameCount The listener interface should be invoked
422     *            after the number of frames specified by this parameter.
423     * @param listener The listener which will be notified of the playback
424     *            progress
425     * @throws IllegalArgumentException if fromMs or toMs is beyond the playback
426     *             duration
427     * @throws IllegalStateException if a playback, preview or an export is
428     *             already in progress
429     */
430    public void startPlayback(SurfaceHolder surfaceHolder, long fromMs, long toMs, boolean loop,
431            int callbackAfterFrameCount, PlaybackProgressListener listener) {
432        if (fromMs >= mDurationMs) {
433            return;
434        }
435        mPlaybackThread = new PlaybackThread(fromMs, toMs, loop, callbackAfterFrameCount,
436                listener);
437        mPlaybackThread.start();
438    }
439
440    /**
441     * Stop the media item playback. This method blocks until the ongoing
442     * playback is stopped.
443     *
444     * @return The accurate current time when stop is effective expressed in
445     *         milliseconds
446     */
447    public long stopPlayback() {
448        final long stopTimeMs;
449        if (mPlaybackThread != null) {
450            stopTimeMs = mPlaybackThread.stopPlayback();
451            mPlaybackThread = null;
452        } else {
453            stopTimeMs = 0;
454        }
455        return stopTimeMs;
456    }
457
458    /**
459     * This API allows to generate a file containing the sample volume levels of
460     * the Audio track of this media item. This function may take significant
461     * time and is blocking. The file can be retrieved using
462     * getAudioWaveformFilename().
463     *
464     * @param listener The progress listener
465     *
466     * @throws IOException if the output file cannot be created
467     * @throws IllegalArgumentException if the mediaItem does not have a valid
468     *             Audio track
469     */
470    public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener)
471            throws IOException {
472        // TODO: Set mAudioWaveformFilename at the end once the export is complete
473    }
474
475    /**
476     * Get the audio waveform file name if {@link #extractAudioWaveform()} was
477     * successful. The file format is as following:
478     * <ul>
479     *  <li>first 4 bytes provide the number of samples for each value, as big-endian signed</li>
480     *  <li>4 following bytes is the total number of values in the file, as big-endian signed</li>
481     *  <li>all values follow as bytes Name is unique.</li>
482     *</ul>
483     * @return the name of the file, null if the file has not been computed or
484     *         if there is no Audio track in the mediaItem
485     */
486    public String getAudioWaveformFilename() {
487        return mAudioWaveformFilename;
488    }
489
490    /**
491     * Set volume of the Audio track of this mediaItem
492     *
493     * @param volumePercent in %/. 100% means no change; 50% means half value, 200%
494     *            means double, 0% means silent.
495     * @throws UsupportedOperationException if volume value is not supported
496     */
497    public void setVolume(int volumePercent) {
498        mVolumePercentage = volumePercent;
499    }
500
501    /**
502     * Get the volume value of the audio track as percentage. Call of this
503     * method before calling setVolume will always return 100%
504     *
505     * @return the volume in percentage
506     */
507    public int getVolume() {
508        return mVolumePercentage;
509    }
510
511    /**
512     * @param muted true to mute the media item
513     */
514    public void setMute(boolean muted) {
515        mMuted = muted;
516    }
517
518    /**
519     * @return true if the media item is muted
520     */
521    public boolean isMuted() {
522        return mMuted;
523    }
524
525    /**
526     * @return The video type
527     */
528    public int getVideoType() {
529        return mVideoType;
530    }
531
532    /**
533     * @return The video profile
534     */
535    public int getVideoProfile() {
536        return mVideoProfile;
537    }
538
539    /**
540     * @return The video bitrate
541     */
542    public int getVideoBitrate() {
543        return mVideoBitrate;
544    }
545
546    /**
547     * @return The audio bitrate
548     */
549    public int getAudioBitrate() {
550        return mAudioBitrate;
551    }
552
553    /**
554     * @return The number of frames per second
555     */
556    public int getFps() {
557        return mFps;
558    }
559
560    /**
561     * @return The audio codec
562     */
563    public int getAudioType() {
564        return mAudioType;
565    }
566
567    /**
568     * @return The number of audio channels
569     */
570    public int getAudioChannels() {
571        return mAudioChannels;
572    }
573
574    /**
575     * @return The audio sample frequency
576     */
577    public int getAudioSamplingFrequency() {
578        return mAudioSamplingFrequency;
579    }
580}
581