Android小视频录制技术实现

        老大下达了一个要求,让我高仿一个类似微信录制小视频的功能,根据网上各路大神的demo及思路参考下,然后我就粗糙的做了一个小demo,个人感觉还不错,不喜者忽喷。源码链接在文件后面提供,欢迎大家学习及参考,有不足之处请跟我联系,我会加紧改进,一起探讨学习,希望能帮助那些需要实现类似功能的小伙伴们。话不多说,直接上效果图:

  • 主界面图

这里写图片描述

  • 短视频界面图

这里写图片描述

  • 短视频录制效果图

        不能发视频效果,只能是图片,就用工具压缩成了gif,吖这画质我也是醉醉的,就将就着看看吧,实际很清晰的,还会自动对焦。
这里写图片描述
        实现代码过长就不全贴了,文件中有两个依赖库,一个是某大神的录制库,一个是金山云的播放库,效果挺好的,大家有兴趣就下载源文件看吧
重点代码:MainActivity.java

package com.example.jerei.videodemo;

import android.content.Intent;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.media.ThumbnailUtils;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;


import com.sh.shvideolibrary.VideoInputActivity;
import com.sh.shvideolibrary.VideoInputDialog;
import com.sh.shvideolibrary.compression.CompressListener;
import com.sh.shvideolibrary.compression.CompressorUtils;

