说说Android的视频裁剪(二)

作者: WHOKNOWME | 来源:发表于2016-12-30 12:12 被阅读1255次

    有关视频裁剪我在前面一篇博客中只是做了简单的说明,主要涉及到的知识准备在这篇博客中拿出来说一下。有兴趣的可以fork我的视频裁剪的项目源码,一起学习进步。
    Githubhttps://github.com/iknow4/Android-Video-Trimmer

    视频裁剪页如下图所示:

    图上面的视频播放用是VideoView来实现的,比较简单。我主要说说视频帧的读取和视频时间的选取。
    视频帧的获取可以通过Android提供的MediaMetadataRetriever类来实现。

    MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
    mediaMetadataRetriever.setDataSource(context, videoUri);
    

    在MediaMetadataRetriever类中会调用到media_jni的这个库中的setDataSource()方法,最终使得我们可以获取视频的meta data,meta data是非常丰富的,足足有26种,足够满足我们的需求了。

     /**
         * The metadata key to retrieve the numeric string describing the
         * order of the audio data source on its original recording.
         */
        public static final int METADATA_KEY_CD_TRACK_NUMBER = 0;
        /**
         * The metadata key to retrieve the information about the album title
         * of the data source.
         */
        public static final int METADATA_KEY_ALBUM           = 1;
        /**
         * The metadata key to retrieve the information about the artist of
         * the data source.
         */
        public static final int METADATA_KEY_ARTIST          = 2;
        /**
         * The metadata key to retrieve the information about the author of
         * the data source.
         */
        public static final int METADATA_KEY_AUTHOR          = 3;
        /**
         * The metadata key to retrieve the information about the composer of
         * the data source.
         */
        public static final int METADATA_KEY_COMPOSER        = 4;
        /**
         * The metadata key to retrieve the date when the data source was created
         * or modified.
         */
        public static final int METADATA_KEY_DATE            = 5;
        /**
         * The metadata key to retrieve the content type or genre of the data
         * source.
         */
        public static final int METADATA_KEY_GENRE           = 6;
        /**
         * The metadata key to retrieve the data source title.
         */
        public static final int METADATA_KEY_TITLE           = 7;
        /**
         * The metadata key to retrieve the year when the data source was created
         * or modified.
         */
        public static final int METADATA_KEY_YEAR            = 8;
        /**
         * The metadata key to retrieve the playback duration of the data source.
         */
        public static final int METADATA_KEY_DURATION        = 9;
        /**
         * The metadata key to retrieve the number of tracks, such as audio, video,
         * text, in the data source, such as a mp4 or 3gpp file.
         */
        public static final int METADATA_KEY_NUM_TRACKS      = 10;
        /**
         * The metadata key to retrieve the information of the writer (such as
         * lyricist) of the data source.
         */
        public static final int METADATA_KEY_WRITER          = 11;
        /**
         * The metadata key to retrieve the mime type of the data source. Some
         * example mime types include: "video/mp4", "audio/mp4", "audio/amr-wb",
         * etc.
         */
        public static final int METADATA_KEY_MIMETYPE        = 12;
        /**
         * The metadata key to retrieve the information about the performers or
         * artist associated with the data source.
         */
        public static final int METADATA_KEY_ALBUMARTIST     = 13;
        /**
         * The metadata key to retrieve the numberic string that describes which
         * part of a set the audio data source comes from.
         */
        public static final int METADATA_KEY_DISC_NUMBER     = 14;
        /**
         * The metadata key to retrieve the music album compilation status.
         */
        public static final int METADATA_KEY_COMPILATION     = 15;
        /**
         * If this key exists the media contains audio content.
         */
        public static final int METADATA_KEY_HAS_AUDIO       = 16;
        /**
         * If this key exists the media contains video content.
         */
        public static final int METADATA_KEY_HAS_VIDEO       = 17;
        /**
         * If the media contains video, this key retrieves its width.
         */
        public static final int METADATA_KEY_VIDEO_WIDTH     = 18;
        /**
         * If the media contains video, this key retrieves its height.
         */
        public static final int METADATA_KEY_VIDEO_HEIGHT    = 19;
        /**
         * This key retrieves the average bitrate (in bits/sec), if available.
         */
        public static final int METADATA_KEY_BITRATE         = 20;
        /**
         * This key retrieves the language code of text tracks, if available.
         * If multiple text tracks present, the return value will look like:
         * "eng:chi"
         * @hide
         */
        public static final int METADATA_KEY_TIMED_TEXT_LANGUAGES      = 21;
        /**
         * If this key exists the media is drm-protected.
         * @hide
         */
        public static final int METADATA_KEY_IS_DRM          = 22;
        /**
         * This key retrieves the location information, if available.
         * The location should be specified according to ISO-6709 standard, under
         * a mp4/3gp box "@xyz". Location with longitude of -90 degrees and latitude
         * of 180 degrees will be retrieved as "-90.0000+180.0000", for instance.
         */
        public static final int METADATA_KEY_LOCATION        = 23;
        /**
         * This key retrieves the video rotation angle in degrees, if available.
         * The video rotation angle may be 0, 90, 180, or 270 degrees.
         */
        public static final int METADATA_KEY_VIDEO_ROTATION = 24;
        /**
         * This key retrieves the original capture framerate, if it's
         * available. The capture framerate will be a floating point
         * number.
         */
        public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25;
        // Add more here...
    

    了解这些meta 信息,就可以知道一个视频所能给我们提供的信息有哪些了。
    在这里,我们是要获取视频的帧(Frame),帧其实就是图片,一帧可能会有N多张图片组成。通过调用MediaMetadataRetriever类中的getFrameAtTime()获取视频的帧的Bitmap。

    
     /**
         * Call this method after setDataSource(). This method finds a
         * representative frame close to the given time position by considering
         * the given option if possible, and returns it as a bitmap. This is
         * useful for generating a thumbnail for an input data source or just
         * obtain and display a frame at the given time position.
         *
         * @param timeUs The time position where the frame will be retrieved.
         * When retrieving the frame at the given time position, there is no
         * guarantee that the data source has a frame located at the position.
         * When this happens, a frame nearby will be returned. If timeUs is
         * negative, time position and option will ignored, and any frame
         * that the implementation considers as representative may be returned.
         *
         * @param option a hint on how the frame is found. Use
         * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame
         * that has a timestamp earlier than or the same as timeUs. Use
         * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame
         * that has a timestamp later than or the same as timeUs. Use
         * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame
         * that has a timestamp closest to or the same as timeUs. Use
         * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may
         * or may not be a sync frame but is closest to or the same as timeUs.
         * {@link #OPTION_CLOSEST} often has larger performance overhead compared
         * to the other options if there is no sync frame located at timeUs.
         *
         * @return A Bitmap containing a representative video frame, which 
         *         can be null, if such a frame cannot be retrieved.
         */
        public Bitmap getFrameAtTime(long timeUs, int option) {
            if (option < OPTION_PREVIOUS_SYNC ||
                option > OPTION_CLOSEST) {
                throw new IllegalArgumentException("Unsupported option: " + option);
            }
    
            return _getFrameAtTime(timeUs, option);
        }
    

    这里有两个参数:
    timeUs 表示每一帧的开始时间位置,给定的时间的帧不一定存在,但是系统会将临近的一帧返回给我们。
    option 参数表示获取帧的方式

    以下是我调用的代码块,代码块是放在单独的线程中执行的。

    try {
        MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
        mediaMetadataRetriever.setDataSource(context, videoUri);
    
        // Retrieve media data use microsecond
        long videoLengthInMs = Long.parseLong(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) * 1000;
    
        long numThumbs = videoLengthInMs < one_frame_time ? 1 : (videoLengthInMs / one_frame_time);
    
        final long interval = videoLengthInMs / numThumbs;
    
        //每次截取到3帧之后上报
        for (long i = 0; i < numThumbs; ++i) {
            Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(i * interval, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
            try {
                bitmap = Bitmap.createScaledBitmap(bitmap, thumb_Width, thumb_Height, false);
            } catch (Exception e) {
                e.printStackTrace();
            }
            thumbnailList.add(bitmap);
            if (thumbnailList.size() == 3) {
                callback.onSingleCallback((ArrayList<Bitmap>) thumbnailList.clone(), (int) interval);
                thumbnailList.clear();
            }
        }
        if (thumbnailList.size() > 0) {
            callback.onSingleCallback((ArrayList<Bitmap>) thumbnailList.clone(), (int) interval);
            thumbnailList.clear();
        }
        mediaMetadataRetriever.release();
    } catch (final Throwable e) {
        Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
    }
    

    子线程会在每截取到三帧之后将数据上报给主线程,主线程拿到数据之后会进行ui的更新操作,将bitmap显示出来。

    当视频源时间比较长的时候,我们会截取到很多帧图片,所以需要将帧图片可以左右滑动。项目中用到的控件是:VideoThumbHorizontalListView (可水平滚动的ListView)

    videoThumbListView.setOnScrollStateChangedListener(new VideoThumbHorizontalListView.OnScrollStateChangedListener({    
    @Override    
    public void onScrollStateChanged(ScrollState scrollState, int scrollDirection) { 
        if (videoThumbListView.getCurrentX() == 0)
                return;
    
        switch (scrollState) {
    
            case SCROLL_STATE_FLING:
            case SCROLL_STATE_IDLE:
            case SCROLL_STATE_TOUCH_SCROLL:
    
                if (scrolledOffset < 0) {
                    mScrolledOffset = mScrolledOffset - Math.abs(scrolledOffset);
                    if (mScrolledOffset <= 0)
                        mScrolledOffset = 0;
                } else {
                    if(PixToTime(mScrolledOffset + SCREEN_WIDTH) <= mDuration)//根据时间来判断还是否可以向左滚动
                        mScrolledOffset = mScrolledOffset + scrolledOffset;
                }
                onVideoReset();
                onSeekThumbs(0, mScrolledOffset + leftThumbValue);
                onSeekThumbs(1, mScrolledOffset + rightThumbValue);
                mRangeSeekBarView.invalidate();
                break; 
        }
    } );
    

    我会去监听水平ListView的左右滚动事件,以获取它的滚动距离,从而算出并显示当前截取到的视频时间的长度。这里我们需要将视频总长度与屏幕的宽度做一个计算,形成一个映射关系,这是一个比较麻烦的事情,处理不好,会导致在ListView滚动过程中时间显示不准确的问题,哈哈,我的项目就存在着这样的bug。

    这个项目只是一个粗糙的项目,页面上会存在一些小问题,不过可以拿来简单的学习一下。
    欢迎有兴趣的同学star和fork。
    Githubhttps://github.com/iknow4/Android-Video-Trimmer

    Thanks for reading. To help others please click ❤ to recommend this article if you found it helpful.

    相关文章

      网友评论

      • 3e5f8438aaca:VideoThumbHorizontalListView上面覆盖着RangeSeekBarView,请问你是使用什么方式把滑动事件分发到VideoThumbHorizontalListView的呢

      本文标题:说说Android的视频裁剪(二)

      本文链接:https://www.haomeiwen.com/subject/gdhmvttx.html