MediaVideoItem.java revision 4b66f7a53f1b5a77c3ca1c12f256cdef078c1799
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;
20import java.util.List;
21
22import android.graphics.Bitmap;
23import android.media.MediaRecorder;
24import android.util.Log;
25import android.view.SurfaceHolder;
26
27/**
28 * This class represents a video clip item on the storyboard
29 * {@hide}
30 */
31public class MediaVideoItem extends MediaItem {
32    // Logging
33    private static final String TAG = "MediaVideoItem";
34
35    // Instance variables
36    private final int mWidth;
37    private final int mHeight;
38    private final int mAspectRatio;
39    private final int mFileType;
40    private final int mVideoType;
41    private final int mVideoProfile;
42    private final int mVideoBitrate;
43    private final long mDurationMs;
44    private final int mAudioBitrate;
45    private final int mFps;
46    private final int mAudioType;
47    private final int mAudioChannels;
48    private final int mAudioSamplingFrequency;
49
50    private long mBeginBoundaryTimeMs;
51    private long mEndBoundaryTimeMs;
52    private int mVolumePercentage;
53    private String mAudioWaveformFilename;
54    private PlaybackThread mPlaybackThread;
55
56    /**
57     * This listener interface is used by the MediaVideoItem to emit playback
58     * progress notifications. This callback should be invoked after the
59     * number of frames specified by
60     * {@link #startPlayback(SurfaceHolder surfaceHolder, long fromMs,
61     *           int callbackAfterFrameCount, PlaybackProgressListener listener)}
62     */
63    public interface PlaybackProgressListener {
64        /**
65         * This method notifies the listener of the current time position while
66         * playing a media item
67         *
68         * @param mediaItem The media item
69         * @param timeMs The current playback position (expressed in milliseconds
70         *            since the beginning of the media item).
71         * @param end true if the end of the media item was reached
72         */
73        public void onProgress(MediaVideoItem mediaItem, long timeMs, boolean end);
74    }
75
76    /**
77     * The playback thread
78     */
79    private class PlaybackThread extends Thread {
80        // Instance variables
81        private final static long FRAME_DURATION = 33;
82        private final PlaybackProgressListener mListener;
83        private final int mCallbackAfterFrameCount;
84        private final long mFromMs, mToMs;
85        private boolean mRun, 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, 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 audioWaveformFilename The name of the audio waveform file
219     *
220     * @throws IOException if the file cannot be opened for reading
221     */
222    MediaVideoItem(String mediaItemId, String filename, int renderingMode,
223            String audioWaveformFilename)  throws IOException {
224        super(mediaItemId, filename, renderingMode);
225        // TODO: Set these variables correctly
226        mWidth = 1080;
227        mHeight = 720;
228        mAspectRatio = MediaProperties.ASPECT_RATIO_3_2;
229        mFileType = MediaProperties.FILE_MP4;
230        mVideoType = MediaRecorder.VideoEncoder.H264;
231        // Do we have predefined values for this variable?
232        mVideoProfile = 0;
233        // Can video and audio duration be different?
234        mDurationMs = 10000;
235        mVideoBitrate = 800000;
236        mAudioBitrate = 30000;
237        mFps = 30;
238        mAudioType = MediaProperties.ACODEC_AAC_LC;
239        mAudioChannels = 2;
240        mAudioSamplingFrequency = 16000;
241
242        mBeginBoundaryTimeMs = 0;
243        mEndBoundaryTimeMs = mDurationMs;
244        mVolumePercentage = 100;
245        mAudioWaveformFilename = audioWaveformFilename;
246    }
247
248    /**
249     * Sets the start and end marks for trimming a video media item.
250     * This method will adjust the duration of bounding transitions if the
251     * current duration of the transactions become greater than the maximum
252     * allowable duration.
253     *
254     * @param beginMs Start time in milliseconds. Set to 0 to extract from the
255     *           beginning
256     * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to
257     *           extract until the end
258     *
259     * @throws IllegalArgumentException if the start time is greater or equal than
260     *           end time, the end time is beyond the file duration, the start time
261     *           is negative
262     */
263    public void setExtractBoundaries(long beginMs, long endMs) {
264        if (beginMs > mDurationMs) {
265            throw new IllegalArgumentException("Invalid start time");
266        }
267        if (endMs > mDurationMs) {
268            throw new IllegalArgumentException("Invalid end time");
269        }
270
271        if (beginMs != mBeginBoundaryTimeMs) {
272            if (mBeginTransition != null) {
273                mBeginTransition.invalidate();
274            }
275        }
276
277        if (endMs != mEndBoundaryTimeMs) {
278            if (mEndTransition != null) {
279                mEndTransition.invalidate();
280            }
281        }
282
283        mBeginBoundaryTimeMs = beginMs;
284        mEndBoundaryTimeMs = endMs;
285
286        // Check if the duration of transitions need to be adjusted
287        if (mBeginTransition != null) {
288            final long maxDurationMs = mBeginTransition.getMaximumDuration();
289            if (mBeginTransition.getDuration() > maxDurationMs) {
290                mBeginTransition.setDuration(maxDurationMs);
291            }
292        }
293
294        if (mEndTransition != null) {
295            final long maxDurationMs = mEndTransition.getMaximumDuration();
296            if (mEndTransition.getDuration() > maxDurationMs) {
297                mEndTransition.setDuration(maxDurationMs);
298            }
299        }
300
301        final List<Overlay> overlays = getAllOverlays();
302        for (Overlay overlay : overlays) {
303            // Adjust the start time if necessary
304            if (overlay.getStartTime() < mBeginBoundaryTimeMs) {
305                overlay.setStartTime(mBeginBoundaryTimeMs);
306            }
307
308            // Adjust the duration if necessary
309            if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) {
310                overlay.setDuration(getTimelineDuration() - overlay.getStartTime());
311            }
312        }
313
314        final List<Effect> effects = getAllEffects();
315        for (Effect effect : effects) {
316            // Adjust the start time if necessary
317            if (effect.getStartTime() < mBeginBoundaryTimeMs) {
318                effect.setStartTime(mBeginBoundaryTimeMs);
319            }
320
321            // Adjust the duration if necessary
322            if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) {
323                effect.setDuration(getTimelineDuration() - effect.getStartTime());
324            }
325        }
326    }
327
328    /**
329     * @return The boundary begin time
330     */
331    public long getBoundaryBeginTime() {
332        return mBeginBoundaryTimeMs;
333    }
334
335    /**
336     * @return The boundary end time
337     */
338    public long getBoundaryEndTime() {
339        return mEndBoundaryTimeMs;
340    }
341
342    /*
343     * {@inheritDoc}
344     */
345    @Override
346    public void addEffect(Effect effect) {
347        if (effect instanceof EffectKenBurns) {
348            throw new IllegalArgumentException("Ken Burns effects cannot be applied to MediaVideoItem");
349        }
350        super.addEffect(effect);
351    }
352
353    /*
354     * {@inheritDoc}
355     */
356    @Override
357    public Bitmap getThumbnail(int width, int height, long timeMs) {
358        return null;
359    }
360
361    /*
362     * {@inheritDoc}
363     */
364    @Override
365    public Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs,
366            int thumbnailCount) throws IOException {
367        return null;
368    }
369
370    /*
371     * {@inheritDoc}
372     */
373    @Override
374    public int getAspectRatio() {
375        return mAspectRatio;
376    }
377
378    /*
379     * {@inheritDoc}
380     */
381    @Override
382    public int getFileType() {
383        return mFileType;
384    }
385
386    /*
387     * {@inheritDoc}
388     */
389    @Override
390    public int getWidth() {
391        return mWidth;
392    }
393
394    /*
395     * {@inheritDoc}
396     */
397    @Override
398    public int getHeight() {
399        return mHeight;
400    }
401
402    /**
403     * @return The duration of the video clip
404     */
405    public long getDuration() {
406        return mDurationMs;
407    }
408
409    /**
410     * @return The timeline duration. This is the actual duration in the
411     *      timeline (trimmed duration)
412     */
413    @Override
414    public long getTimelineDuration() {
415        return mEndBoundaryTimeMs - mBeginBoundaryTimeMs;
416    }
417
418    /**
419     * Render a frame according to the playback (in the native aspect ratio) for
420     * the specified media item. All effects and overlays applied to the media
421     * item are ignored. The extract boundaries are also ignored. This method
422     * can be used to playback frames when implementing trimming functionality.
423     *
424     * @param surfaceHolder SurfaceHolder used by the application
425     * @param timeMs time corresponding to the frame to display (relative to the
426     *            the beginning of the media item).
427     * @return The accurate time stamp of the frame that is rendered .
428     * @throws IllegalStateException if a playback, preview or an export is
429     *             already in progress
430     * @throws IllegalArgumentException if time is negative or greater than the
431     *             media item duration
432     */
433    public long renderFrame(SurfaceHolder surfaceHolder, long timeMs) {
434        return timeMs;
435    }
436
437    /**
438     * Start the playback of this media item. This method does not block (does
439     * not wait for the playback to complete). The PlaybackProgressListener
440     * allows to track the progress at the time interval determined by the
441     * callbackAfterFrameCount parameter. The SurfaceHolder has to be created
442     * and ready for use before calling this method.
443     *
444     * @param surfaceHolder SurfaceHolder where the frames are rendered.
445     * @param fromMs The time (relative to the beginning of the media item) at
446     *            which the playback will start
447     * @param toMs The time (relative to the beginning of the media item) at
448     *            which the playback will stop. Use -1 to play to the end of the
449     *            media item
450     * @param loop true if the playback should be looped once it reaches the end
451     * @param callbackAfterFrameCount The listener interface should be invoked
452     *            after the number of frames specified by this parameter.
453     * @param listener The listener which will be notified of the playback
454     *            progress
455     * @throws IllegalArgumentException if fromMs or toMs is beyond the playback
456     *             duration
457     * @throws IllegalStateException if a playback, preview or an export is
458     *             already in progress
459     */
460    public void startPlayback(SurfaceHolder surfaceHolder, long fromMs, long toMs, boolean loop,
461            int callbackAfterFrameCount, PlaybackProgressListener listener) {
462        if (fromMs >= mDurationMs) {
463            return;
464        }
465        mPlaybackThread = new PlaybackThread(fromMs, toMs, loop, callbackAfterFrameCount,
466                listener);
467        mPlaybackThread.start();
468    }
469
470    /**
471     * Stop the media item playback. This method blocks until the ongoing
472     * playback is stopped.
473     *
474     * @return The accurate current time when stop is effective expressed in
475     *         milliseconds
476     */
477    public long stopPlayback() {
478        final long stopTimeMs;
479        if (mPlaybackThread != null) {
480            stopTimeMs = mPlaybackThread.stopPlayback();
481            mPlaybackThread = null;
482        } else {
483            stopTimeMs = 0;
484        }
485        return stopTimeMs;
486    }
487
488    /**
489     * This API allows to generate a file containing the sample volume levels of
490     * the Audio track of this media item. This function may take significant
491     * time and is blocking. The file can be retrieved using
492     * getAudioWaveformFilename().
493     *
494     * @param listener The progress listener
495     *
496     * @throws IOException if the output file cannot be created
497     * @throws IllegalArgumentException if the mediaItem does not have a valid
498     *             Audio track
499     */
500    public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener)
501            throws IOException {
502        // TODO: Set mAudioWaveformFilename at the end once the export is complete
503    }
504
505    /**
506     * Get the audio waveform file name if {@link #extractAudioWaveform()} was
507     * successful. The file format is as following:
508     * <ul>
509     *  <li>first 4 bytes provide the number of samples for each value, as big-endian signed</li>
510     *  <li>4 following bytes is the total number of values in the file, as big-endian signed</li>
511     *  <li>all values follow as bytes Name is unique.</li>
512     *</ul>
513     * @return the name of the file, null if the file has not been computed or
514     *         if there is no Audio track in the mediaItem
515     */
516    public String getAudioWaveformFilename() {
517        return mAudioWaveformFilename;
518    }
519
520    /**
521     * Set volume of the Audio track of this mediaItem
522     *
523     * @param volumePercent in %/. 100% means no change; 50% means half value, 200%
524     *            means double, 0% means silent.
525     * @throws UsupportedOperationException if volume value is not supported
526     */
527    public void setVolume(int volumePercent) {
528        mVolumePercentage = volumePercent;
529    }
530
531    /**
532     * Get the volume value of the audio track as percentage. Call of this
533     * method before calling setVolume will always return 100%
534     *
535     * @return the volume in percentage
536     */
537    public int getVolume() {
538        return mVolumePercentage;
539    }
540
541    /**
542     * @return The video type
543     */
544    public int getVideoType() {
545        return mVideoType;
546    }
547
548    /**
549     * @return The video profile
550     */
551    public int getVideoProfile() {
552        return mVideoProfile;
553    }
554
555    /**
556     * @return The video bitrate
557     */
558    public int getVideoBitrate() {
559        return mVideoBitrate;
560    }
561
562    /**
563     * @return The audio bitrate
564     */
565    public int getAudioBitrate() {
566        return mAudioBitrate;
567    }
568
569    /**
570     * @return The number of frames per second
571     */
572    public int getFps() {
573        return mFps;
574    }
575
576    /**
577     * @return The audio codec
578     */
579    public int getAudioType() {
580        return mAudioType;
581    }
582
583    /**
584     * @return The number of audio channels
585     */
586    public int getAudioChannels() {
587        return mAudioChannels;
588    }
589
590    /**
591     * @return The audio sample frequency
592     */
593    public int getAudioSamplingFrequency() {
594        return mAudioSamplingFrequency;
595    }
596}
597