import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MainActivity extends AppCompatActivity implements VideoInputDialog.VideoCall {

    private static final int REQUEST_CODE_FOR_PLAYER = 4000;
    Button button;
    Button button2;

    TextView first;
    TextView back;
    ProgressBar progressBar;


    static String TAG = "MainActivity";

    String path;//视频录制输出地址
    //视频压缩数据地址
    private String currentOutputVideoPath;
    private static final int REQUEST_CODE_FOR_RECORD_VIDEO = 5230;//录制视频请求码
    Double videoLength = 0.0;//视频时长
    private Button btnPlay;
    private String finishPath;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.button);
        button2 = (Button) findViewById(R.id.button2);
        first = (TextView) findViewById(R.id.first);
        btnPlay = ((Button) findViewById(R.id.btnPlay));
        back = (TextView) findViewById(R.id.back);
        progressBar = (ProgressBar) findViewById(R.id.progressBar);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                btnPlay.setEnabled(false);
                //显示视频录制控件
                VideoInputDialog.show(getSupportFragmentManager(), MainActivity.this, VideoInputDialog.Q720, MainActivity.this);
            }
        });
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                btnPlay.setEnabled(false);
                VideoInputActivity.startActivityForResult(MainActivity.this, REQUEST_CODE_FOR_RECORD_VIDEO, VideoInputActivity.Q720);
            }
        });
        btnPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                breakPlayer();
            }
        });
    }

    private void breakPlayer() {
        Intent intent = new Intent(MainActivity.this, TextureVideoActivity.class);
        Log.e(TAG, "跳转path=" + finishPath);
        intent.putExtra("path", finishPath);
        startActivityForResult(intent, REQUEST_CODE_FOR_PLAYER);
    }


    /**
     * 小视屏录制回调
     *
     * @param path
     */
    @Override
    public void videoPathCall(String path) {

        Log.e("地址:", path);
        //根据视频地址获取缩略图
        this.path = path;
        first.setText(getFileSize(path));

        finishPath = path;

    }

    @Override
    public void videoFinish(boolean isFinish) {
        if (isFinish) {
            final File mediaFile = VideoInputDialog.getOutputMediaFile();
            currentOutputVideoPath = mediaFile.getAbsolutePath();
            breakPlayer();
            btnPlay.setEnabled(true);

            //获取视频时长  计算压缩进度用
            // ysVideo(mediaFile);
        }

    }

    private void ysVideo(final File mediaFile) {
        MediaMetadataRetriever retr = new MediaMetadataRetriever();
        retr.setDataSource(path);
        String time = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);//获取视频时长
        //7680
        try {
            videoLength = Double.parseDouble(time) / 1000.00;
        } catch (Exception e) {
            e.printStackTrace();
            videoLength = 0.00;
        }
        Log.v(TAG, "videoLength = " + videoLength + "s");


        /**
         * 压缩视频
         */
        CompressorUtils compressorUtils = new CompressorUtils(path, mediaFile.getAbsolutePath(), MainActivity.this);
        compressorUtils.execCommand(new CompressListener() {
            @Override
            public void onExecSuccess(String message) {
                Log.i(TAG, "success " + message);
                progressBar.setVisibility(View.INVISIBLE);
                textAppend(getString(R.string.compress_succeed));
                back.setText(getFileSize(mediaFile.getAbsolutePath()));
                //获取缩略图
                Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(mediaFile.getAbsolutePath(), MediaStore.Video.Thumbnails.MINI_KIND);
                File file = new File(path);
                if (file.isFile()) {
                    file.delete();
                }
                finishPath = mediaFile.getAbsolutePath();

                btnPlay.setEnabled(true);
            }

            @Override
            public void onExecFail(String reason) {
                Log.i(TAG, "fail " + reason);
            }

            @Override
            public void onExecProgress(String message) {
                progressBar.setVisibility(View.VISIBLE);
                textAppend(getString(R.string.compress_progress, message));
                int i = getProgress(message);
                Log.e("进度", i + "");
                progressBar.setProgress(i);
            }
        });
    }

    /**
     * 录制视频回调
     *
     * @param requestCode
     * @param resultCode
     * @param data
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_CODE_FOR_RECORD_VIDEO && resultCode == RESULT_CANCELED) {

        }
        if (requestCode == REQUEST_CODE_FOR_PLAYER && resultCode == RESULT_OK) {
            btnPlay.setEnabled(false);
        }

        if (requestCode == REQUEST_CODE_FOR_RECORD_VIDEO && resultCode == RESULT_OK) {
            String path = data.getStringExtra(VideoInputActivity.INTENT_EXTRA_VIDEO_PATH);
            Log.e("地址:", path);
            //根据视频地址获取缩略图
            this.path = path;
            Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(path, MediaStore.Video.Thumbnails.MINI_KIND);
            first.setText(getFileSize(path));
            finishPath=path;
            breakPlayer();
            btnPlay.setEnabled(true);
           // ysVideo(VideoInputDialog.getOutputMediaFile());
        }
        super.onActivityResult(requestCode, resultCode, data);
    }


    private String getFileSize(String path) {
        File f = new File(path);
        if (!f.exists()) {
            return "0 MB";
        } else {
            long size = f.length();
            return (size / 1024f) / 1024f + "MB";
        }
    }

    int progress = 0;

    private int getProgress(String source) {
        // Duration: 00:00:22.50, start: 0.000000, bitrate: 13995 kb/s

        //progress frame=   28 fps=0.0 q=24.0 size= 107kB time=00:00:00.91 bitrate= 956.4kbits/s
        if (source.contains("start: 0.000000")) {
            return progress;
        }
        Pattern p = Pattern.compile("00:\\d{2}:\\d{2}");
        Matcher m = p.matcher(source);
        if (m.find()) {
            //00:00:00
            String result = m.group(0);
            String temp[] = result.split(":");
            Double seconds = Double.parseDouble(temp[1]) * 60 + Double.parseDouble(temp[2]);

            if (0 != videoLength) {
                Log.v("进度长度", "current second = " + seconds + "/videoLength=" + videoLength);
                progress = (int) (seconds * 100 / videoLength);

                return progress;
            }
            return progress;
        }
        return progress;
    }


    private void textAppend(String text) {
        if (!TextUtils.isEmpty(text)) {
            Log.e("日志", text);
        }
    }
}

**

  • 布局文件:activity_main.xml

**

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/button"
        android:layout_height="60dp"
        android:layout_width="match_parent"
        android:text="录制短视频"/>
    <Button
        android:id="@+id/button2"
        android:layout_height="60dp"
        android:layout_width="match_parent"
        android:text="录制视频"/>

    <!-- 定义一个水平进度条 -->
    <ProgressBar android:layout_width="fill_parent"
        android:visibility="invisible"
        android:layout_height="wrap_content"
        android:max="100"
        android:id="@+id/progressBar"
        style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"/>
    <TextView
        android:gravity="center"
        android:text="压缩前"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/first"
        android:gravity="center"
        android:text=""
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:gravity="center"
        android:text="压缩后"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:gravity="center"
        android:id="@+id/back"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/btnPlay"
        android:layout_height="60dp"
        android:layout_width="match_parent"
        android:text="播放视频"
        android:visibility="visible"
        android:enabled="false"/>
</LinearLayout>

        视频Recorder时的重点顺序,根据官网文档要求顺序最好不要颠倒了否则会报错,在VideoInputDialog类中为了提高录制效果及画质我将 设置了以下属性: mMediaRecorder.setVideoSize(800, 480);// 视频尺寸
mMediaRecorder.setVideoFrameRate(30);// 视频帧频率,这个可忽略,没什么效果
mMediaRecorder.setVideoEncodingBitRate(3 * 1024 * 1024);//越大画质越好但是视频越大

  //初始化 mMediaRecorder 用于录像
    private boolean prepareVideoRecorder(){

        if (mCamera==null)
            return  false;
        mMediaRecorder = new MediaRecorder();
        /**
         * 解锁camera
         * 设置输出格式为mpeg_4(mp4),此格式音频编码格式必须为AAC否则网页无法播放
         */
        mCamera.unlock();
        mMediaRecorder.setCamera(mCamera);
        //声音
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        //视频
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

        // 第3步:设置输出格式和编码格式(针对低于API Level 8版本)
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        //音频编码格式对应应为AAC
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        //视频编码格式对应应为H264
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        //路径
        mMediaRecorder.setOutputFile(getOutputMediaFile().toString());

        //设置分辨率为480P
//        mMediaRecorder.setProfile(CamcorderProfile.get(quality));
        mMediaRecorder.setVideoSize(800, 480);// 视频尺寸
        mMediaRecorder.setVideoFrameRate(30);// 视频帧频率
        mMediaRecorder.setVideoEncodingBitRate(3 * 1024 * 1024);

        mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());

        try {
            if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
                if (cameraFront) {
                    mMediaRecorder.setOrientationHint(270);
                } else {
                    mMediaRecorder.setOrientationHint(90);
                }
            }

            mMediaRecorder.prepare();
        } catch (IllegalStateException e) {
            Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
            releaseMediaRecorder();
            return false;
        } catch (IOException e) {
            Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
            releaseMediaRecorder();
            return false;
        }
        return true;
    }

        播放控件采用了金山云的,感觉他们家的效果很棒,可以调整拉升、旋转,大小设置等功能很齐全,用起来容易上手,个人感觉还不错,挺喜欢的,拿着demo修修改改,终于弄出了自己想要的效果

package com.example.jerei.videodemo;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Toast;
import com.ksyun.media.player.IMediaPlayer;
import com.ksyun.media.player.KSYMediaPlayer;
import com.ksyun.media.player.KSYTextureView;

import java.io.File;
import java.io.IOException;



public class TextureVideoActivity extends Activity implements View.OnClickListener{

    private static final String TAG = "TextureVideoActivity";

    private Context mContext;
    KSYTextureView mVideoView = null;
    private int mVideoWidth = 0;
    private int mVideoHeight = 0;
    private String mDataSource;

    private IMediaPlayer.OnPreparedListener mOnPreparedListener = new IMediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(IMediaPlayer mp) {
            Log.d("VideoPlayer", "OnPrepared");
            mVideoWidth = mVideoView.getVideoWidth();
            mVideoHeight = mVideoView.getVideoHeight();
            // Set Video Scaling Mode
            mVideoView.setVideoScalingMode(KSYMediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
            //start player
            mVideoView.start();

        }
    };


    private IMediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangeListener = new IMediaPlayer.OnVideoSizeChangedListener() {
        @Override
        public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, int sarNum, int sarDen) {
            if (mVideoWidth > 0 && mVideoHeight > 0) {
                if (width != mVideoWidth || height != mVideoHeight) {
                    mVideoWidth = mp.getVideoWidth();
                    mVideoHeight = mp.getVideoHeight();

                    if (mVideoView != null)
                        mVideoView.setVideoScalingMode(KSYMediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
                }
            }
        }
    };

    private IMediaPlayer.OnSeekCompleteListener mOnSeekCompletedListener = new IMediaPlayer.OnSeekCompleteListener() {
        @Override
        public void onSeekComplete(IMediaPlayer mp) {
            Log.e(TAG, "onSeekComplete...............");
        }
    };

    private IMediaPlayer.OnCompletionListener mOnCompletionListener = new IMediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(IMediaPlayer mp) {
            //Toast.makeText(mContext, "OnCompletionListener, play complete.", Toast.LENGTH_LONG).show();
            //videoPlayEnd();
            mVideoView.start();
        }
    };

    private IMediaPlayer.OnErrorListener mOnErrorListener = new IMediaPlayer.OnErrorListener() {
        @Override
        public boolean onError(IMediaPlayer mp, int what, int extra) {
            switch (what) {
                //case KSYVideoView.MEDIA_ERROR_UNKNOWN:
                // Log.e(TAG, "OnErrorListener, Error Unknown:" + what + ",extra:" + extra);
                //  break;
                default:
                    Log.e(TAG, "OnErrorListener, Error:" + what + ",extra:" + extra);
            }

            videoPlayEnd();

            return false;
        }
    };

    public IMediaPlayer.OnInfoListener mOnInfoListener = new IMediaPlayer.OnInfoListener() {
        @Override
        public boolean onInfo(IMediaPlayer iMediaPlayer, int i, int i1) {
            switch (i) {
                case KSYMediaPlayer.MEDIA_INFO_BUFFERING_START:
                    Log.d(TAG, "Buffering Start.");
                    break;
                case KSYMediaPlayer.MEDIA_INFO_BUFFERING_END:
                    Log.d(TAG, "Buffering End.");
                    break;
                case KSYMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START:
                    Toast.makeText(mContext, "Audio Rendering Start", Toast.LENGTH_SHORT).show();
                    break;
                case KSYMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:
                    Toast.makeText(mContext, "Video Rendering Start", Toast.LENGTH_SHORT).show();
                    break;
                case KSYMediaPlayer.MEDIA_INFO_SUGGEST_RELOAD:
                    // Player find a new stream(video or audio), and we could reload the video.
                    if (mVideoView != null)
                        mVideoView.reload(mDataSource, false, KSYMediaPlayer.KSYReloadMode.KSY_RELOAD_MODE_ACCURATE);
                    break;
                case KSYMediaPlayer.MEDIA_INFO_RELOADED:
                    Toast.makeText(mContext, "Succeed to reload video.", Toast.LENGTH_SHORT).show();
                    Log.d(TAG, "Succeed to reload video.");
                    return false;
            }
            return false;
        }
    };



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this.getApplicationContext();
        setContentView(R.layout.texture_player);
        mVideoView = (KSYTextureView) findViewById(R.id.texture_view);
        mVideoView.setKeepScreenOn(true);
        this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
        mDataSource = getIntent().getStringExtra("path");
        mVideoView.setOnCompletionListener(mOnCompletionListener);
        mVideoView.setOnPreparedListener(mOnPreparedListener);
        mVideoView.setOnInfoListener(mOnInfoListener);
        mVideoView.setOnVideoSizeChangedListener(mOnVideoSizeChangeListener);
        mVideoView.setOnErrorListener(mOnErrorListener);
        mVideoView.setOnSeekCompleteListener(mOnSeekCompletedListener);
        mVideoView.setScreenOnWhilePlaying(true);
        mVideoView.setBufferTimeMax(3.0f);
        mVideoView.setTimeout(5, 30);
        mVideoView.setDecodeMode(KSYMediaPlayer.KSYDecodeMode.KSY_DECODE_MODE_AUTO);
        try {
            mVideoView.setDataSource(mDataSource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        mVideoView.prepareAsync();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mVideoView = null;
    }

    @Override
    protected void onPause() {
        super.onPause();

        if (mVideoView != null) {
            mVideoView.runInBackground(true);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mVideoView != null) {
            mVideoView.runInForeground();
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            videoPlayEnd();
        }

        return super.onKeyDown(keyCode, event);
    }

    @Override
    public int getChangingConfigurations() {
        return super.getChangingConfigurations();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

    private void videoPlayEnd() {
        if (mVideoView != null) {
            mVideoView.release();
            mVideoView = null;
        }
        finish();
    }

    @Override
    public void onClick(View v) {
        final Intent intent = getIntent();
        switch (v.getId()){
            case R.id.tv_del:
                File file = new File(mDataSource);
                if (file.isFile()) {
                    file.delete();
                }
                setResult(Activity.RESULT_OK, intent);
                videoPlayEnd();
                break;
            case R.id.tv_relese:
                Toast.makeText(TextureVideoActivity.this, "点击了发布按钮", Toast.LENGTH_SHORT).show();
                //进行数据发布操作
                setResult(Activity.RESULT_OK, intent);
                videoPlayEnd();
                break;
        }

    }
}

**

  • 布局文件 :texture_player.xml

**

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/player_black">

    <com.ksyun.media.player.KSYTextureView
        android:id="@+id/texture_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        tools:targetApi="ice_cream_sandwich" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_del"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="20dp"
            android:text="取消"
            android:onClick="onClick"
            android:gravity="center"
            android:textColor="#fff"
            android:textSize="16sp" />
        <TextView
            android:id="@+id/tv_relese"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="20dp"
            android:text="发布"
            android:onClick="onClick"
            android:gravity="center"
            android:textColor="#fff"
            android:textSize="16sp" />
    </LinearLayout>
</RelativeLayout>


点击此处下载源文件

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值