├── README.md ├── WeiXinRecorded.apk └── WeiXinRecorded ├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── dictionaries │ └── zhaoshuang.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── app ├── .gitignore ├── WeiXinRecordDemo.apk ├── build.gradle ├── libs │ └── armeabi-v7a │ │ └── libutility.so ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ ├── example │ │ └── zhaoshuang │ │ │ └── weixinrecordeddemo │ │ │ ├── BaseActivity.java │ │ │ ├── EditVideoActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── MyApplication.java │ │ │ ├── MyVideoView.java │ │ │ ├── RecordedButton.java │ │ │ ├── TouchView.java │ │ │ ├── TuyaView.java │ │ │ └── VideoPlayActivity.java │ │ └── yixia │ │ ├── camera │ │ ├── AudioRecorder.java │ │ ├── IMediaRecorder.java │ │ ├── MediaRecorderBase.java │ │ ├── MediaRecorderNative.java │ │ ├── VCamera.java │ │ ├── model │ │ │ ├── MediaObject.java │ │ │ └── MediaThemeObject.java │ │ └── util │ │ │ ├── DeviceUtils.java │ │ │ ├── FileUtils.java │ │ │ ├── Log.java │ │ │ └── StringUtils.java │ │ └── videoeditor │ │ └── adapter │ │ └── UtilityAdapter.java │ └── res │ ├── color │ └── dialog_pro_color.xml │ ├── drawable │ ├── color1.xml │ ├── color2.xml │ ├── color3.xml │ ├── color4.xml │ ├── color5.xml │ └── yuanjiao.xml │ ├── layout │ ├── activity_edit_video.xml │ ├── activity_main.xml │ ├── activity_video_play.xml │ └── dialog_loading.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ ├── back.png │ ├── back2.png │ ├── color_click.png │ ├── edit_delete.png │ ├── expression1.jpg │ ├── expression2.jpg │ ├── expression3.jpg │ ├── expression4.jpg │ ├── expression5.jpg │ ├── expression6.jpg │ ├── expression7.jpg │ ├── expression8.jpg │ ├── finish.png │ ├── ic_launcher.png │ ├── icon.png │ ├── icon_click.png │ ├── pen.png │ ├── pen_click.png │ ├── text.png │ ├── text_click.png │ ├── video_delete.png │ └── video_finish.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /README.md: -------------------------------------------------------------------------------- 1 | # WeiXinRecordedDemo 2 | 3 | 简书链接 4 | 5 | ``` 6 | 功能主要包含5点: 7 | 8 | 1.基于ffmpeg的视频拍摄及合成; 9 | 10 | 2.自定义拍摄按钮, 长按放大并且显示拍摄进度; 11 | 12 | 3.自定义view, 实现手绘涂鸦; 13 | 14 | 4.自定义可触摸旋转缩放位移的表情文字view; 15 | 16 | 5.基于ffmpeg的图片和视频合成处理. 17 | ``` 18 | 19 | 效果如图: 20 | 21 | ![image](http://p1.bpimg.com/1949/91265a1c314bbbcb.gif) 22 | 23 | 界面风格高仿微信, 只不过微信的编辑处理是作用于图片, 而我们的是基于视频, 所以如果你有需求, 把视频编辑处理换成图片编辑, 更是简单. 24 | 25 | ##1.实现使用ffmpeg录制视频 26 | ![image](http://om4qaz231.bkt.clouddn.com/WeiXin1.png) 27 | 28 | 首先导入lib库和ffmpeg的录制java文件, 我使用的是第三方VCamera封装的ffmpeg, 他没有jar包, 所以需要将con.yixia包下的所有文件都copy过来, 29 | 30 | ![image](http://om4qaz231.bkt.clouddn.com/WeiXin2.png) 31 | 32 | 然后在application里面初始化VCamera: 33 | 34 | ![image](http://om4qaz231.bkt.clouddn.com/WeiXin3.png) 35 | ![image](http://om4qaz231.bkt.clouddn.com/WeiXin4.png) 36 | 37 | 这个时候, 你就可以在SurfaceView上看见拍摄预览界面了, 38 | 39 | 然后mMediaRecorder.startRecord()拍摄视频, 40 | 41 | 调用mMediaRecorder.stopRecord()停止录制视频, 42 | 43 | 因为拍摄出来的文件是ts视频流, 所以还要调用mMediaRecorder.startEncoding()开始合成MP4视频文件. 44 | 45 | MediaRecorderBase类还可以设置视频各个参数, 如: 46 | 47 | ![image](http://om4qaz231.bkt.clouddn.com/WeiXin5.png) 48 | 49 | ##2.自定义拍摄按钮, 长按放大并且显示拍摄进度 50 | ![image](http://om4qaz231.bkt.clouddn.com/WeiXin6.png) 51 | 52 | 自定义RecordedButton继承View, 在onDraw里分三部分绘制: 53 | ![image](http://om4qaz231.bkt.clouddn.com/WeiXin7.png) 54 | 55 | 在拍摄模式下, 改变radius(半径), 达到放大或者缩小外圈和内圈圆的效果, 不断增加girth值达到显示拍摄进度的效果, 是不是很简单. 56 | 57 | ##3.自定义view, 实现手绘涂鸦 58 | 59 | ![image](http://om4qaz231.bkt.clouddn.com/WeiXin8.png) 60 | 61 | 自定义TuyaView继承View, 重写onTouch(), 在手指点下和移动时实时绘制触摸轨迹: 62 | 63 | ![image](http://om4qaz231.bkt.clouddn.com/WeiXin9.png) 64 | 65 | 在手指按下时创建new Path()对象, 记录本次手指触摸移动轨迹, 并且实时调用invalidate() 达到不断调用onDraw()的目的, 然后使用canvas.drawPath(path,paint)绘制触摸路径, 是不是非常简单. 66 | 67 | ##4.自定义可触摸旋转缩放位移的表情文字view 68 | 69 | ![image](http://om4qaz231.bkt.clouddn.com/WeiXin10.png) 70 | 71 | 这个view稍微有点麻烦, 但我单独写了一篇文章点击跳转, 非常详细的讲解了这个view, 而且封装的非常好, 只要addView到布局中就可以使用了, 大家可以点击链接过去看一下. 72 | 73 | ##5.基于ffmpeg的图片和视频合成处理 74 | 75 | 这也是demo的最后一步, 将涂鸦,和表情文字全部合成到视频当中, 首先是得到需要合成的图片, 我们可以通过view.draw(Canvas canvas),得到布局的bitmap: 76 | 77 | ![image](http://om4qaz231.bkt.clouddn.com/WeiXin11.png) 78 | 79 | 然后通过ffmpeg来执行图片和视频的合成, 具体语句是这样的: 80 | 81 | ffmpeg -i videoPath -i imagePath -filter_complex overlay=0:0 -vcodec libx264 -profile:v baseline -preset ultrafast -b:v 3000k -g 25 -f mp4 outPath 82 | 83 | 我把参数讲解一下: videoPath代表你要编辑视频的路径 84 | 85 | imagePath代表你要合成的图片路径 86 | 87 | outPath是合成之后的输出视频路径 88 | 89 | 这些是我们需要替换的参数至于一些别的, 例如: 90 | 91 | overlay=0:0表示图片坐标位置, 0:0表示x轴=0,y轴=0 92 | 93 | -vcodec后面表示视频输出格式, 3000k码率, 25帧数, 总之ffmpeg的参数还有很多, 如果感兴趣可以去ffmpeg官网看命令大全. 94 | 95 | ![image](http://om4qaz231.bkt.clouddn.com/WeiXin12.png) 96 | 97 | 向UtilityAdapter.FFmpegRun()里传入ffmpeg语句就可以执行了, 返回值 int , 如果等于0就是成功, 非0则是失败, FFmpegRun()方法的第一参数如果传入空字符串就是异步执行视频处理, 否则就是同步执行, 这点要注意. 98 | 99 | #如果这篇文章对大家有所帮助, 希望可以点一下star哦, 我会经常在上面分享我工作中遇到的问题和酷炫的特效实现. 100 | -------------------------------------------------------------------------------- /WeiXinRecorded.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded.apk -------------------------------------------------------------------------------- /WeiXinRecorded/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /WeiXinRecorded/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /WeiXinRecorded/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /WeiXinRecorded/.idea/dictionaries/zhaoshuang.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /WeiXinRecorded/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /WeiXinRecorded/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Abstraction issuesJava 39 | 40 | 41 | Android 42 | 43 | 44 | Android > Lint > Correctness 45 | 46 | 47 | Android > Lint > Correctness > Messages 48 | 49 | 50 | Android > Lint > Internationalization > Bidirectional Text 51 | 52 | 53 | Android > Lint > Performance 54 | 55 | 56 | Android > Lint > Security 57 | 58 | 59 | Android > Lint > Usability 60 | 61 | 62 | Assignment issuesGroovy 63 | 64 | 65 | Assignment issuesJava 66 | 67 | 68 | Class structureJava 69 | 70 | 71 | Cloning issuesJava 72 | 73 | 74 | Code style issuesJava 75 | 76 | 77 | Concurrency annotation issuesJava 78 | 79 | 80 | Control FlowGroovy 81 | 82 | 83 | Control flow issuesJava 84 | 85 | 86 | CorrectnessLintAndroid 87 | 88 | 89 | Data flow issuesJava 90 | 91 | 92 | Declaration redundancyJava 93 | 94 | 95 | Dependency issuesJava 96 | 97 | 98 | Encapsulation issuesJava 99 | 100 | 101 | Error handlingGroovy 102 | 103 | 104 | Error handlingJava 105 | 106 | 107 | General 108 | 109 | 110 | Groovy 111 | 112 | 113 | Inheritance issuesJava 114 | 115 | 116 | Initialization issuesJava 117 | 118 | 119 | Internationalization issuesJava 120 | 121 | 122 | JUnit issuesJava 123 | 124 | 125 | Java 126 | 127 | 128 | LintAndroid 129 | 130 | 131 | Logging issuesJava 132 | 133 | 134 | Memory issuesJava 135 | 136 | 137 | Method MetricsGroovy 138 | 139 | 140 | Modularization issuesJava 141 | 142 | 143 | Naming ConventionsGroovy 144 | 145 | 146 | Naming conventionsJava 147 | 148 | 149 | Numeric issuesJava 150 | 151 | 152 | Packaging issuesJava 153 | 154 | 155 | Pattern Validation 156 | 157 | 158 | Performance issuesJava 159 | 160 | 161 | Portability issuesJava 162 | 163 | 164 | Potentially confusing code constructsGroovy 165 | 166 | 167 | Probable bugsGroovy 168 | 169 | 170 | Probable bugsJava 171 | 172 | 173 | Resource management issuesJava 174 | 175 | 176 | Security issuesJava 177 | 178 | 179 | Serialization issuesJava 180 | 181 | 182 | TestNGJava 183 | 184 | 185 | Threading issuesGroovy 186 | 187 | 188 | Threading issuesJava 189 | 190 | 191 | Visibility issuesJava 192 | 193 | 194 | 195 | 196 | Android 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 218 | 219 | $USER_HOME$/.subversion 220 | 221 | 222 | 223 | 224 | 225 | Android API 17 Platform 226 | 227 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 243 | 244 | 245 | 246 | 247 | 248 | -------------------------------------------------------------------------------- /WeiXinRecorded/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WeiXinRecorded/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/WeiXinRecordDemo.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/WeiXinRecordDemo.apk -------------------------------------------------------------------------------- /WeiXinRecorded/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "24.0.2" 6 | defaultConfig { 7 | applicationId "com.example.zhaoshuang.weixinrecordeddemo" 8 | minSdkVersion 17 9 | targetSdkVersion 17 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | sourceSets { 21 | main { 22 | jniLibs.srcDirs = ['libs'] 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | compile fileTree(include: ['*.jar'], dir: 'libs') 29 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 30 | exclude group: 'com.android.support', module: 'support-annotations' 31 | }) 32 | compile 'com.android.support:appcompat-v7:23.1.1' 33 | testCompile 'junit:junit:4.12' 34 | compile 'com.android.support:recyclerview-v7:23.2.1' 35 | } 36 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/libs/armeabi-v7a/libutility.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/libs/armeabi-v7a/libutility.so -------------------------------------------------------------------------------- /WeiXinRecorded/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/zhaoshuang/Documents/AndroidStudioSDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/example/zhaoshuang/weixinrecordeddemo/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.zhaoshuang.weixinrecordeddemo; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.os.Build; 6 | import android.support.v4.content.ContextCompat; 7 | import android.support.v7.app.AlertDialog; 8 | import android.view.View; 9 | import android.widget.ProgressBar; 10 | import android.widget.TextView; 11 | 12 | /** 13 | * Created by zhaoshuang on 17/2/23. 14 | */ 15 | 16 | public abstract class BaseActivity extends Activity{ 17 | 18 | private AlertDialog progressDialog; 19 | 20 | public TextView showProgressDialog() { 21 | 22 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 23 | builder.setCancelable(false); 24 | View view = View.inflate(this, R.layout.dialog_loading, null); 25 | builder.setView(view); 26 | ProgressBar pb_loading = (ProgressBar) view.findViewById(R.id.pb_loading); 27 | TextView tv_hint = (TextView) view.findViewById(R.id.tv_hint); 28 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 29 | pb_loading.setIndeterminateTintList(ContextCompat.getColorStateList(this, R.color.dialog_pro_color)); 30 | } 31 | tv_hint.setText("视频编译中"); 32 | progressDialog = builder.create(); 33 | progressDialog.show(); 34 | 35 | return tv_hint; 36 | } 37 | 38 | public void closeProgressDialog() { 39 | try { 40 | if (progressDialog != null) { 41 | progressDialog.dismiss(); 42 | } 43 | } catch (Exception e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/example/zhaoshuang/weixinrecordeddemo/EditVideoActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.zhaoshuang.weixinrecordeddemo; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.graphics.Bitmap; 9 | import android.graphics.Canvas; 10 | import android.graphics.Color; 11 | import android.graphics.Matrix; 12 | import android.graphics.drawable.BitmapDrawable; 13 | import android.media.MediaPlayer; 14 | import android.os.AsyncTask; 15 | import android.os.Bundle; 16 | import android.text.Editable; 17 | import android.text.TextUtils; 18 | import android.text.TextWatcher; 19 | import android.view.MotionEvent; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | import android.view.ViewTreeObserver; 23 | import android.view.WindowManager; 24 | import android.view.inputmethod.InputMethodManager; 25 | import android.widget.EditText; 26 | import android.widget.ImageView; 27 | import android.widget.LinearLayout; 28 | import android.widget.RelativeLayout; 29 | import android.widget.TextView; 30 | 31 | import com.yixia.camera.MediaRecorderBase; 32 | import com.yixia.videoeditor.adapter.UtilityAdapter; 33 | 34 | import java.io.BufferedOutputStream; 35 | import java.io.File; 36 | import java.io.FileOutputStream; 37 | import java.io.IOException; 38 | 39 | /** 40 | * Created by zhaoshuang on 17/2/21. 41 | */ 42 | 43 | public class EditVideoActivity extends BaseActivity implements View.OnClickListener{ 44 | 45 | private MyVideoView vv_play; 46 | private LinearLayout ll_color; 47 | 48 | private int[] drawableBg = new int[]{R.drawable.color1, R.drawable.color2, R.drawable.color3, R.drawable.color4, R.drawable.color5}; 49 | private int[] colors = new int[]{R.color.color1, R.color.color2, R.color.color3, R.color.color4, R.color.color5}; 50 | private int[] expressions = new int[]{R.mipmap.expression1, R.mipmap.expression2, R.mipmap.expression3, R.mipmap.expression4, 51 | R.mipmap.expression5, R.mipmap.expression6, R.mipmap.expression7, R.mipmap.expression8}; 52 | private int dp20; 53 | private int dp25; 54 | 55 | private int currentColorPosition; 56 | private TuyaView tv_video; 57 | private ImageView iv_pen; 58 | private ImageView iv_icon; 59 | private RelativeLayout rl_expression; 60 | private RelativeLayout rl_touch_view; 61 | private RelativeLayout rl_edit_text; 62 | private EditText et_tag; 63 | private TextView tv_tag; 64 | private InputMethodManager manager; 65 | private int windowHeight; 66 | private int windowWidth; 67 | private String path; 68 | private RelativeLayout rl_tuya; 69 | private RelativeLayout rl_close; 70 | private RelativeLayout rl_title; 71 | private RelativeLayout rl_bottom; 72 | private TextView tv_hint_delete; 73 | private int dp100; 74 | private TextView textView; 75 | 76 | @Override 77 | protected void onCreate(Bundle savedInstanceState) { 78 | super.onCreate(savedInstanceState); 79 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 80 | setContentView(R.layout.activity_edit_video); 81 | 82 | manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 83 | windowWidth = getWindowManager().getDefaultDisplay().getWidth(); 84 | windowHeight = getWindowManager().getDefaultDisplay().getHeight(); 85 | 86 | dp100 = (int) getResources().getDimension(R.dimen.dp100); 87 | 88 | initUI(); 89 | 90 | Intent intent = getIntent(); 91 | path = intent.getStringExtra("path"); 92 | 93 | vv_play.setVideoPath(path); 94 | vv_play.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { 95 | @Override 96 | public void onPrepared(MediaPlayer mp) { 97 | vv_play.setLooping(true); 98 | vv_play.start(); 99 | } 100 | }); 101 | 102 | //当进行涂鸦操作时, 隐藏标题栏和底部工具栏 103 | tv_video.setOnTouchListener(new TuyaView.OnTouchListener() { 104 | @Override 105 | public void onDown() { 106 | changeMode(false); 107 | } 108 | @Override 109 | public void onUp() { 110 | changeMode(true); 111 | } 112 | }); 113 | } 114 | 115 | private void initUI() { 116 | 117 | vv_play = (MyVideoView) findViewById(R.id.vv_play); 118 | RelativeLayout rv_pen = (RelativeLayout) findViewById(R.id.rv_pen); 119 | RelativeLayout rv_icon = (RelativeLayout) findViewById(R.id.rv_icon); 120 | RelativeLayout rv_text = (RelativeLayout) findViewById(R.id.rv_text); 121 | ll_color = (LinearLayout) findViewById(R.id.ll_color); 122 | tv_video = (TuyaView) findViewById(R.id.tv_video); 123 | rl_expression = (RelativeLayout) findViewById(R.id.rl_expression); 124 | rl_touch_view = (RelativeLayout) findViewById(R.id.rl_touch_view); 125 | TextView tv_close = (TextView) findViewById(R.id.tv_close); 126 | TextView tv_finish = (TextView) findViewById(R.id.tv_finish); 127 | rl_edit_text = (RelativeLayout) findViewById(R.id.rl_edit_text); 128 | et_tag = (EditText) findViewById(R.id.et_tag); 129 | tv_tag = (TextView) findViewById(R.id.tv_tag); 130 | TextView tv_finish_video = (TextView) findViewById(R.id.tv_finish_video); 131 | iv_pen = (ImageView) findViewById(R.id.iv_pen); 132 | iv_icon = (ImageView) findViewById(R.id.iv_icon); 133 | rl_tuya = (RelativeLayout) findViewById(R.id.rl_tuya); 134 | rl_close = (RelativeLayout) findViewById(R.id.rl_close); 135 | rl_title = (RelativeLayout) findViewById(R.id.rl_title); 136 | rl_bottom = (RelativeLayout) findViewById(R.id.rl_bottom); 137 | tv_hint_delete = (TextView) findViewById(R.id.tv_hint_delete); 138 | 139 | RelativeLayout rl_back = (RelativeLayout) findViewById(R.id.rl_back); 140 | 141 | rv_pen.setOnClickListener(this); 142 | rv_icon.setOnClickListener(this); 143 | rv_text.setOnClickListener(this); 144 | 145 | rl_back.setOnClickListener(this); 146 | ll_color.setOnClickListener(this); 147 | tv_close.setOnClickListener(this); 148 | tv_finish.setOnClickListener(this); 149 | tv_finish_video.setOnClickListener(this); 150 | rl_close.setOnClickListener(this); 151 | 152 | initColors(); 153 | initExpression(); 154 | 155 | et_tag.addTextChangedListener(new TextWatcher() { 156 | @Override 157 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 158 | 159 | } 160 | @Override 161 | public void onTextChanged(CharSequence s, int start, int before, int count) { 162 | 163 | } 164 | @Override 165 | public void afterTextChanged(Editable s) { 166 | tv_tag.setText(s.toString()); 167 | } 168 | }); 169 | } 170 | 171 | //更改界面模式 172 | private void changeMode(boolean flag){ 173 | if(flag){ 174 | rl_title.setVisibility(View.VISIBLE); 175 | rl_bottom.setVisibility(View.VISIBLE); 176 | }else{ 177 | rl_title.setVisibility(View.GONE); 178 | rl_bottom.setVisibility(View.GONE); 179 | } 180 | } 181 | 182 | private void initExpression() { 183 | 184 | int dp80 = (int) getResources().getDimension(R.dimen.dp80); 185 | int dp10 = (int) getResources().getDimension(R.dimen.dp10); 186 | for (int x=0; x 0) { 525 | addTextToWindow(); 526 | } 527 | break; 528 | case R.id.tv_finish_video: 529 | new AsyncTask() { 530 | @Override 531 | protected void onPreExecute() { 532 | textView = showProgressDialog(); 533 | } 534 | @Override 535 | protected String doInBackground(Void... params) { 536 | return mergeImage(); 537 | } 538 | @Override 539 | protected void onPostExecute(String result) { 540 | closeProgressDialog(); 541 | if(!TextUtils.isEmpty(result)) { 542 | Intent intent = new Intent(EditVideoActivity.this, VideoPlayActivity.class); 543 | intent.putExtra("path", result); 544 | startActivity(intent); 545 | } 546 | } 547 | }.execute(); 548 | break; 549 | case R.id.rl_close: 550 | onBackPressed(); 551 | break; 552 | } 553 | } 554 | } 555 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/example/zhaoshuang/weixinrecordeddemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.zhaoshuang.weixinrecordeddemo; 2 | 3 | import android.Manifest; 4 | import android.animation.ValueAnimator; 5 | import android.content.Intent; 6 | import android.graphics.PixelFormat; 7 | import android.hardware.Camera; 8 | import android.media.MediaPlayer; 9 | import android.os.AsyncTask; 10 | import android.os.Bundle; 11 | import android.os.Handler; 12 | import android.os.Message; 13 | import android.support.v4.app.ActivityCompat; 14 | import android.text.TextUtils; 15 | import android.util.Log; 16 | import android.view.SurfaceView; 17 | import android.view.View; 18 | import android.view.WindowManager; 19 | import android.widget.ImageView; 20 | import android.widget.TextView; 21 | 22 | import com.yixia.camera.MediaRecorderBase; 23 | import com.yixia.camera.MediaRecorderNative; 24 | import com.yixia.camera.VCamera; 25 | import com.yixia.camera.model.MediaObject; 26 | import com.yixia.camera.util.FileUtils; 27 | import com.yixia.videoeditor.adapter.UtilityAdapter; 28 | 29 | import java.io.File; 30 | import java.util.LinkedList; 31 | 32 | /** 33 | * 仿新版微信录制视频 34 | * 基于ffmpeg视频编译 35 | * 使用的是免费第三方VCamera 36 | * Created by zhaoshuang on 17/2/8. 37 | */ 38 | public class MainActivity extends BaseActivity implements MediaRecorderBase.OnEncodeListener { 39 | 40 | private static final int REQUEST_KEY = 100; 41 | private MediaRecorderNative mMediaRecorder; 42 | private MediaObject mMediaObject; 43 | private SurfaceView sv_ffmpeg; 44 | private RecordedButton rb_start; 45 | private int maxDuration = 3000; 46 | private boolean recordedOver; 47 | private MyVideoView vv_play; 48 | private ImageView iv_finish; 49 | private ImageView iv_back; 50 | private float dp100; 51 | private TextView tv_hint; 52 | private float backX = -1; 53 | private TextView textView; 54 | 55 | @Override 56 | protected void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 59 | setContentView(R.layout.activity_main); 60 | 61 | sv_ffmpeg = (SurfaceView) findViewById(R.id.sv_ffmpeg); 62 | rb_start = (RecordedButton) findViewById(R.id.rb_start); 63 | vv_play = (MyVideoView) findViewById(R.id.vv_play); 64 | iv_finish = (ImageView) findViewById(R.id.iv_finish); 65 | iv_back = (ImageView) findViewById(R.id.iv_back); 66 | tv_hint = (TextView) findViewById(R.id.tv_hint); 67 | 68 | dp100 = getResources().getDimension(R.dimen.dp100); 69 | 70 | initMediaRecorder(); 71 | 72 | rb_start.setMax(maxDuration); 73 | rb_start.setOnGestureListener(new RecordedButton.OnGestureListener() { 74 | @Override 75 | public void onLongClick() { 76 | mMediaRecorder.startRecord(); 77 | myHandler.sendEmptyMessageDelayed(0, 50); 78 | } 79 | @Override 80 | public void onClick() { 81 | 82 | } 83 | @Override 84 | public void onLift() { 85 | recordedOver = true; 86 | videoFinish(); 87 | } 88 | @Override 89 | public void onOver() { 90 | recordedOver = true; 91 | rb_start.closeButton(); 92 | videoFinish(); 93 | } 94 | }); 95 | 96 | iv_back.setOnClickListener(new View.OnClickListener() { 97 | @Override 98 | public void onClick(View v) { 99 | onBackPressed(); 100 | } 101 | }); 102 | 103 | ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 0); 104 | } 105 | 106 | /** 107 | * 初始化视频拍摄状态 108 | */ 109 | private void initMediaRecorderState(){ 110 | 111 | vv_play.setVisibility(View.GONE); 112 | vv_play.pause(); 113 | 114 | iv_back.setX(backX); 115 | iv_finish.setX(backX); 116 | 117 | tv_hint.setVisibility(View.VISIBLE); 118 | rb_start.setVisibility(View.VISIBLE); 119 | 120 | lastValue = 0; 121 | } 122 | 123 | private void videoFinish() { 124 | 125 | textView = showProgressDialog(); 126 | mMediaRecorder.stopRecord(); 127 | //开始合成视频, 异步 128 | mMediaRecorder.startEncoding(); 129 | } 130 | 131 | float lastValue; 132 | private void startAnim() { 133 | 134 | rb_start.setVisibility(View.GONE); 135 | ValueAnimator va = ValueAnimator.ofFloat(0, dp100).setDuration(300); 136 | va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 137 | @Override 138 | public void onAnimationUpdate(ValueAnimator animation) { 139 | 140 | float value = (float) animation.getAnimatedValue(); 141 | float dis = value-lastValue; 142 | iv_back.setX(iv_back.getX()-dis); 143 | iv_finish.setX(iv_finish.getX()+dis); 144 | lastValue = value; 145 | } 146 | }); 147 | va.start(); 148 | } 149 | 150 | private Handler myHandler = new Handler(){ 151 | @Override 152 | public void handleMessage(Message msg) { 153 | if(!recordedOver){ 154 | rb_start.setProgress(mMediaObject.getDuration()); 155 | myHandler.sendEmptyMessageDelayed(0, 50); 156 | tv_hint.setVisibility(View.GONE); 157 | } 158 | } 159 | }; 160 | 161 | /** 162 | * 初始化录制对象 163 | */ 164 | private void initMediaRecorder() { 165 | 166 | mMediaRecorder = new MediaRecorderNative(); 167 | mMediaRecorder.setOnEncodeListener(this); 168 | String key = String.valueOf(System.currentTimeMillis()); 169 | //设置缓存文件夹 170 | mMediaObject = mMediaRecorder.setOutputDirectory(key, VCamera.getVideoCachePath()); 171 | //设置视频预览源 172 | mMediaRecorder.setSurfaceHolder(sv_ffmpeg.getHolder()); 173 | //准备 174 | mMediaRecorder.prepare(); 175 | //滤波器相关 176 | UtilityAdapter.freeFilterParser(); 177 | UtilityAdapter.initFilterParser(); 178 | } 179 | 180 | @Override 181 | protected void onResume() { 182 | super.onResume(); 183 | mMediaRecorder.startPreview(); 184 | } 185 | 186 | @Override 187 | protected void onPause() { 188 | super.onPause(); 189 | mMediaRecorder.stopPreview(); 190 | } 191 | 192 | @Override 193 | public void onBackPressed() { 194 | if(rb_start.getVisibility() != View.VISIBLE) { 195 | initMediaRecorderState(); 196 | LinkedList medaParts = mMediaObject.getMedaParts(); 197 | for (MediaObject.MediaPart part : medaParts) { 198 | mMediaObject.removePart(part, true); 199 | } 200 | mMediaRecorder.startPreview(); 201 | }else{ 202 | super.onBackPressed(); 203 | } 204 | } 205 | 206 | @Override 207 | public void onWindowFocusChanged(boolean hasFocus) { 208 | 209 | if(backX == -1) { 210 | backX = iv_back.getX(); 211 | } 212 | } 213 | 214 | @Override 215 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 216 | if(resultCode == RESULT_OK){ 217 | if(requestCode == REQUEST_KEY){ 218 | LinkedList medaParts = mMediaObject.getMedaParts(); 219 | for (MediaObject.MediaPart part : medaParts){ 220 | mMediaObject.removePart(part, true); 221 | } 222 | deleteDir(MyApplication.VIDEO_PATH); 223 | } 224 | } 225 | } 226 | 227 | /** 228 | * 删除文件夹下所有文件 229 | */ 230 | public static void deleteDir(String dirPath){ 231 | 232 | File dir = new File(dirPath); 233 | if(dir.exists() && dir.isDirectory()){ 234 | File[] files = dir.listFiles(); 235 | for(File f: files){ 236 | deleteDir(f.getAbsolutePath()); 237 | } 238 | }else if(dir.exists()) { 239 | dir.delete(); 240 | } 241 | } 242 | 243 | @Override 244 | public void onEncodeStart() { 245 | Log.i("Log.i", "onEncodeStart"); 246 | } 247 | 248 | @Override 249 | public void onEncodeProgress(int progress) { 250 | if(textView != null){ 251 | textView.setText("视频编译中"+progress); 252 | } 253 | } 254 | 255 | 256 | /** 257 | * 视频编辑完成 258 | */ 259 | @Override 260 | public void onEncodeComplete() { 261 | closeProgressDialog(); 262 | final String path = mMediaObject.getOutputTempVideoPath(); 263 | if(!TextUtils.isEmpty(path)){ 264 | iv_finish.setOnClickListener(new View.OnClickListener() { 265 | @Override 266 | public void onClick(View v) { 267 | Intent intent = new Intent(MainActivity.this, EditVideoActivity.class); 268 | intent.putExtra("path", path); 269 | startActivityForResult(intent, REQUEST_KEY); 270 | initMediaRecorderState(); 271 | } 272 | }); 273 | 274 | vv_play.setVisibility(View.VISIBLE); 275 | vv_play.setVideoPath(path); 276 | vv_play.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { 277 | @Override 278 | public void onPrepared(MediaPlayer mp) { 279 | vv_play.setLooping(true); 280 | vv_play.start(); 281 | } 282 | }); 283 | vv_play.start(); 284 | 285 | recordedOver = false; 286 | startAnim(); 287 | } 288 | } 289 | 290 | @Override 291 | public void onEncodeError() { 292 | Log.i("Log.i", "onEncodeError"); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/example/zhaoshuang/weixinrecordeddemo/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.zhaoshuang.weixinrecordeddemo; 2 | 3 | import android.app.Application; 4 | 5 | import com.yixia.camera.VCamera; 6 | 7 | import java.io.File; 8 | 9 | /** 10 | * Created by zhaoshuang on 17/2/8. 11 | */ 12 | 13 | public class MyApplication extends Application { 14 | 15 | public static String VIDEO_PATH = "/sdcard/WeiXinRecordedDemo/"; 16 | 17 | @Override 18 | public void onCreate() { 19 | 20 | VIDEO_PATH += String.valueOf(System.currentTimeMillis()); 21 | File file = new File(VIDEO_PATH); 22 | if(!file.exists()) file.mkdirs(); 23 | 24 | //设置视频缓存路径 25 | VCamera.setVideoCachePath(VIDEO_PATH); 26 | 27 | // 开启log输出,ffmpeg输出到logcat 28 | VCamera.setDebugMode(true); 29 | 30 | // 初始化拍摄SDK,必须 31 | VCamera.initialize(this); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/example/zhaoshuang/weixinrecordeddemo/MyVideoView.java: -------------------------------------------------------------------------------- 1 | package com.example.zhaoshuang.weixinrecordeddemo; 2 | 3 | import android.content.Context; 4 | import android.graphics.SurfaceTexture; 5 | import android.media.AudioManager; 6 | import android.media.MediaPlayer; 7 | import android.net.Uri; 8 | import android.os.Handler; 9 | import android.os.Message; 10 | import android.util.AttributeSet; 11 | import android.view.Surface; 12 | import android.view.TextureView; 13 | 14 | import java.io.IOException; 15 | 16 | public class MyVideoView extends TextureView implements TextureView.SurfaceTextureListener { 17 | 18 | private MediaPlayer.OnCompletionListener mOnCompletionListener; 19 | private MediaPlayer.OnPreparedListener mOnPreparedListener; 20 | private MediaPlayer.OnErrorListener mOnErrorListener; 21 | private MediaPlayer.OnSeekCompleteListener mOnSeekCompleteListener; 22 | private OnPlayStateListener mOnPlayStateListener; 23 | private MediaPlayer mMediaPlayer = null; 24 | private SurfaceTexture mSurfaceHolder = null; 25 | 26 | private static final int STATE_ERROR = -1; 27 | private static final int STATE_IDLE = 0; 28 | private static final int STATE_PREPARING = 1; 29 | private static final int STATE_PREPARED = 2; 30 | private static final int STATE_PLAYING = 3; 31 | private static final int STATE_PAUSED = 4; 32 | private static final int STATE_STOP = 5; 33 | /** 34 | * PlaybackCompleted状态:文件正常播放完毕,而又没有设置循环播放的话就进入该状态, 35 | * 并会触发OnCompletionListener的onCompletion 36 | * ()方法。此时可以调用start()方法重新从头播放文件,也可以stop()停止MediaPlayer,或者也可以seekTo()来重新定位播放位置。 37 | */ 38 | private static final int STATE_PLAYBACK_COMPLETED = 5; 39 | /** Released/End状态:通过release()方法可以进入End状态 */ 40 | private static final int STATE_RELEASED = 5; 41 | 42 | private int mCurrentState = STATE_IDLE; 43 | private int mTargetState = STATE_IDLE; 44 | 45 | private int mVideoWidth; 46 | private int mVideoHeight; 47 | // private int mSurfaceWidth; 48 | // private int mSurfaceHeight; 49 | 50 | private float mVolumn = -1; 51 | private int mDuration; 52 | private Uri mUri; 53 | 54 | // SurfaceTextureAvailable 55 | 56 | public MyVideoView(Context context, AttributeSet attrs, int defStyle) { 57 | super(context, attrs, defStyle); 58 | initVideoView(); 59 | } 60 | 61 | public MyVideoView(Context context) { 62 | super(context); 63 | initVideoView(); 64 | } 65 | 66 | public MyVideoView(Context context, AttributeSet attrs) { 67 | super(context, attrs); 68 | initVideoView(); 69 | } 70 | 71 | public MediaPlayer getMediaPlayer(){ 72 | return mMediaPlayer; 73 | } 74 | 75 | protected void initVideoView() { 76 | try { 77 | AudioManager mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 78 | mVolumn = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 79 | } catch (UnsupportedOperationException e) { 80 | 81 | } 82 | // mTryCount = 0; 83 | mVideoWidth = 0; 84 | mVideoHeight = 0; 85 | setSurfaceTextureListener(this); 86 | // setFocusable(true); 87 | // setFocusableInTouchMode(true); 88 | // requestFocus(); 89 | mCurrentState = STATE_IDLE; 90 | mTargetState = STATE_IDLE; 91 | } 92 | 93 | public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) { 94 | mOnPreparedListener = l; 95 | } 96 | 97 | public void setOnErrorListener(MediaPlayer.OnErrorListener l) { 98 | mOnErrorListener = l; 99 | } 100 | 101 | public void setOnPlayStateListener(OnPlayStateListener l) { 102 | mOnPlayStateListener = l; 103 | } 104 | 105 | public void setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener l) { 106 | mOnSeekCompleteListener = l; 107 | } 108 | 109 | public static interface OnPlayStateListener { 110 | public void onStateChanged(boolean isPlaying); 111 | } 112 | 113 | public void setOnCompletionListener(MediaPlayer.OnCompletionListener l) { 114 | mOnCompletionListener = l; 115 | } 116 | 117 | public void setVideoPath(String path) { 118 | // if (StringUtils.isNotEmpty(path) && MediaUtils.isNative(path)) { 119 | mTargetState = STATE_PREPARED; 120 | openVideo(Uri.parse(path)); 121 | // } 122 | } 123 | 124 | public int getVideoWidth() { 125 | return mVideoWidth; 126 | } 127 | 128 | public int getVideoHeight() { 129 | return mVideoHeight; 130 | } 131 | 132 | public void reOpen() { 133 | mTargetState = STATE_PREPARED; 134 | openVideo(mUri); 135 | } 136 | 137 | public int getDuration() { 138 | return mDuration; 139 | } 140 | 141 | /** 重试 */ 142 | private void tryAgain(Exception e) { 143 | e.printStackTrace(); 144 | mCurrentState = STATE_ERROR; 145 | openVideo(mUri); 146 | } 147 | 148 | public void start() { 149 | mTargetState = STATE_PLAYING; 150 | //可用状态{Prepared, Started, Paused, PlaybackCompleted} 151 | if (mMediaPlayer != null && (mCurrentState == STATE_PREPARED || mCurrentState == STATE_PAUSED || mCurrentState == STATE_PLAYING || mCurrentState == STATE_PLAYBACK_COMPLETED)) { 152 | try { 153 | if (!isPlaying()) 154 | mMediaPlayer.start(); 155 | mCurrentState = STATE_PLAYING; 156 | if (mOnPlayStateListener != null) 157 | mOnPlayStateListener.onStateChanged(true); 158 | } catch (IllegalStateException e) { 159 | tryAgain(e); 160 | } catch (Exception e) { 161 | tryAgain(e); 162 | } 163 | } 164 | } 165 | 166 | public void pause() { 167 | mTargetState = STATE_PAUSED; 168 | //可用状态{Started, Paused} 169 | if (mMediaPlayer != null && (mCurrentState == STATE_PLAYING || mCurrentState == STATE_PAUSED)) { 170 | try { 171 | mMediaPlayer.pause(); 172 | mCurrentState = STATE_PAUSED; 173 | if (mOnPlayStateListener != null) 174 | mOnPlayStateListener.onStateChanged(false); 175 | } catch (IllegalStateException e) { 176 | tryAgain(e); 177 | } catch (Exception e) { 178 | tryAgain(e); 179 | } 180 | } 181 | } 182 | 183 | public void stop(){ 184 | mTargetState = STATE_STOP; 185 | if (mMediaPlayer != null && (mCurrentState == STATE_PLAYING || mCurrentState == STATE_PAUSED)) { 186 | try { 187 | mMediaPlayer.stop(); 188 | mCurrentState = STATE_STOP; 189 | if (mOnPlayStateListener != null) 190 | mOnPlayStateListener.onStateChanged(false); 191 | } catch (IllegalStateException e) { 192 | tryAgain(e); 193 | } catch (Exception e) { 194 | tryAgain(e); 195 | } 196 | } 197 | } 198 | 199 | public void setVolume(float volume) { 200 | //可用状态{Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted} 201 | if (mMediaPlayer != null && (mCurrentState == STATE_PREPARED || mCurrentState == STATE_PLAYING || mCurrentState == STATE_PAUSED || mCurrentState == STATE_PLAYBACK_COMPLETED)) { 202 | try { 203 | mMediaPlayer.setVolume(volume, volume); 204 | } catch (Exception e) { 205 | e.printStackTrace(); 206 | } 207 | } 208 | } 209 | 210 | public void setLooping(boolean looping) { 211 | //可用状态{Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted} 212 | if (mMediaPlayer != null && (mCurrentState == STATE_PREPARED || mCurrentState == STATE_PLAYING || mCurrentState == STATE_PAUSED || mCurrentState == STATE_PLAYBACK_COMPLETED)) { 213 | try { 214 | mMediaPlayer.setLooping(looping); 215 | } catch (Exception e) { 216 | e.printStackTrace(); 217 | } 218 | } 219 | } 220 | 221 | public void seekTo(int msec) { 222 | //可用状态{Prepared, Started, Paused, PlaybackCompleted} 223 | if (mMediaPlayer != null && (mCurrentState == STATE_PREPARED || mCurrentState == STATE_PLAYING || mCurrentState == STATE_PAUSED || mCurrentState == STATE_PLAYBACK_COMPLETED)) { 224 | try { 225 | if (msec < 0) 226 | msec = 0; 227 | mMediaPlayer.seekTo(msec); 228 | } catch (IllegalStateException e) { 229 | e.printStackTrace(); 230 | } catch (Exception e) { 231 | e.printStackTrace(); 232 | } 233 | } 234 | } 235 | 236 | /** 获取当前播放位置 */ 237 | public int getCurrentPosition() { 238 | int position = 0; 239 | //可用状态{Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} 240 | if (mMediaPlayer != null) { 241 | switch (mCurrentState) { 242 | case STATE_PLAYBACK_COMPLETED: 243 | position = getDuration(); 244 | break; 245 | case STATE_PLAYING: 246 | case STATE_PAUSED: 247 | try { 248 | position = mMediaPlayer.getCurrentPosition(); 249 | } catch (IllegalStateException e) { 250 | e.printStackTrace(); 251 | } catch (Exception e) { 252 | e.printStackTrace(); 253 | } 254 | break; 255 | } 256 | } 257 | return position; 258 | } 259 | 260 | public boolean isPlaying() { 261 | //可用状态{Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} 262 | if (mMediaPlayer != null && mCurrentState == STATE_PLAYING) { 263 | try { 264 | return mMediaPlayer.isPlaying(); 265 | } catch (IllegalStateException e) { 266 | e.printStackTrace(); 267 | } catch (Exception e) { 268 | e.printStackTrace(); 269 | } 270 | } 271 | return false; 272 | } 273 | 274 | /** 调用release方法以后MediaPlayer无法再恢复使用 */ 275 | public void release() { 276 | mTargetState = STATE_RELEASED; 277 | mCurrentState = STATE_RELEASED; 278 | if (mMediaPlayer != null) { 279 | try { 280 | mMediaPlayer.release(); 281 | } catch (IllegalStateException e) { 282 | e.printStackTrace(); 283 | } catch (Exception e) { 284 | e.printStackTrace(); 285 | } 286 | mMediaPlayer = null; 287 | } 288 | } 289 | 290 | public void openVideo(Uri uri) { 291 | if (uri == null || mSurfaceHolder == null || getContext() == null) { 292 | // not ready for playback just yet, will try again later 293 | if (mSurfaceHolder == null && uri != null) { 294 | mUri = uri; 295 | } 296 | return; 297 | } 298 | 299 | mUri = uri; 300 | mDuration = 0; 301 | 302 | //Idle 状态:当使用new()方法创建一个MediaPlayer对象或者调用了其reset()方法时,该MediaPlayer对象处于idle状态。 303 | //End 状态:通过release()方法可以进入End状态,只要MediaPlayer对象不再被使用,就应当尽快将其通过release()方法释放掉 304 | //Initialized 状态:这个状态比较简单,MediaPlayer调用setDataSource()方法就进入Initialized状态,表示此时要播放的文件已经设置好了。 305 | //Prepared 状态:初始化完成之后还需要通过调用prepare()或prepareAsync()方法,这两个方法一个是同步的一个是异步的,只有进入Prepared状态,才表明MediaPlayer到目前为止都没有错误,可以进行文件播放。 306 | 307 | Exception exception = null; 308 | try { 309 | if (mMediaPlayer == null) { 310 | mMediaPlayer = new MediaPlayer(); 311 | mMediaPlayer.setOnPreparedListener(mPreparedListener); 312 | mMediaPlayer.setOnCompletionListener(mCompletionListener); 313 | mMediaPlayer.setOnErrorListener(mErrorListener); 314 | mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 315 | mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener); 316 | // mMediaPlayer.setScreenOnWhilePlaying(true); 317 | mMediaPlayer.setVolume(mVolumn, mVolumn); 318 | mMediaPlayer.setSurface(new Surface(mSurfaceHolder)); 319 | } else { 320 | mMediaPlayer.reset(); 321 | } 322 | mMediaPlayer.setDataSource(getContext(), uri); 323 | 324 | // if (mLooping) 325 | // mMediaPlayer.setLooping(true);//循环播放 326 | mMediaPlayer.prepareAsync(); 327 | // we don't set the target state here either, but preserve the 328 | // target state that was there before. 329 | mCurrentState = STATE_PREPARING; 330 | } catch (IOException ex) { 331 | exception = ex; 332 | } catch (IllegalArgumentException ex) { 333 | exception = ex; 334 | } catch (Exception ex) { 335 | exception = ex; 336 | } 337 | if (exception != null) { 338 | exception.printStackTrace(); 339 | mCurrentState = STATE_ERROR; 340 | if (mErrorListener != null) 341 | mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); 342 | } 343 | } 344 | 345 | private MediaPlayer.OnCompletionListener mCompletionListener = new MediaPlayer.OnCompletionListener() { 346 | @Override 347 | public void onCompletion(MediaPlayer mp) { 348 | mCurrentState = STATE_PLAYBACK_COMPLETED; 349 | // mTargetState = STATE_PLAYBACK_COMPLETED; 350 | if (mOnCompletionListener != null) 351 | mOnCompletionListener.onCompletion(mp); 352 | } 353 | }; 354 | 355 | MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { 356 | @Override 357 | public void onPrepared(MediaPlayer mp) { 358 | //必须是正常状态 359 | if (mCurrentState == STATE_PREPARING) { 360 | mCurrentState = STATE_PREPARED; 361 | try { 362 | mDuration = mp.getDuration(); 363 | } catch (IllegalStateException e) { 364 | e.printStackTrace(); 365 | } 366 | 367 | try { 368 | mVideoWidth = mp.getVideoWidth(); 369 | mVideoHeight = mp.getVideoHeight(); 370 | } catch (IllegalStateException e) { 371 | e.printStackTrace(); 372 | } 373 | 374 | switch (mTargetState) { 375 | case STATE_PREPARED: 376 | if (mOnPreparedListener != null) 377 | mOnPreparedListener.onPrepared(mMediaPlayer); 378 | break; 379 | case STATE_PLAYING: 380 | start(); 381 | break; 382 | } 383 | } 384 | } 385 | }; 386 | 387 | private MediaPlayer.OnSeekCompleteListener mSeekCompleteListener = new MediaPlayer.OnSeekCompleteListener() { 388 | 389 | @Override 390 | public void onSeekComplete(MediaPlayer mp) { 391 | if (mOnSeekCompleteListener != null) 392 | mOnSeekCompleteListener.onSeekComplete(mp); 393 | } 394 | }; 395 | 396 | private MediaPlayer.OnErrorListener mErrorListener = new MediaPlayer.OnErrorListener() { 397 | @Override 398 | public boolean onError(MediaPlayer mp, int framework_err, int impl_err) { 399 | mCurrentState = STATE_ERROR; 400 | // mTargetState = STATE_ERROR; 401 | //FIX,可以考虑出错以后重新开始 402 | if (mOnErrorListener != null) 403 | mOnErrorListener.onError(mp, framework_err, impl_err); 404 | 405 | return true; 406 | } 407 | }; 408 | 409 | @Override 410 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 411 | boolean needReOpen = (mSurfaceHolder == null); 412 | mSurfaceHolder = surface; 413 | if (needReOpen) { 414 | reOpen(); 415 | } 416 | } 417 | 418 | @Override 419 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 420 | 421 | } 422 | 423 | @Override 424 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 425 | //画布失效 426 | mSurfaceHolder = null; 427 | release(); 428 | return true; 429 | } 430 | 431 | @Override 432 | public void onSurfaceTextureUpdated(SurfaceTexture surface) { 433 | 434 | } 435 | 436 | /** 是否可用 */ 437 | public boolean isPrepared() { 438 | //|| mCurrentState == STATE_PAUSED || mCurrentState == STATE_PLAYING 439 | return mMediaPlayer != null && (mCurrentState == STATE_PREPARED); 440 | } 441 | 442 | // /** 是否能即可播放 */ 443 | // public boolean canStart() { 444 | // return mMediaPlayer != null && (mCurrentState == STATE_PREPARED || mCurrentState == STATE_PAUSED); 445 | // } 446 | 447 | private static final int HANDLER_MESSAGE_PARSE = 0; 448 | private static final int HANDLER_MESSAGE_LOOP = 1; 449 | 450 | private Handler mVideoHandler = new Handler() { 451 | @Override 452 | public void handleMessage(Message msg) { 453 | switch (msg.what) { 454 | case HANDLER_MESSAGE_PARSE: 455 | pause(); 456 | break; 457 | case HANDLER_MESSAGE_LOOP: 458 | if (isPlaying()) { 459 | seekTo(msg.arg1); 460 | sendMessageDelayed(mVideoHandler.obtainMessage(HANDLER_MESSAGE_LOOP, msg.arg1, msg.arg2), msg.arg2); 461 | } 462 | break; 463 | default: 464 | break; 465 | } 466 | super.handleMessage(msg); 467 | } 468 | }; 469 | 470 | /** 定时暂停 */ 471 | public void pauseDelayed(int delayMillis) { 472 | if (mVideoHandler.hasMessages(HANDLER_MESSAGE_PARSE)) 473 | mVideoHandler.removeMessages(HANDLER_MESSAGE_PARSE); 474 | mVideoHandler.sendEmptyMessageDelayed(HANDLER_MESSAGE_PARSE, delayMillis); 475 | } 476 | 477 | /** 暂停并且清除定时任务 */ 478 | public void pauseClearDelayed() { 479 | pause(); 480 | if (mVideoHandler.hasMessages(HANDLER_MESSAGE_PARSE)) 481 | mVideoHandler.removeMessages(HANDLER_MESSAGE_PARSE); 482 | if (mVideoHandler.hasMessages(HANDLER_MESSAGE_LOOP)) 483 | mVideoHandler.removeMessages(HANDLER_MESSAGE_LOOP); 484 | } 485 | 486 | /** 区域内循环播放 */ 487 | public void loopDelayed(int startTime, int endTime) { 488 | int delayMillis = endTime - startTime; 489 | seekTo(startTime); 490 | if (!isPlaying()) 491 | start(); 492 | if (mVideoHandler.hasMessages(HANDLER_MESSAGE_LOOP)) 493 | mVideoHandler.removeMessages(HANDLER_MESSAGE_LOOP); 494 | mVideoHandler.sendMessageDelayed(mVideoHandler.obtainMessage(HANDLER_MESSAGE_LOOP, getCurrentPosition(), delayMillis), delayMillis); 495 | } 496 | } 497 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/example/zhaoshuang/weixinrecordeddemo/RecordedButton.java: -------------------------------------------------------------------------------- 1 | package com.example.zhaoshuang.weixinrecordeddemo; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.RectF; 9 | import android.os.Handler; 10 | import android.os.Message; 11 | import android.util.AttributeSet; 12 | import android.util.Log; 13 | import android.view.MotionEvent; 14 | import android.view.View; 15 | 16 | /** 17 | * 拍摄button的自定义view 18 | * Created by zhaoshuang on 17/2/8. 19 | */ 20 | 21 | public class RecordedButton extends View { 22 | 23 | private int measuredWidth = -1; 24 | private Paint paint; 25 | private int colorGray; 26 | private float radius1; 27 | private float radius2; 28 | private float zoom = 0.7f;//初始化缩放比例 29 | private int dp5; 30 | private Paint paintProgress; 31 | private int colorBlue; 32 | private float girth; 33 | private RectF oval; 34 | private int max; 35 | private OnGestureListener onGestureListener; 36 | private int animTime = 200; 37 | private float downX; 38 | private float downY; 39 | private boolean closeMode = true; 40 | 41 | public RecordedButton(Context context) { 42 | super(context); 43 | init(); 44 | } 45 | 46 | public RecordedButton(Context context, AttributeSet attrs) { 47 | super(context, attrs); 48 | init(); 49 | } 50 | 51 | public RecordedButton(Context context, AttributeSet attrs, int defStyleAttr) { 52 | super(context, attrs, defStyleAttr); 53 | init(); 54 | } 55 | 56 | private void init() { 57 | 58 | dp5 = (int) getResources().getDimension(R.dimen.dp5); 59 | colorGray = getResources().getColor(R.color.gray); 60 | colorBlue = getResources().getColor(R.color.blue); 61 | 62 | paint = new Paint(); 63 | paint.setAntiAlias(true); 64 | 65 | paintProgress = new Paint(); 66 | paintProgress.setAntiAlias(true); 67 | paintProgress.setColor(colorBlue); 68 | paintProgress.setStrokeWidth(dp5); 69 | paintProgress.setStyle(Paint.Style.STROKE); 70 | } 71 | 72 | public interface OnGestureListener { 73 | void onLongClick(); 74 | void onClick(); 75 | void onLift(); 76 | void onOver(); 77 | } 78 | 79 | public void setOnGestureListener(OnGestureListener onGestureListener){ 80 | this.onGestureListener = onGestureListener; 81 | } 82 | 83 | private Handler myHandler = new Handler(){ 84 | @Override 85 | public void handleMessage(Message msg) { 86 | if(onGestureListener != null) { 87 | startAnim(0, 1-zoom); 88 | onGestureListener.onLongClick(); 89 | closeMode = true; 90 | } 91 | } 92 | }; 93 | 94 | @Override 95 | public boolean onTouchEvent(MotionEvent event) { 96 | switch (event.getAction()){ 97 | case MotionEvent.ACTION_DOWN: 98 | myHandler.sendEmptyMessageDelayed(0, animTime); 99 | downX = event.getX(); 100 | downY = event.getY(); 101 | break; 102 | case MotionEvent.ACTION_MOVE: 103 | break; 104 | case MotionEvent.ACTION_UP: 105 | case MotionEvent.ACTION_CANCEL: 106 | float upX = event.getX(); 107 | float upY = event.getY(); 108 | if(myHandler.hasMessages(0)){ 109 | myHandler.removeMessages(0); 110 | if (Math.abs(upX - downX) < dp5 && Math.abs(upY - downY) < dp5) { 111 | if(onGestureListener != null) onGestureListener.onClick(); 112 | } 113 | }else if(onGestureListener != null && closeMode){ 114 | onGestureListener.onLift(); 115 | closeButton(); 116 | } 117 | break; 118 | } 119 | return true; 120 | } 121 | 122 | public void closeButton(){ 123 | 124 | if(closeMode) { 125 | closeMode = false; 126 | startAnim(1 - zoom, 0); 127 | girth = 0; 128 | invalidate(); 129 | } 130 | } 131 | 132 | private void startAnim(float start, float end){ 133 | 134 | ValueAnimator va = ValueAnimator.ofFloat(start, end).setDuration(animTime); 135 | va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 136 | @Override 137 | public void onAnimationUpdate(ValueAnimator animation) { 138 | float value = (float) animation.getAnimatedValue(); 139 | radius1 = measuredWidth * (zoom + value) / 2; 140 | radius2 = measuredWidth * (zoom - value) / 2.5f; 141 | invalidate(); 142 | } 143 | }); 144 | va.start(); 145 | } 146 | 147 | public void setMax(int max){ 148 | this.max = max; 149 | } 150 | 151 | public void setProgress(float progress){ 152 | 153 | float ratio = progress/max; 154 | girth = 370*ratio; 155 | invalidate(); 156 | 157 | if(ratio >= 1){ 158 | if(onGestureListener != null) onGestureListener.onOver(); 159 | } 160 | } 161 | 162 | @Override 163 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 164 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 165 | 166 | if(measuredWidth == -1) { 167 | measuredWidth = getMeasuredWidth(); 168 | 169 | radius1 = measuredWidth* zoom /2; 170 | radius2 = measuredWidth* zoom /2.5f; 171 | 172 | //设置绘制大小 173 | oval = new RectF(); 174 | oval.left = dp5/2; 175 | oval.top = dp5/2; 176 | oval.right = measuredWidth-dp5/2; 177 | oval.bottom = measuredWidth-dp5/2; 178 | } 179 | } 180 | 181 | @Override 182 | protected void onDraw(Canvas canvas) { 183 | 184 | //绘制外圈圆 radius1代表绘制半径 185 | paint.setColor(colorGray); 186 | canvas.drawCircle(measuredWidth/2, measuredWidth/2, radius1, paint); 187 | 188 | //绘制内圈圆 radius2代表绘制半径 189 | paint.setColor(Color.WHITE); 190 | canvas.drawCircle(measuredWidth/2, measuredWidth/2, radius2, paint); 191 | 192 | //绘制进度 270表示以圆的270度为起点, 绘制girth长度的弧线 193 | canvas.drawArc(oval, 270, girth, false, paintProgress); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/example/zhaoshuang/weixinrecordeddemo/TouchView.java: -------------------------------------------------------------------------------- 1 | package com.example.zhaoshuang.weixinrecordeddemo; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | /** 10 | * Created by zhaoshuang on 16/6/17. 11 | * 手势控制旋转放大缩小View 12 | */ 13 | public class TouchView extends View { 14 | 15 | private float downX; 16 | private float downY; 17 | private float firstX; 18 | private float firstY; 19 | private OnClickListener listener; 20 | private boolean clickable = true; 21 | private int minX = -1; 22 | private int maxX = -1; 23 | private int minY = -1; 24 | private int maxY = -1; 25 | private OnLimitsListener onLimitsListener; 26 | private OnTouchListener onTouchListener; 27 | private boolean isOutLimits; 28 | private float whRatio; 29 | private int minWidth; 30 | private int maxWidth; 31 | private int minHeight; 32 | private int maxHeight; 33 | 34 | public TouchView(Context context, AttributeSet attrs, int defStyleAttr) { 35 | super(context, attrs, defStyleAttr); 36 | } 37 | 38 | public TouchView(Context context, AttributeSet attrs) { 39 | super(context, attrs); 40 | } 41 | 42 | public TouchView(Context context) { 43 | super(context); 44 | } 45 | 46 | public void setOnClickListener(OnClickListener listener) { 47 | this.listener = listener; 48 | } 49 | 50 | public void setClickable(boolean clickable) { 51 | this.clickable = clickable; 52 | } 53 | 54 | /** 55 | * 设置边界X 56 | */ 57 | public void setLimitsX(int minX, int maxX){ 58 | this.minX = minX; 59 | this.maxX = maxX; 60 | } 61 | 62 | /** 63 | * 设置边界Y 64 | */ 65 | public void setLimitsY(int minY, int maxY){ 66 | this.minY = minY; 67 | this.maxY = maxY; 68 | } 69 | 70 | /** 71 | * 超出边界的回调 72 | */ 73 | public interface OnLimitsListener { 74 | void OnOutLimits(float x, float y); 75 | void OnInnerLimits(float x, float y); 76 | } 77 | 78 | public void setOnLimitsListener(OnLimitsListener onLimitsListener){ 79 | this.onLimitsListener = onLimitsListener; 80 | } 81 | 82 | /** 83 | * 手指触摸事件 84 | */ 85 | public interface OnTouchListener{ 86 | void onDown(TouchView view, MotionEvent event); 87 | void onMove(TouchView view, MotionEvent event); 88 | void onUp(TouchView view, MotionEvent event); 89 | } 90 | 91 | public void setOnTouchListener(OnTouchListener listener){ 92 | this.onTouchListener = listener; 93 | } 94 | 95 | /** 96 | * 是否超出范围 97 | */ 98 | public boolean isOutLimits(){ 99 | return isOutLimits; 100 | } 101 | 102 | private float lastDis; 103 | private float coreX; 104 | private float coreY; 105 | private boolean doubleMove = false; 106 | 107 | @Override 108 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 109 | super.onLayout(changed, left, top, right, bottom); 110 | 111 | if(minWidth == 0){ 112 | whRatio = getWidth()*1f/getHeight(); 113 | minWidth = getWidth()/2; 114 | ViewGroup parent = (ViewGroup) getParent(); 115 | maxWidth = parent.getWidth(); 116 | minHeight = getHeight()/2; 117 | maxHeight = (int) (maxWidth / whRatio); 118 | } 119 | } 120 | 121 | View tempView; 122 | float lastRota; 123 | 124 | @Override 125 | public boolean onTouchEvent(MotionEvent event) { 126 | 127 | switch (event.getAction()) { 128 | case MotionEvent.ACTION_DOWN: 129 | if(onTouchListener != null) onTouchListener.onDown(this, event); 130 | firstX = downX = event.getRawX(); 131 | firstY = downY = event.getRawY(); 132 | coreX = getWidth()/2+getX();//view的中心点坐标 133 | coreY = getHeight()/2+getY(); 134 | break; 135 | case MotionEvent.ACTION_MOVE: 136 | int pointerCount = event.getPointerCount(); 137 | if(pointerCount >= 2){//双点触摸事件 138 | doubleMove = true; 139 | float distance = getSlideDis(event); 140 | float spaceRotation = getRotation(event); 141 | if(tempView == null){//创建镜像 142 | tempView = new View(getContext()); 143 | tempView.setX(getX()); 144 | tempView.setY(getY()); 145 | tempView.setRotation(getRotation()); 146 | tempView.setBackground(getBackground()); 147 | tempView.setLayoutParams(new ViewGroup.LayoutParams(getWidth(), getHeight())); 148 | ViewGroup parent = (ViewGroup) getParent(); 149 | parent.addView(tempView); 150 | setAlpha(0); 151 | }else{ 152 | float slide = lastDis - distance; 153 | ViewGroup.LayoutParams layoutParams = getLayoutParams(); 154 | layoutParams.width = (int) (layoutParams.width - slide); 155 | float slide2 = slide/whRatio; 156 | layoutParams.height = (int) (layoutParams.height - slide2); 157 | 158 | if(layoutParams.width > maxWidth || layoutParams.height > maxHeight){ 159 | layoutParams.width = maxWidth; 160 | layoutParams.height = maxHeight; 161 | }else if(layoutParams.width < minWidth || layoutParams.height < minHeight){ 162 | layoutParams.width = minWidth; 163 | layoutParams.height = minHeight; 164 | } 165 | 166 | setLayoutParams(layoutParams); 167 | 168 | float x = coreX - getWidth() / 2; 169 | float y = coreY - getHeight() / 2; 170 | setX(x); 171 | setY(y); 172 | 173 | tempView.setX(x); 174 | tempView.setY(y); 175 | ViewGroup.LayoutParams layoutParams1 = tempView.getLayoutParams(); 176 | layoutParams1.width = layoutParams.width; 177 | layoutParams1.height = layoutParams.height; 178 | tempView.setLayoutParams(layoutParams1); 179 | if(lastRota != 0){ 180 | float f = lastRota-spaceRotation; 181 | tempView.setRotation(tempView.getRotation()-f); 182 | } 183 | } 184 | lastRota = spaceRotation; 185 | lastDis = distance; 186 | }else if(!doubleMove && pointerCount == 1){//单点移动事件 187 | if(onTouchListener != null) onTouchListener.onMove(this, event); 188 | float moveX = event.getRawX(); 189 | float moveY = event.getRawY(); 190 | if(moveX != -1 && moveY != -1){ 191 | if(moveX<=minX || moveX>=maxX || moveY<=minY || moveY>=maxY){ 192 | if(onLimitsListener != null) onLimitsListener.OnOutLimits(moveX, moveY); 193 | isOutLimits = true; 194 | }else if(moveX>minX && moveXminY && moveY savePathList = new ArrayList<>(); 25 | private List paintList = new ArrayList<>(); 26 | private boolean isDrawMode; 27 | private OnLineChangeListener onLineChangeListener; 28 | private boolean touchMode;//触摸状态 29 | private OnTouchListener onTouchListener; 30 | 31 | public TuyaView(Context context) { 32 | super(context); 33 | init(); 34 | } 35 | 36 | public TuyaView(Context context, AttributeSet attrs) { 37 | super(context, attrs); 38 | init(); 39 | } 40 | 41 | public TuyaView(Context context, AttributeSet attrs, int defStyleAttr) { 42 | super(context, attrs, defStyleAttr); 43 | init(); 44 | } 45 | 46 | public void setNewPaintColor(int color){ 47 | 48 | mPaint.setColor(color); 49 | } 50 | 51 | public Paint newPaint(int color){ 52 | 53 | Paint paint = new Paint(); 54 | paint.setAntiAlias(true); 55 | paint.setStrokeWidth(getResources().getDimension(R.dimen.dp3)); 56 | paint.setStyle(Paint.Style.STROKE); 57 | paint.setColor(color); 58 | 59 | return paint; 60 | } 61 | 62 | private void init() { 63 | 64 | mPaint = newPaint(Color.WHITE); 65 | mPath = new Path(); 66 | } 67 | 68 | public void setDrawMode(boolean flag){ 69 | isDrawMode = flag; 70 | } 71 | 72 | /** 73 | * 清除上一个绘制线条 74 | */ 75 | public void backPath(){ 76 | 77 | if(savePathList.size() != 0){ 78 | if(savePathList.size() == 1){ 79 | mPath.reset(); 80 | savePathList.clear(); 81 | paintList.clear(); 82 | }else{ 83 | savePathList.remove(savePathList.size()-1); 84 | paintList.remove(paintList.size()-1); 85 | mPath = savePathList.get(savePathList.size() - 1); 86 | mPaint = paintList.get(paintList.size() - 1); 87 | } 88 | if(onLineChangeListener != null) onLineChangeListener.onDeleteLine(savePathList.size()); 89 | } 90 | invalidate(); 91 | } 92 | 93 | /** 94 | * 触摸状态的回调 95 | */ 96 | public interface OnTouchListener{ 97 | void onDown(); 98 | void onUp(); 99 | } 100 | 101 | /** 102 | * 绘制状态的回调 103 | */ 104 | public interface OnLineChangeListener{ 105 | /** 106 | * @param sum 现在总共绘制线条的数目 107 | */ 108 | void onDrawLine(int sum); 109 | void onDeleteLine(int sum); 110 | } 111 | 112 | public void setOnLineChangeListener(OnLineChangeListener onLineChangeListener){ 113 | this.onLineChangeListener = onLineChangeListener; 114 | } 115 | 116 | public void setOnTouchListener(OnTouchListener onTouchListener){ 117 | this.onTouchListener = onTouchListener; 118 | } 119 | 120 | @Override 121 | public boolean onTouchEvent(MotionEvent event) { 122 | 123 | if(isDrawMode) { 124 | switch (event.getAction()) { 125 | case MotionEvent.ACTION_DOWN: 126 | touchMode = true; 127 | touchDown(event); 128 | if(onTouchListener != null) onTouchListener.onDown(); 129 | break; 130 | case MotionEvent.ACTION_MOVE: 131 | touchMove(event); 132 | break; 133 | case MotionEvent.ACTION_CANCEL: 134 | case MotionEvent.ACTION_UP: 135 | touchMode = false; 136 | savePathList.add(new Path(mPath)); 137 | paintList.add(new Paint(mPaint)); 138 | if(onTouchListener != null) onTouchListener.onUp(); 139 | if(onLineChangeListener != null) onLineChangeListener.onDrawLine(savePathList.size()); 140 | break; 141 | } 142 | invalidate(); 143 | } 144 | return isDrawMode; 145 | } 146 | 147 | private float mX; 148 | private float mY; 149 | 150 | //手指点下屏幕时调用 151 | private void touchDown(MotionEvent event) { 152 | 153 | mPath = new Path(); 154 | 155 | float x = event.getX(); 156 | float y = event.getY(); 157 | 158 | mX = x; 159 | mY = y; 160 | 161 | //mPath绘制的绘制起点 162 | mPath.moveTo(x, y); 163 | } 164 | 165 | //手指在屏幕上滑动时调用 166 | private void touchMove(MotionEvent event) { 167 | 168 | final float x = event.getX(); 169 | final float y = event.getY(); 170 | 171 | final float previousX = mX; 172 | final float previousY = mY; 173 | 174 | final float dx = Math.abs(x - previousX); 175 | final float dy = Math.abs(y - previousY); 176 | 177 | //两点之间的距离大于等于3时,连接连接两点形成直线 178 | if (dx >= 3 || dy >= 3) { 179 | //两点连成直线 180 | mPath.lineTo(x, y); 181 | 182 | //第二次执行时,第一次结束调用的坐标值将作为第二次调用的初始坐标值 183 | mX = x; 184 | mY = y; 185 | } 186 | } 187 | 188 | @Override 189 | protected void onDraw(Canvas canvas) { 190 | super.onDraw(canvas); 191 | 192 | //绘制之前的线条 193 | for (int x=0; x 0) { 61 | mMediaRecorder.receiveAudioData(sampleBuffer, result); 62 | } 63 | } 64 | } catch (Exception e) { 65 | String message = ""; 66 | if (e != null) 67 | message = e.getMessage(); 68 | mMediaRecorder.onAudioError(MediaRecorderBase.AUDIO_RECORD_ERROR_UNKNOWN, message); 69 | } 70 | 71 | mAudioRecord.release(); 72 | mAudioRecord = null; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/yixia/camera/IMediaRecorder.java: -------------------------------------------------------------------------------- 1 | package com.yixia.camera; 2 | 3 | import com.yixia.camera.model.MediaObject; 4 | 5 | /** 6 | * 视频录制接口 7 | * 8 | * @author yixia.com 9 | * 10 | */ 11 | public interface IMediaRecorder { 12 | 13 | /** 14 | * 开始录制 15 | * 16 | * @return 录制失败返回null 17 | */ 18 | public MediaObject.MediaPart startRecord(); 19 | 20 | /** 21 | * 停止录制 22 | */ 23 | public void stopRecord(); 24 | 25 | /** 26 | * 音频错误 27 | * 28 | * @param what 错误类型 29 | * @param message 30 | */ 31 | public void onAudioError(int what, String message); 32 | /** 33 | * 接收音频数据 34 | * 35 | * @param sampleBuffer 音频数据 36 | * @param len 37 | */ 38 | public void receiveAudioData(byte[] sampleBuffer, int len); 39 | } 40 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/yixia/camera/MediaRecorderBase.java: -------------------------------------------------------------------------------- 1 | package com.yixia.camera; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.annotation.TargetApi; 5 | import android.graphics.ImageFormat; 6 | import android.graphics.PixelFormat; 7 | import android.hardware.Camera; 8 | import android.hardware.Camera.Area; 9 | import android.hardware.Camera.AutoFocusCallback; 10 | import android.hardware.Camera.PreviewCallback; 11 | import android.hardware.Camera.Size; 12 | import android.os.AsyncTask; 13 | import android.os.Build; 14 | import android.os.CountDownTimer; 15 | import android.os.Handler; 16 | import android.os.Message; 17 | import android.text.TextUtils; 18 | import android.util.Log; 19 | import android.view.SurfaceHolder; 20 | import android.view.SurfaceHolder.Callback; 21 | 22 | import com.yixia.camera.model.MediaObject; 23 | import com.yixia.camera.util.DeviceUtils; 24 | import com.yixia.camera.util.FileUtils; 25 | import com.yixia.camera.util.StringUtils; 26 | import com.yixia.videoeditor.adapter.UtilityAdapter; 27 | 28 | import java.io.File; 29 | import java.io.IOException; 30 | import java.lang.ref.WeakReference; 31 | import java.util.Collections; 32 | import java.util.List; 33 | 34 | /** 35 | * 视频录制抽象类 36 | * 37 | * @author yixia.com 38 | * 39 | */ 40 | public abstract class MediaRecorderBase implements Callback, PreviewCallback, IMediaRecorder { 41 | 42 | public static int VIDEO_WIDTH = 800; 43 | public static int VIDEO_HEIGHT = 480; 44 | 45 | /** 未知错误 */ 46 | public static final int MEDIA_ERROR_UNKNOWN = 1; 47 | /** 预览画布设置错误 */ 48 | public static final int MEDIA_ERROR_CAMERA_SET_PREVIEW_DISPLAY = 101; 49 | /** 预览错误 */ 50 | public static final int MEDIA_ERROR_CAMERA_PREVIEW = 102; 51 | /** 自动对焦错误 */ 52 | public static final int MEDIA_ERROR_CAMERA_AUTO_FOCUS = 103; 53 | 54 | public static final int AUDIO_RECORD_ERROR_UNKNOWN = 0; 55 | /** 采样率设置不支持 */ 56 | public static final int AUDIO_RECORD_ERROR_SAMPLERATE_NOT_SUPPORT = 1; 57 | /** 最小缓存获取失败 */ 58 | public static final int AUDIO_RECORD_ERROR_GET_MIN_BUFFER_SIZE_NOT_SUPPORT = 2; 59 | /** 创建AudioRecord失败 */ 60 | public static final int AUDIO_RECORD_ERROR_CREATE_FAILED = 3; 61 | 62 | /** 视频码率 1M */ 63 | public static final int VIDEO_BITRATE_NORMAL = 1024; 64 | /** 视频码率 1.5M(默认) */ 65 | public static final int VIDEO_BITRATE_MEDIUM = 1536; 66 | /** 视频码率 2M */ 67 | public static final int VIDEO_BITRATE_HIGH = 2048; 68 | 69 | /** 开始转码 */ 70 | protected static final int MESSAGE_ENCODE_START = 0; 71 | /** 转码进度 */ 72 | protected static final int MESSAGE_ENCODE_PROGRESS = 1; 73 | /** 转码完成 */ 74 | protected static final int MESSAGE_ENCODE_COMPLETE = 2; 75 | /** 转码失败 */ 76 | protected static final int MESSAGE_ENCODE_ERROR = 3; 77 | 78 | /** 最大帧率 */ 79 | public static final int MAX_FRAME_RATE = 25; 80 | /** 最小帧率 */ 81 | public static final int MIN_FRAME_RATE = 15; 82 | 83 | /** 摄像头对象 */ 84 | protected Camera camera; 85 | /** 摄像头参数 */ 86 | protected Camera.Parameters mParameters = null; 87 | /** 摄像头支持的预览尺寸集合 */ 88 | protected List mSupportedPreviewSizes; 89 | /** 画布 */ 90 | protected SurfaceHolder mSurfaceHolder; 91 | 92 | /** 声音录制 */ 93 | protected AudioRecorder mAudioRecorder; 94 | /** 转码Handler */ 95 | protected EncodeHandler mEncodeHanlder; 96 | /** 拍摄存储对象 */ 97 | protected MediaObject mMediaObject; 98 | 99 | /** 转码监听器 */ 100 | protected OnEncodeListener mOnEncodeListener; 101 | /** 录制错误监听 */ 102 | protected OnErrorListener mOnErrorListener; 103 | /** 录制已经准备就绪的监听 */ 104 | protected OnPreparedListener mOnPreparedListener; 105 | 106 | /** 帧率 */ 107 | protected int mFrameRate = MIN_FRAME_RATE; 108 | /** 摄像头类型(前置/后置),默认后置 */ 109 | protected int mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK; 110 | /** 视频码率 */ 111 | protected int mVideoBitrate = 2048; 112 | /** 状态标记 */ 113 | protected boolean mPrepared, mStartPreview, mSurfaceCreated; 114 | /** 是否正在录制 */ 115 | protected volatile boolean mRecording; 116 | /** PreviewFrame调用次数,测试用 */ 117 | protected volatile long mPreviewFrameCallCount = 0; 118 | 119 | public MediaRecorderBase() { 120 | 121 | } 122 | 123 | /** 124 | * 设置预览输出SurfaceHolder 125 | * @param sh 126 | */ 127 | @SuppressWarnings("deprecation") 128 | public void setSurfaceHolder(SurfaceHolder sh) { 129 | if (sh != null) { 130 | sh.addCallback(this); 131 | if (!DeviceUtils.hasHoneycomb()) { 132 | sh.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 133 | } 134 | } 135 | } 136 | 137 | /** 设置转码监听 */ 138 | public void setOnEncodeListener(OnEncodeListener l) { 139 | this.mOnEncodeListener = l; 140 | mEncodeHanlder = new EncodeHandler(this); 141 | } 142 | 143 | /** 设置预处理监听 */ 144 | public void setOnPreparedListener(OnPreparedListener l) { 145 | mOnPreparedListener = l; 146 | } 147 | 148 | /** 设置错误监听 */ 149 | public void setOnErrorListener(OnErrorListener l) { 150 | mOnErrorListener = l; 151 | } 152 | 153 | /** 是否前置摄像头 */ 154 | public boolean isFrontCamera() { 155 | return mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT; 156 | } 157 | 158 | /** 是否支持前置摄像头 */ 159 | @SuppressLint("NewApi") 160 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 161 | public static boolean isSupportFrontCamera() { 162 | if (!DeviceUtils.hasGingerbread()) { 163 | return false; 164 | } 165 | int numberOfCameras = Camera.getNumberOfCameras(); 166 | if (2 == numberOfCameras) { 167 | return true; 168 | } 169 | return false; 170 | } 171 | 172 | /** 切换前置/后置摄像头 */ 173 | public void switchCamera(int cameraFacingFront) { 174 | switch (cameraFacingFront) { 175 | case Camera.CameraInfo.CAMERA_FACING_FRONT: 176 | case Camera.CameraInfo.CAMERA_FACING_BACK: 177 | mCameraId = cameraFacingFront; 178 | stopPreview(); 179 | startPreview(); 180 | break; 181 | } 182 | } 183 | 184 | /** 切换前置/后置摄像头 */ 185 | public void switchCamera() { 186 | if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) { 187 | switchCamera(Camera.CameraInfo.CAMERA_FACING_FRONT); 188 | } else { 189 | switchCamera(Camera.CameraInfo.CAMERA_FACING_BACK); 190 | } 191 | } 192 | 193 | /** 194 | * 自动对焦 195 | * 196 | * @param cb 197 | * @return 198 | */ 199 | public boolean autoFocus(AutoFocusCallback cb) { 200 | if (camera != null) { 201 | try { 202 | camera.cancelAutoFocus(); 203 | 204 | if (mParameters != null) { 205 | String mode = getAutoFocusMode(); 206 | if (StringUtils.isNotEmpty(mode)) { 207 | mParameters.setFocusMode(mode); 208 | camera.setParameters(mParameters); 209 | } 210 | } 211 | camera.autoFocus(cb); 212 | return true; 213 | } catch (Exception e) { 214 | if (mOnErrorListener != null) { 215 | mOnErrorListener.onVideoError(MEDIA_ERROR_CAMERA_AUTO_FOCUS, 0); 216 | } 217 | if (e != null) 218 | Log.e("Yixia", "autoFocus", e); 219 | } 220 | } 221 | return false; 222 | } 223 | 224 | /** 连续自动对焦 */ 225 | private String getAutoFocusMode() { 226 | if (mParameters != null) { 227 | //持续对焦是指当场景发生变化时,相机会主动去调节焦距来达到被拍摄的物体始终是清晰的状态。 228 | List focusModes = mParameters.getSupportedFocusModes(); 229 | if ((Build.MODEL.startsWith("GT-I950") || Build.MODEL.endsWith("SCH-I959") || Build.MODEL.endsWith("MEIZU MX3")) && isSupported(focusModes, "continuous-picture")) { 230 | return "continuous-picture"; 231 | } else if (isSupported(focusModes, "continuous-video")) { 232 | return "continuous-video"; 233 | } else if (isSupported(focusModes, "auto")) { 234 | return "auto"; 235 | } 236 | } 237 | return null; 238 | } 239 | 240 | /** 241 | * 手动对焦 242 | * 243 | * @param focusAreas 对焦区域 244 | * @return 245 | */ 246 | @SuppressLint("NewApi") 247 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 248 | public boolean manualFocus(AutoFocusCallback cb, List focusAreas) { 249 | if (camera != null && focusAreas != null && mParameters != null && DeviceUtils.hasICS()) { 250 | try { 251 | camera.cancelAutoFocus(); 252 | // getMaxNumFocusAreas检测设备是否支持 253 | if (mParameters.getMaxNumFocusAreas() > 0) { 254 | // mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_MACRO);// 255 | // Macro(close-up) focus mode 256 | mParameters.setFocusAreas(focusAreas); 257 | } 258 | 259 | if (mParameters.getMaxNumMeteringAreas() > 0) 260 | mParameters.setMeteringAreas(focusAreas); 261 | 262 | mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_MACRO); 263 | camera.setParameters(mParameters); 264 | camera.autoFocus(cb); 265 | return true; 266 | } catch (Exception e) { 267 | if (mOnErrorListener != null) { 268 | mOnErrorListener.onVideoError(MEDIA_ERROR_CAMERA_AUTO_FOCUS, 0); 269 | } 270 | if (e != null) 271 | Log.e("Yixia", "autoFocus", e); 272 | } 273 | } 274 | return false; 275 | } 276 | 277 | /** 278 | * 切换闪关灯,默认关闭 279 | */ 280 | public boolean toggleFlashMode() { 281 | if (mParameters != null) { 282 | try { 283 | final String mode = mParameters.getFlashMode(); 284 | if (TextUtils.isEmpty(mode) || Camera.Parameters.FLASH_MODE_OFF.equals(mode)) 285 | setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); 286 | else 287 | setFlashMode(Camera.Parameters.FLASH_MODE_OFF); 288 | return true; 289 | } catch (Exception e) { 290 | Log.e("Yixia", "toggleFlashMode", e); 291 | } 292 | } 293 | return false; 294 | } 295 | 296 | /** 297 | * 设置闪光灯 298 | * 299 | * @param value 300 | */ 301 | private boolean setFlashMode(String value) { 302 | if (mParameters != null && camera != null) { 303 | try { 304 | if (Camera.Parameters.FLASH_MODE_TORCH.equals(value) || Camera.Parameters.FLASH_MODE_OFF.equals(value)) { 305 | mParameters.setFlashMode(value); 306 | camera.setParameters(mParameters); 307 | } 308 | return true; 309 | } catch (Exception e) { 310 | Log.e("Yixia", "setFlashMode", e); 311 | } 312 | } 313 | return false; 314 | } 315 | 316 | /** 设置码率 */ 317 | public void setVideoBitRate(int bitRate) { 318 | if (bitRate > 0) 319 | mVideoBitrate = bitRate; 320 | } 321 | 322 | /** 设置帧数 */ 323 | public void setVideoRota(int rota) { 324 | if (rota > 0) 325 | mFrameRate = rota; 326 | } 327 | 328 | /** 329 | * 设置视频的宽高 330 | */ 331 | public void setVideoRecordedSize(int width, int height){ 332 | VIDEO_WIDTH = width; 333 | VIDEO_HEIGHT = height; 334 | } 335 | 336 | /** 337 | * 开始预览 338 | */ 339 | public void prepare() { 340 | mPrepared = true; 341 | if (mSurfaceCreated) 342 | startPreview(); 343 | } 344 | 345 | /** 346 | * 设置视频临时存储文件夹 347 | * 348 | * @param key 视频输出的名称,同目录下唯一,一般取系统当前时间 349 | * @param path 文件夹路径 350 | * @return 录制信息对象 351 | */ 352 | public MediaObject setOutputDirectory(String key, String path) { 353 | if (StringUtils.isNotEmpty(path)) { 354 | File f = new File(path); 355 | if (f != null) { 356 | if (f.exists()) { 357 | //已经存在,删除 358 | if (f.isDirectory()) 359 | FileUtils.deleteDir(f); 360 | else 361 | FileUtils.deleteFile(f); 362 | } 363 | 364 | if (f.mkdirs()) { 365 | mMediaObject = new MediaObject(key, path, mVideoBitrate); 366 | } 367 | } 368 | } 369 | return mMediaObject; 370 | } 371 | 372 | /** 设置视频信息 */ 373 | public void setMediaObject(MediaObject mediaObject) { 374 | this.mMediaObject = mediaObject; 375 | } 376 | 377 | public void stopRecord() { 378 | mRecording = false; 379 | 380 | // 判断数据是否处理完,处理完了关闭输出流 381 | if (mMediaObject != null) { 382 | MediaObject.MediaPart part = mMediaObject.getCurrentPart(); 383 | if (part != null && part.recording) { 384 | part.recording = false; 385 | part.endTime = System.currentTimeMillis(); 386 | part.duration = (int) (part.endTime - part.startTime); 387 | part.cutStartTime = 0; 388 | part.cutEndTime = part.duration; 389 | // 检测视频大小是否大于0,否则丢弃(注意有音频没视频的情况下音频也会丢弃) 390 | // File videoFile = new File(part.mediaPath); 391 | // if (videoFile != null && videoFile.length() < 1) { 392 | // mMediaObject.removePart(part, true); 393 | // } 394 | } 395 | } 396 | } 397 | 398 | /** 停止所有块的写入 */ 399 | private void stopAllRecord() { 400 | mRecording = false; 401 | if (mMediaObject != null && mMediaObject.getMedaParts() != null) { 402 | for (MediaObject.MediaPart part : mMediaObject.getMedaParts()) { 403 | if (part != null && part.recording) { 404 | part.recording = false; 405 | part.endTime = System.currentTimeMillis(); 406 | part.duration = (int) (part.endTime - part.startTime); 407 | part.cutStartTime = 0; 408 | part.cutEndTime = part.duration; 409 | // 检测视频大小是否大于0,否则丢弃(注意有音频没视频的情况下音频也会丢弃) 410 | File videoFile = new File(part.mediaPath); 411 | if (videoFile != null && videoFile.length() < 1) { 412 | mMediaObject.removePart(part, true); 413 | } 414 | } 415 | } 416 | } 417 | } 418 | 419 | /** 检测是否支持指定特性 */ 420 | private boolean isSupported(List list, String key) { 421 | return list != null && list.contains(key); 422 | } 423 | 424 | /** 425 | * 预处理一些拍摄参数 426 | * 注意:自动对焦参数cam_mode和cam-mode可能有些设备不支持,导致视频画面变形,需要判断一下,已知有"GT-N7100", "GT-I9308"会存在这个问题 427 | * 428 | */ 429 | @SuppressWarnings("deprecation") 430 | protected void prepareCameraParaments() { 431 | if (mParameters == null) 432 | return; 433 | 434 | List rates = mParameters.getSupportedPreviewFrameRates(); 435 | if (rates != null) { 436 | if (rates.contains(MAX_FRAME_RATE)) { 437 | mFrameRate = MAX_FRAME_RATE; 438 | } else { 439 | Collections.sort(rates); 440 | for (int i = rates.size() - 1; i >= 0; i--) { 441 | if (rates.get(i) <= MAX_FRAME_RATE) { 442 | mFrameRate = rates.get(i); 443 | break; 444 | } 445 | } 446 | } 447 | } 448 | 449 | Size previewSize = mParameters.getPreviewSize(); 450 | mParameters.setPreviewFrameRate(mFrameRate); 451 | // mParameters.setPreviewFpsRange(15 * 1000, 20 * 1000); 452 | mParameters.setPreviewSize(MediaRecorderBase.VIDEO_WIDTH, MediaRecorderBase.VIDEO_HEIGHT);// 3:2 453 | 454 | // 设置输出视频流尺寸,采样率 455 | mParameters.setPreviewFormat(ImageFormat.NV21); 456 | 457 | //设置自动连续对焦 458 | String mode = getAutoFocusMode(); 459 | if (StringUtils.isNotEmpty(mode)) { 460 | mParameters.setFocusMode(mode); 461 | } 462 | 463 | //设置人像模式,用来拍摄人物相片,如证件照。数码相机会把光圈调到最大,做出浅景深的效果。而有些相机还会使用能够表现更强肤色效果的色调、对比度或柔化效果进行拍摄,以突出人像主体。 464 | // if (mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT && isSupported(mParameters.getSupportedSceneModes(), Camera.Parameters.SCENE_MODE_PORTRAIT)) 465 | // mParameters.setSceneMode(Camera.Parameters.SCENE_MODE_PORTRAIT); 466 | 467 | if (isSupported(mParameters.getSupportedWhiteBalance(), "auto")) 468 | mParameters.setWhiteBalance("auto"); 469 | 470 | //是否支持视频防抖 471 | if ("true".equals(mParameters.get("video-stabilization-supported"))) 472 | mParameters.set("video-stabilization", "true"); 473 | 474 | // mParameters.set("recording-hint", "false"); 475 | // 476 | // mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); 477 | if (!DeviceUtils.isDevice("GT-N7100", "GT-I9308", "GT-I9300")) { 478 | mParameters.set("cam_mode", 1); 479 | mParameters.set("cam-mode", 1); 480 | } 481 | } 482 | 483 | /** 开始预览 */ 484 | public void startPreview() { 485 | if (mStartPreview || mSurfaceHolder == null || !mPrepared) 486 | return; 487 | else 488 | mStartPreview = true; 489 | 490 | try { 491 | 492 | if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) 493 | camera = Camera.open(); 494 | else 495 | camera = Camera.open(mCameraId); 496 | 497 | camera.setDisplayOrientation(90); 498 | try { 499 | camera.setPreviewDisplay(mSurfaceHolder); 500 | } catch (IOException e) { 501 | if (mOnErrorListener != null) { 502 | mOnErrorListener.onVideoError(MEDIA_ERROR_CAMERA_SET_PREVIEW_DISPLAY, 0); 503 | } 504 | } 505 | 506 | //设置摄像头参数 507 | mParameters = camera.getParameters(); 508 | mSupportedPreviewSizes = mParameters.getSupportedPreviewSizes();// 获取支持的尺寸 509 | prepareCameraParaments(); 510 | camera.setParameters(mParameters); 511 | setPreviewCallback(); 512 | camera.startPreview(); 513 | 514 | onStartPreviewSuccess(); 515 | if (mOnPreparedListener != null) 516 | mOnPreparedListener.onPrepared(); 517 | } catch (Exception e) { 518 | e.printStackTrace(); 519 | if (mOnErrorListener != null) { 520 | mOnErrorListener.onVideoError(MEDIA_ERROR_CAMERA_PREVIEW, 0); 521 | } 522 | Log.e("Yixia", "startPreview fail :" + e.getMessage()); 523 | } 524 | } 525 | 526 | /** 预览调用成功,子类可以做一些操作 */ 527 | protected void onStartPreviewSuccess() { 528 | 529 | } 530 | 531 | /** 设置回调 */ 532 | protected void setPreviewCallback() { 533 | Size size = mParameters.getPreviewSize(); 534 | if (size != null) { 535 | PixelFormat pf = new PixelFormat(); 536 | PixelFormat.getPixelFormatInfo(mParameters.getPreviewFormat(), pf); 537 | int buffSize = size.width * size.height * pf.bitsPerPixel / 8; 538 | try { 539 | camera.addCallbackBuffer(new byte[buffSize]); 540 | camera.addCallbackBuffer(new byte[buffSize]); 541 | camera.addCallbackBuffer(new byte[buffSize]); 542 | camera.setPreviewCallbackWithBuffer(this); 543 | } catch (OutOfMemoryError e) { 544 | Log.e("Yixia", "startPreview...setPreviewCallback...", e); 545 | } 546 | Log.e("Yixia", "startPreview...setPreviewCallbackWithBuffer...width:" + size.width + " height:" + size.height); 547 | } else { 548 | camera.setPreviewCallback(this); 549 | } 550 | } 551 | 552 | /** 停止预览 */ 553 | public void stopPreview() { 554 | if (camera != null) { 555 | try { 556 | camera.stopPreview(); 557 | camera.setPreviewCallback(null); 558 | // camera.lock(); 559 | camera.release(); 560 | } catch (Exception e) { 561 | Log.e("Yixia", "stopPreview..."); 562 | } 563 | camera = null; 564 | } 565 | mStartPreview = false; 566 | } 567 | 568 | /** 释放资源 */ 569 | public void release() { 570 | stopAllRecord(); 571 | // 停止视频预览 572 | stopPreview(); 573 | // 停止音频录制 574 | if (mAudioRecorder != null) { 575 | mAudioRecorder.interrupt(); 576 | mAudioRecorder = null; 577 | } 578 | 579 | mSurfaceHolder = null; 580 | mPrepared = false; 581 | mSurfaceCreated = false; 582 | } 583 | 584 | @Override 585 | public void surfaceCreated(SurfaceHolder holder) { 586 | this.mSurfaceHolder = holder; 587 | this.mSurfaceCreated = true; 588 | if (mPrepared && !mStartPreview) 589 | startPreview(); 590 | } 591 | 592 | @Override 593 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 594 | this.mSurfaceHolder = holder; 595 | } 596 | 597 | @Override 598 | public void surfaceDestroyed(SurfaceHolder holder) { 599 | mSurfaceHolder = null; 600 | mSurfaceCreated = false; 601 | } 602 | 603 | @Override 604 | public void onAudioError(int what, String message) { 605 | if (mOnErrorListener != null) 606 | mOnErrorListener.onAudioError(what, message); 607 | } 608 | 609 | @Override 610 | public void onPreviewFrame(byte[] data, Camera camera) { 611 | mPreviewFrameCallCount++; 612 | camera.addCallbackBuffer(data); 613 | } 614 | 615 | /** 616 | * 测试PreviewFrame回调次数,时间1分钟 617 | * 618 | */ 619 | public void testPreviewFrameCallCount() { 620 | new CountDownTimer(1 * 60 * 1000, 1000) { 621 | 622 | @Override 623 | public void onTick(long millisUntilFinished) { 624 | Log.e("[Vitamio Recorder]", "testFrameRate..." + mPreviewFrameCallCount); 625 | mPreviewFrameCallCount = 0; 626 | } 627 | 628 | @Override 629 | public void onFinish() { 630 | 631 | } 632 | 633 | }.start(); 634 | } 635 | 636 | /** 接收音频数据 */ 637 | @Override 638 | public void receiveAudioData(byte[] sampleBuffer, int len) { 639 | 640 | } 641 | 642 | /** 643 | * 预处理监听 644 | * 645 | */ 646 | public interface OnPreparedListener { 647 | /** 648 | * 预处理完毕,可以开始录制了 649 | */ 650 | void onPrepared(); 651 | } 652 | 653 | /** 654 | * 错误监听 655 | * 656 | */ 657 | public interface OnErrorListener { 658 | /** 659 | * 视频录制错误 660 | * 661 | * @param what 662 | * @param extra 663 | */ 664 | void onVideoError(int what, int extra); 665 | 666 | /** 667 | * 音频录制错误 668 | * 669 | * @param what 670 | * @param message 671 | */ 672 | void onAudioError(int what, String message); 673 | } 674 | 675 | /** 转码接口 */ 676 | public interface OnEncodeListener { 677 | /** 开始转码 */ 678 | void onEncodeStart(); 679 | 680 | /** 转码进度 */ 681 | void onEncodeProgress(int progress); 682 | 683 | /** 转码完成 */ 684 | void onEncodeComplete(); 685 | 686 | /** 转码失败 */ 687 | void onEncodeError(); 688 | } 689 | 690 | /** 开始转码,主要是合并视频片段 */ 691 | public void startEncoding() { 692 | if (mMediaObject == null || mEncodeHanlder == null) 693 | return; 694 | 695 | mEncodeHanlder.removeMessages(MESSAGE_ENCODE_PROGRESS); 696 | mEncodeHanlder.removeMessages(MESSAGE_ENCODE_COMPLETE); 697 | mEncodeHanlder.removeMessages(MESSAGE_ENCODE_START); 698 | mEncodeHanlder.removeMessages(MESSAGE_ENCODE_ERROR); 699 | mEncodeHanlder.sendEmptyMessage(MESSAGE_ENCODE_START); 700 | } 701 | 702 | /** 合并视频片段 */ 703 | protected void concatVideoParts() { 704 | new AsyncTask() { 705 | 706 | @Override 707 | protected Boolean doInBackground(Void... params) { 708 | //合并ts流 709 | String cmd = String.format("ffmpeg -i \"%s\" -vcodec copy -acodec copy -absf aac_adtstoasc -f mp4 -movflags faststart \"%s\"", mMediaObject.getConcatYUV(), mMediaObject.getOutputTempVideoPath()); 710 | long l = System.currentTimeMillis(); 711 | int i = UtilityAdapter.FFmpegRun("", cmd); 712 | Log.i("Log.i", System.currentTimeMillis()-l+" aaa"); 713 | return i == 0; 714 | } 715 | 716 | @Override 717 | protected void onPostExecute(Boolean result) { 718 | if (result) { 719 | mEncodeHanlder.sendEmptyMessage(MESSAGE_ENCODE_COMPLETE); 720 | } else { 721 | mEncodeHanlder.sendEmptyMessage(MESSAGE_ENCODE_ERROR); 722 | } 723 | } 724 | }.execute(); 725 | } 726 | 727 | /** 转码队列 */ 728 | public static class EncodeHandler extends Handler { 729 | 730 | private WeakReference mMediaRecorderBase; 731 | 732 | public EncodeHandler(MediaRecorderBase l) { 733 | mMediaRecorderBase = new WeakReference(l); 734 | } 735 | 736 | @Override 737 | public void handleMessage(Message msg) { 738 | MediaRecorderBase mrb = mMediaRecorderBase.get(); 739 | if (mrb == null || mrb.mOnEncodeListener == null) 740 | return; 741 | OnEncodeListener listener = mrb.mOnEncodeListener; 742 | switch (msg.what) { 743 | case MESSAGE_ENCODE_START: 744 | listener.onEncodeStart(); 745 | sendEmptyMessage(MESSAGE_ENCODE_PROGRESS); 746 | break; 747 | case MESSAGE_ENCODE_PROGRESS: 748 | //查询片段是否都已经转码完成 749 | final int progress = UtilityAdapter.FilterParserAction("", UtilityAdapter.PARSERACTION_PROGRESS); 750 | if (progress == 100) { 751 | listener.onEncodeProgress(progress); 752 | mrb.concatVideoParts();//合并视频 753 | } else if (progress == -1) { 754 | sendEmptyMessage(MESSAGE_ENCODE_ERROR); 755 | } else { 756 | listener.onEncodeProgress(progress); 757 | sendEmptyMessageDelayed(MESSAGE_ENCODE_PROGRESS, 50); 758 | } 759 | break; 760 | case MESSAGE_ENCODE_COMPLETE: 761 | listener.onEncodeComplete(); 762 | break; 763 | case MESSAGE_ENCODE_ERROR: 764 | listener.onEncodeError(); 765 | break; 766 | } 767 | } 768 | }; 769 | } 770 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/yixia/camera/MediaRecorderNative.java: -------------------------------------------------------------------------------- 1 | package com.yixia.camera; 2 | 3 | import android.hardware.Camera; 4 | import android.media.MediaRecorder; 5 | import android.util.Log; 6 | 7 | import com.yixia.camera.model.MediaObject; 8 | import com.yixia.videoeditor.adapter.UtilityAdapter; 9 | 10 | /** 11 | * 视频录制:边录制边底层处理视频(旋转和裁剪) 12 | * 13 | * @author yixia.com 14 | * 15 | */ 16 | public class MediaRecorderNative extends MediaRecorderBase implements MediaRecorder.OnErrorListener { 17 | 18 | /** 视频后缀 */ 19 | private static final String VIDEO_SUFFIX = ".ts"; 20 | 21 | /** 开始录制 */ 22 | @Override 23 | public MediaObject.MediaPart startRecord() { 24 | //防止没有初始化的情况 25 | if (!UtilityAdapter.isInitialized()) { 26 | UtilityAdapter.initFilterParser(); 27 | } 28 | 29 | MediaObject.MediaPart result = null; 30 | 31 | if (mMediaObject != null) { 32 | mRecording = true; 33 | result = mMediaObject.buildMediaPart(mCameraId, VIDEO_SUFFIX); 34 | String cmd = String.format("filename = \"%s\"; ", result.mediaPath); 35 | //如果需要定制非480x480的视频,可以启用以下代码,其他vf参数参考ffmpeg的文档: 36 | //cmd += String.format("addcmd = %s; "," -vf \"transpose=1,crop="+UtilityAdapter.VIDEO_WIDTH+":"+UtilityAdapter.VIDEO_HEIGHT+":0:240\" "); 37 | cmd += String.format("addcmd = %s; "," -vf \"transpose=1\" "); 38 | UtilityAdapter.FilterParserAction(cmd, UtilityAdapter.PARSERACTION_START); 39 | if (mAudioRecorder == null && result != null) { 40 | mAudioRecorder = new AudioRecorder(this); 41 | mAudioRecorder.start(); 42 | } 43 | } 44 | return result; 45 | } 46 | 47 | /** 停止录制 */ 48 | @Override 49 | public void stopRecord() { 50 | UtilityAdapter.FilterParserAction("", UtilityAdapter.PARSERACTION_STOP); 51 | super.stopRecord(); 52 | } 53 | 54 | /** 数据回调 */ 55 | @Override 56 | public void onPreviewFrame(byte[] data, Camera camera) { 57 | if (mRecording) { 58 | //底层实时处理视频,将视频旋转好,并剪切成480x480 59 | UtilityAdapter.RenderDataYuv(data); 60 | } 61 | super.onPreviewFrame(data, camera); 62 | } 63 | 64 | /** 预览成功,设置视频输入输出参数 */ 65 | @Override 66 | protected void onStartPreviewSuccess() { 67 | if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) { 68 | UtilityAdapter.RenderInputSettings(MediaRecorderBase.VIDEO_WIDTH, MediaRecorderBase.VIDEO_HEIGHT, 0, UtilityAdapter.FLIPTYPE_NORMAL); 69 | } else { 70 | UtilityAdapter.RenderInputSettings(MediaRecorderBase.VIDEO_WIDTH, MediaRecorderBase.VIDEO_HEIGHT, 180, UtilityAdapter.FLIPTYPE_HORIZONTAL); 71 | } 72 | UtilityAdapter.RenderOutputSettings(MediaRecorderBase.VIDEO_WIDTH, MediaRecorderBase.VIDEO_HEIGHT, mFrameRate, UtilityAdapter.OUTPUTFORMAT_YUV | UtilityAdapter.OUTPUTFORMAT_MASK_MP4 /*| UtilityAdapter.OUTPUTFORMAT_MASK_HARDWARE_ACC*/); 73 | } 74 | 75 | @Override 76 | public void onError(MediaRecorder mr, int what, int extra) { 77 | try { 78 | if (mr != null) 79 | mr.reset(); 80 | } catch (IllegalStateException e) { 81 | Log.w("Yixia", "stopRecord", e); 82 | } catch (Exception e) { 83 | Log.w("Yixia", "stopRecord", e); 84 | } 85 | if (mOnErrorListener != null) 86 | mOnErrorListener.onVideoError(what, extra); 87 | } 88 | 89 | /** 接收音频数据,传递到底层 */ 90 | @Override 91 | public void receiveAudioData(byte[] sampleBuffer, int len) { 92 | if (mRecording && len > 0) { 93 | UtilityAdapter.RenderDataPcm(sampleBuffer); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/yixia/camera/VCamera.java: -------------------------------------------------------------------------------- 1 | package com.yixia.camera; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageManager.NameNotFoundException; 5 | 6 | import com.yixia.camera.util.DeviceUtils; 7 | import com.yixia.camera.util.Log; 8 | import com.yixia.videoeditor.adapter.UtilityAdapter; 9 | 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | import java.io.FileNotFoundException; 13 | import java.io.FileOutputStream; 14 | import java.io.IOException; 15 | 16 | /** 17 | * 18 | * 拍摄SDK 19 | * 20 | * @author yixia.com 21 | * 22 | */ 23 | public class VCamera { 24 | /** 应用包名 */ 25 | private static String mPackageName; 26 | /** 应用版本名称 */ 27 | private static String mAppVersionName; 28 | /** 应用版本号 */ 29 | private static int mAppVersionCode; 30 | /** 视频缓存路径 */ 31 | private static String mVideoCachePath; 32 | /** SDK版本号 */ 33 | public final static String VCAMERA_SDK_VERSION = "1.2.0"; 34 | /** FFMPEG执行失败存的log文件 */ 35 | public final static String FFMPEG_LOG_FILENAME = "ffmpeg.log"; 36 | /** 执行FFMPEG命令保存路径 */ 37 | public final static String FFMPEG_LOG_FILENAME_TEMP = "temp_ffmpeg.log"; 38 | 39 | /** 40 | * 初始化SDK 41 | * 42 | * @param context 43 | */ 44 | public static void initialize(Context context) { 45 | mPackageName = context.getPackageName(); 46 | 47 | mAppVersionName = getVerName(context); 48 | mAppVersionCode = getVerCode(context); 49 | 50 | //初始化底层库 51 | UtilityAdapter.FFmpegInit(context, String.format("versionName=%s&versionCode=%d&sdkVersion=%s&android=%s&device=%s", 52 | mAppVersionName, mAppVersionCode, VCAMERA_SDK_VERSION, DeviceUtils.getReleaseVersion(), DeviceUtils.getDeviceModel())); 53 | } 54 | 55 | /** 56 | * 获取当前应用的版本号 57 | * @param context 58 | * @return 59 | */ 60 | public static int getVerCode(Context context) { 61 | int verCode = -1; 62 | try { 63 | verCode = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode; 64 | } catch (NameNotFoundException e) { 65 | } 66 | return verCode; 67 | } 68 | 69 | /** 获取当前应用的版本名称 */ 70 | public static String getVerName(Context context) { 71 | try { 72 | return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName; 73 | } catch (NameNotFoundException e) { 74 | } 75 | return ""; 76 | } 77 | 78 | // /** 上传错误日志 */ 79 | // public static void uploadErrorLog() { 80 | // LogHelper.upload(); 81 | // } 82 | 83 | /** 是否开启log输出 */ 84 | public static boolean isLog() { 85 | return Log.getIsLog(); 86 | } 87 | 88 | public static String getPackageName() { 89 | return mPackageName; 90 | } 91 | 92 | /** 是否开启Debug模式,会输出log */ 93 | public static void setDebugMode(boolean enable) { 94 | Log.setLog(enable); 95 | } 96 | 97 | /** 获取视频缓存文件夹 */ 98 | public static String getVideoCachePath() { 99 | return mVideoCachePath; 100 | } 101 | 102 | /** 设置视频缓存路径 */ 103 | public static void setVideoCachePath(String path) { 104 | File file = new File(path); 105 | if (!file.exists()) { 106 | file.mkdirs(); 107 | } 108 | 109 | mVideoCachePath = path; 110 | 111 | // 生成空的日志文件 112 | File temp = new File(VCamera.getVideoCachePath(), VCamera.FFMPEG_LOG_FILENAME_TEMP); 113 | if (!temp.exists()) { 114 | try { 115 | temp.createNewFile(); 116 | } catch (IOException e) { 117 | e.printStackTrace(); 118 | } 119 | } 120 | } 121 | 122 | /** 拷贝转码失败的log */ 123 | protected static boolean copyFFmpegLog(String cmd) { 124 | boolean result = false; 125 | 126 | int size = 1 * 1024; 127 | 128 | FileInputStream in = null; 129 | FileOutputStream out = null; 130 | try { 131 | File temp = new File(VCamera.getVideoCachePath(), VCamera.FFMPEG_LOG_FILENAME_TEMP); 132 | if (!temp.exists()) { 133 | temp.createNewFile(); 134 | return false; 135 | } 136 | in = new FileInputStream(temp); 137 | out = new FileOutputStream(new File(VCamera.getVideoCachePath(), VCamera.FFMPEG_LOG_FILENAME), true); 138 | out.write("--------------------------------------------------\r\n".getBytes()); 139 | out.write(cmd.getBytes()); 140 | out.write("\r\n\r\n".getBytes()); 141 | byte[] buffer = new byte[size]; 142 | int bytesRead = -1; 143 | while ((bytesRead = in.read(buffer)) != -1) { 144 | out.write(buffer, 0, bytesRead); 145 | } 146 | out.flush(); 147 | result = true; 148 | } catch (FileNotFoundException e) { 149 | e.printStackTrace(); 150 | } catch (IOException e) { 151 | e.printStackTrace(); 152 | } catch (Exception e) { 153 | e.printStackTrace(); 154 | } finally { 155 | try { 156 | if (in != null) { 157 | in.close(); 158 | } 159 | } catch (IOException e) { 160 | } 161 | try { 162 | if (out != null) { 163 | out.close(); 164 | } 165 | } catch (IOException e) { 166 | } 167 | } 168 | return result; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/yixia/camera/model/MediaObject.java: -------------------------------------------------------------------------------- 1 | package com.yixia.camera.model; 2 | 3 | import com.yixia.camera.util.FileUtils; 4 | import com.yixia.camera.util.StringUtils; 5 | 6 | import java.io.File; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.Serializable; 10 | import java.util.LinkedList; 11 | 12 | @SuppressWarnings("serial") 13 | public class MediaObject implements Serializable { 14 | 15 | /** 拍摄 */ 16 | public final static int MEDIA_PART_TYPE_RECORD = 0; 17 | /** 导入视频 */ 18 | public final static int MEDIA_PART_TYPE_IMPORT_VIDEO = 1; 19 | /** 导入图片 */ 20 | public final static int MEDIA_PART_TYPE_IMPORT_IMAGE = 2; 21 | /** 使用系统拍摄mp4 */ 22 | public final static int MEDIA_PART_TYPE_RECORD_MP4 = 3; 23 | /** 默认最大时长 */ 24 | public final static int DEFAULT_MAX_DURATION = 10 * 1000; 25 | /** 默认码率 */ 26 | public final static int DEFAULT_VIDEO_BITRATE = 800; 27 | 28 | /** 视频最大时长,默认10秒 */ 29 | private int mMaxDuration; 30 | /** 视频目录 */ 31 | private String mOutputDirectory; 32 | /** 对象文件 */ 33 | private String mOutputObjectPath; 34 | /** 视频码率 */ 35 | private int mVideoBitrate; 36 | /** 最终视频输出路径 */ 37 | private String mOutputVideoPath; 38 | /** 最终视频截图输出路径 */ 39 | private String mOutputVideoThumbPath; 40 | /** 文件夹、文件名 */ 41 | private String mKey; 42 | /** 当前分块 */ 43 | private volatile transient MediaPart mCurrentPart; 44 | /** 获取所有分块 */ 45 | private LinkedList mMediaList = new LinkedList(); 46 | /** 主题 */ 47 | public MediaThemeObject mThemeObject; 48 | 49 | public MediaObject(String key, String path) { 50 | this(key, path, DEFAULT_VIDEO_BITRATE); 51 | } 52 | 53 | public MediaObject(String key, String path, int videoBitrate) { 54 | this.mKey = key; 55 | this.mOutputDirectory = path; 56 | this.mVideoBitrate = videoBitrate; 57 | this.mOutputObjectPath = mOutputDirectory + File.separator + mKey + ".obj"; 58 | this.mOutputVideoPath = mOutputDirectory + ".mp4"; 59 | this.mOutputVideoThumbPath = mOutputDirectory + ".jpg"; 60 | this.mMaxDuration = DEFAULT_MAX_DURATION; 61 | } 62 | 63 | /** 获取视频码率 */ 64 | public int getVideoBitrate() { 65 | return mVideoBitrate; 66 | } 67 | 68 | /** 获取视频最大长度 */ 69 | public int getMaxDuration() { 70 | return mMaxDuration; 71 | } 72 | 73 | /** 设置最大时长,必须大于1秒 */ 74 | public void setMaxDuration(int duration) { 75 | if (duration >= 1000) { 76 | mMaxDuration = duration; 77 | } 78 | } 79 | 80 | /** 获取视频临时文件夹 */ 81 | public String getOutputDirectory() { 82 | return mOutputDirectory; 83 | } 84 | 85 | /** 获取视频临时输出播放 */ 86 | public String getOutputTempVideoPath() { 87 | return mOutputDirectory + File.separator + mKey + ".mp4"; 88 | } 89 | 90 | /** 清空主题 */ 91 | public void cleanTheme() { 92 | mThemeObject = null; 93 | if (mMediaList != null) { 94 | for (MediaPart part : mMediaList) { 95 | part.cutStartTime = 0; 96 | part.cutEndTime = part.duration; 97 | } 98 | } 99 | } 100 | 101 | /** 获取视频信息春促路径 */ 102 | public String getObjectFilePath() { 103 | if (StringUtils.isEmpty(mOutputObjectPath)) { 104 | File f = new File(mOutputVideoPath); 105 | String obj = mOutputDirectory + File.separator + f.getName() + ".obj"; 106 | mOutputObjectPath = obj; 107 | } 108 | return mOutputObjectPath; 109 | } 110 | 111 | /** 获取视频最终输出地址 */ 112 | public String getOutputVideoPath() { 113 | return mOutputVideoPath; 114 | } 115 | 116 | /** 获取视频截图最终输出地址 */ 117 | public String getOutputVideoThumbPath() { 118 | return mOutputVideoThumbPath; 119 | } 120 | 121 | /** 获取录制的总时长 */ 122 | public int getDuration() { 123 | int duration = 0; 124 | if (mMediaList != null) { 125 | for (MediaPart part : mMediaList) { 126 | duration += part.getDuration(); 127 | } 128 | } 129 | return duration; 130 | } 131 | 132 | /** 获取剪切后的总时长 */ 133 | public int getCutDuration() { 134 | int duration = 0; 135 | if (mMediaList != null) { 136 | for (MediaPart part : mMediaList) { 137 | int cut = (part.cutEndTime - part.cutStartTime); 138 | if (part.speed != 10) { 139 | cut = (int) (cut * (10F / part.speed)); 140 | } 141 | duration += cut; 142 | } 143 | } 144 | return duration; 145 | } 146 | 147 | /** 删除分块 */ 148 | public void removePart(MediaPart part, boolean deleteFile) { 149 | if (mMediaList != null) 150 | mMediaList.remove(part); 151 | 152 | if (part != null) { 153 | part.stop(); 154 | // 删除文件 155 | if (deleteFile) { 156 | part.delete(); 157 | } 158 | mMediaList.remove(part); 159 | } 160 | } 161 | 162 | /** 163 | * 生成分块信息,主要用于拍摄 164 | * 165 | * @param cameraId 记录摄像头是前置还是后置 166 | * @return 167 | */ 168 | public MediaPart buildMediaPart(int cameraId) { 169 | mCurrentPart = new MediaPart(); 170 | mCurrentPart.position = getDuration(); 171 | mCurrentPart.index = mMediaList.size(); 172 | mCurrentPart.mediaPath = mOutputDirectory + File.separator + mCurrentPart.index + ".v"; 173 | mCurrentPart.audioPath = mOutputDirectory + File.separator + mCurrentPart.index + ".a"; 174 | mCurrentPart.thumbPath = mOutputDirectory + File.separator + mCurrentPart.index + ".jpg"; 175 | mCurrentPart.cameraId = cameraId; 176 | mCurrentPart.prepare(); 177 | mCurrentPart.recording = true; 178 | mCurrentPart.startTime = System.currentTimeMillis(); 179 | mCurrentPart.type = MEDIA_PART_TYPE_IMPORT_VIDEO; 180 | mMediaList.add(mCurrentPart); 181 | return mCurrentPart; 182 | } 183 | 184 | public MediaPart buildMediaPart(int cameraId, String videoSuffix) { 185 | mCurrentPart = new MediaPart(); 186 | mCurrentPart.position = getDuration(); 187 | mCurrentPart.index = mMediaList.size(); 188 | mCurrentPart.mediaPath = mOutputDirectory + File.separator + mCurrentPart.index + videoSuffix; 189 | mCurrentPart.audioPath = mOutputDirectory + File.separator + mCurrentPart.index + ".a"; 190 | mCurrentPart.thumbPath = mOutputDirectory + File.separator + mCurrentPart.index + ".jpg"; 191 | mCurrentPart.recording = true; 192 | mCurrentPart.cameraId = cameraId; 193 | mCurrentPart.startTime = System.currentTimeMillis(); 194 | mCurrentPart.type = MEDIA_PART_TYPE_IMPORT_VIDEO; 195 | mMediaList.add(mCurrentPart); 196 | return mCurrentPart; 197 | } 198 | 199 | /** 200 | * 生成分块信息,主要用于视频导入 201 | * 202 | * @param path 203 | * @param duration 204 | * @param type 205 | * @return 206 | */ 207 | public MediaPart buildMediaPart(String path, int duration, int type) { 208 | mCurrentPart = new MediaPart(); 209 | mCurrentPart.position = getDuration(); 210 | mCurrentPart.index = mMediaList.size(); 211 | mCurrentPart.mediaPath = mOutputDirectory + File.separator + mCurrentPart.index + ".v"; 212 | mCurrentPart.audioPath = mOutputDirectory + File.separator + mCurrentPart.index + ".a"; 213 | mCurrentPart.thumbPath = mOutputDirectory + File.separator + mCurrentPart.index + ".jpg"; 214 | mCurrentPart.duration = duration; 215 | mCurrentPart.startTime = 0; 216 | mCurrentPart.endTime = duration; 217 | mCurrentPart.cutStartTime = 0; 218 | mCurrentPart.cutEndTime = duration; 219 | mCurrentPart.tempPath = path; 220 | mCurrentPart.type = type; 221 | mMediaList.add(mCurrentPart); 222 | return mCurrentPart; 223 | } 224 | 225 | public String getConcatYUV() { 226 | StringBuilder yuv = new StringBuilder(); 227 | if (mMediaList != null && mMediaList.size() > 0) { 228 | if (mMediaList.size() == 1) { 229 | if (StringUtils.isEmpty(mMediaList.get(0).tempMediaPath)) 230 | yuv.append(mMediaList.get(0).mediaPath); 231 | else 232 | yuv.append(mMediaList.get(0).tempMediaPath); 233 | } else { 234 | yuv.append("concat:"); 235 | for (int i = 0, j = mMediaList.size(); i < j; i++) { 236 | MediaPart part = mMediaList.get(i); 237 | if (StringUtils.isEmpty(part.tempMediaPath)) 238 | yuv.append(part.mediaPath); 239 | else 240 | yuv.append(part.tempMediaPath); 241 | if (i + 1 < j) { 242 | yuv.append("|"); 243 | } 244 | } 245 | } 246 | } 247 | return yuv.toString(); 248 | } 249 | 250 | public String getConcatPCM() { 251 | StringBuilder yuv = new StringBuilder(); 252 | if (mMediaList != null && mMediaList.size() > 0) { 253 | if (mMediaList.size() == 1) { 254 | if (StringUtils.isEmpty(mMediaList.get(0).tempAudioPath)) 255 | yuv.append(mMediaList.get(0).audioPath); 256 | else 257 | yuv.append(mMediaList.get(0).tempAudioPath); 258 | } else { 259 | yuv.append("concat:"); 260 | for (int i = 0, j = mMediaList.size(); i < j; i++) { 261 | MediaPart part = mMediaList.get(i); 262 | if (StringUtils.isEmpty(part.tempAudioPath)) 263 | yuv.append(part.audioPath); 264 | else 265 | yuv.append(part.tempAudioPath); 266 | if (i + 1 < j) { 267 | yuv.append("|"); 268 | } 269 | } 270 | } 271 | } 272 | return yuv.toString(); 273 | } 274 | 275 | /** 获取当前分块 */ 276 | public MediaPart getCurrentPart() { 277 | if (mCurrentPart != null) 278 | return mCurrentPart; 279 | if (mMediaList != null && mMediaList.size() > 0) 280 | mCurrentPart = mMediaList.get(mMediaList.size() - 1); 281 | return mCurrentPart; 282 | } 283 | 284 | public int getCurrentIndex() { 285 | MediaPart part = getCurrentPart(); 286 | if (part != null) 287 | return part.index; 288 | return 0; 289 | } 290 | 291 | public MediaPart getPart(int index) { 292 | if (mCurrentPart != null && index < mMediaList.size()) 293 | return mMediaList.get(index); 294 | return null; 295 | } 296 | 297 | /** 取消拍摄 */ 298 | public void delete() { 299 | if (mMediaList != null) { 300 | for (MediaPart part : mMediaList) { 301 | part.stop(); 302 | } 303 | } 304 | FileUtils.deleteDir(mOutputDirectory); 305 | } 306 | 307 | public LinkedList getMedaParts() { 308 | return mMediaList; 309 | } 310 | 311 | /** 预处理数据对象 */ 312 | public static void preparedMediaObject(MediaObject mMediaObject) { 313 | if (mMediaObject != null && mMediaObject.mMediaList != null) { 314 | int duration = 0; 315 | for (MediaPart part : mMediaObject.mMediaList) { 316 | part.startTime = duration; 317 | part.endTime = part.startTime + part.duration; 318 | duration += part.duration; 319 | } 320 | } 321 | } 322 | 323 | @Override 324 | public String toString() { 325 | StringBuffer result = new StringBuffer(); 326 | if (mMediaList != null) { 327 | result.append("[" + mMediaList.size() + "]"); 328 | for (MediaPart part : mMediaList) { 329 | result.append(part.mediaPath + ":" + part.duration + "\n"); 330 | } 331 | } 332 | return result.toString(); 333 | } 334 | 335 | public static class MediaPart implements Serializable { 336 | 337 | /** 索引 */ 338 | public int index; 339 | /** 视频路径 */ 340 | public String mediaPath; 341 | /** 音频路径 */ 342 | public String audioPath; 343 | /** 临时视频路径 */ 344 | public String tempMediaPath; 345 | /** 临时音频路径 */ 346 | public String tempAudioPath; 347 | /** 截图路径 */ 348 | public String thumbPath; 349 | /** 存放导入的视频和图片 */ 350 | public String tempPath; 351 | /** 类型 */ 352 | public int type = MEDIA_PART_TYPE_RECORD; 353 | /** 剪切视频(开始时间) */ 354 | public int cutStartTime; 355 | /** 剪切视频(结束时间) */ 356 | public int cutEndTime; 357 | /** 分段长度 */ 358 | public int duration; 359 | /** 总时长中的具体位置 */ 360 | public int position; 361 | /** 0.2倍速-3倍速(取值2~30) */ 362 | public int speed = 10; 363 | /** 摄像头 */ 364 | public int cameraId; 365 | /** 视频尺寸 */ 366 | public int yuvWidth; 367 | /** 视频高度 */ 368 | public int yuvHeight; 369 | public transient boolean remove; 370 | public transient long startTime; 371 | public transient long endTime; 372 | public transient FileOutputStream mCurrentOutputVideo; 373 | public transient FileOutputStream mCurrentOutputAudio; 374 | public transient volatile boolean recording; 375 | 376 | public MediaPart() { 377 | 378 | } 379 | 380 | public void delete() { 381 | FileUtils.deleteFile(mediaPath); 382 | FileUtils.deleteFile(audioPath); 383 | FileUtils.deleteFile(thumbPath); 384 | FileUtils.deleteFile(tempMediaPath); 385 | FileUtils.deleteFile(tempAudioPath); 386 | } 387 | 388 | /** 写入音频数据 */ 389 | public void writeAudioData(byte[] buffer) throws IOException { 390 | if (mCurrentOutputAudio != null) 391 | mCurrentOutputAudio.write(buffer); 392 | } 393 | 394 | /** 写入视频数据 */ 395 | public void writeVideoData(byte[] buffer) throws IOException { 396 | if (mCurrentOutputVideo != null) 397 | mCurrentOutputVideo.write(buffer); 398 | } 399 | 400 | public void prepare() { 401 | try { 402 | mCurrentOutputVideo = new FileOutputStream(mediaPath); 403 | } catch (IOException e) { 404 | e.printStackTrace(); 405 | } 406 | prepareAudio(); 407 | } 408 | 409 | public void prepareAudio() { 410 | try { 411 | mCurrentOutputAudio = new FileOutputStream(audioPath); 412 | } catch (IOException e) { 413 | e.printStackTrace(); 414 | } 415 | } 416 | 417 | public int getDuration() { 418 | return duration > 0 ? duration : (int) (System.currentTimeMillis() - startTime); 419 | } 420 | 421 | public void stop() { 422 | if (mCurrentOutputVideo != null) { 423 | try { 424 | mCurrentOutputVideo.flush(); 425 | mCurrentOutputVideo.close(); 426 | } catch (IOException e) { 427 | e.printStackTrace(); 428 | } 429 | mCurrentOutputVideo = null; 430 | } 431 | 432 | if (mCurrentOutputAudio != null) { 433 | try { 434 | mCurrentOutputAudio.flush(); 435 | mCurrentOutputAudio.close(); 436 | } catch (IOException e) { 437 | e.printStackTrace(); 438 | } 439 | 440 | mCurrentOutputAudio = null; 441 | } 442 | } 443 | 444 | } 445 | 446 | } 447 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/yixia/camera/model/MediaThemeObject.java: -------------------------------------------------------------------------------- 1 | package com.yixia.camera.model; 2 | 3 | public class MediaThemeObject { 4 | 5 | /** MV主题 */ 6 | public String mMVThemeName; 7 | 8 | /** 音乐 */ 9 | public String mMusicThemeName; 10 | 11 | /** 水印 */ 12 | public String mWatermarkThemeName; 13 | 14 | /** 滤镜 */ 15 | public String mFilterThemeName; 16 | 17 | // ~~~ 变声 18 | /** 音频文件 */ 19 | public String mSoundText; 20 | /** 音频文件编号 */ 21 | public String mSoundTextId; 22 | /** 变声主题名称 */ 23 | public String mSoundThemeName; 24 | 25 | // ~~~ 变速 26 | /** 变声主题名称 */ 27 | public String mSpeedThemeName; 28 | 29 | // ~~~ 静音 30 | /** 主题静音 */ 31 | public boolean mThemeMute; 32 | /** 原声静音 */ 33 | public boolean mOrgiMute; 34 | 35 | public MediaThemeObject() { 36 | 37 | } 38 | 39 | /** 检测是否是空主题,没有设置任何参数 */ 40 | public boolean isEmpty() { 41 | //非空主题 42 | if (!"Empty".equals(mMVThemeName)) { 43 | return false; 44 | } 45 | //没有静音、没有音乐、没有水印、没有滤镜、没有变声、没有变速 46 | return !mOrgiMute && isEmpty(mMusicThemeName, mWatermarkThemeName, mFilterThemeName, mSoundThemeName, mSpeedThemeName); 47 | } 48 | 49 | private boolean isEmpty(String... themes) { 50 | for (String theme : themes) { 51 | //非空 52 | if (!"Empty".equals(theme)) { 53 | return false; 54 | } 55 | } 56 | return true; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/yixia/camera/util/DeviceUtils.java: -------------------------------------------------------------------------------- 1 | package com.yixia.camera.util; 2 | 3 | import android.content.Context; 4 | import android.content.pm.FeatureInfo; 5 | import android.content.pm.PackageManager; 6 | import android.content.res.Configuration; 7 | import android.os.Build; 8 | import android.util.TypedValue; 9 | import android.view.Display; 10 | import android.view.WindowManager; 11 | 12 | import java.io.BufferedReader; 13 | import java.io.File; 14 | import java.io.FileReader; 15 | import java.io.IOException; 16 | 17 | /** 18 | * 系统版本信息类 19 | * 20 | * @author tangjun 21 | * 22 | */ 23 | public class DeviceUtils { 24 | 25 | /** >=2.2 */ 26 | public static boolean hasFroyo() { 27 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; 28 | } 29 | 30 | /** >=2.3 */ 31 | public static boolean hasGingerbread() { 32 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; 33 | } 34 | 35 | /** >=3.0 LEVEL:11 */ 36 | public static boolean hasHoneycomb() { 37 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; 38 | } 39 | 40 | /** >=3.1 */ 41 | public static boolean hasHoneycombMR1() { 42 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1; 43 | } 44 | 45 | /** >=4.0 14 */ 46 | public static boolean hasICS() { 47 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; 48 | } 49 | 50 | /** 51 | * >= 4.1 16 52 | * 53 | * @return 54 | */ 55 | public static boolean hasJellyBean() { 56 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; 57 | } 58 | 59 | /** >= 4.2 17 */ 60 | public static boolean hasJellyBeanMr1() { 61 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1; 62 | } 63 | 64 | /** >= 4.3 18 */ 65 | public static boolean hasJellyBeanMr2() { 66 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; 67 | } 68 | 69 | /** >=4.4 19 */ 70 | public static boolean hasKitkat() { 71 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 72 | } 73 | 74 | public static int getSDKVersionInt() { 75 | return Build.VERSION.SDK_INT; 76 | } 77 | 78 | @SuppressWarnings("deprecation") 79 | public static String getSDKVersion() { 80 | return Build.VERSION.SDK; 81 | } 82 | 83 | /** 84 | * 获得设备的固件版本号 85 | */ 86 | public static String getReleaseVersion() { 87 | return StringUtils.makeSafe(Build.VERSION.RELEASE); 88 | } 89 | 90 | /** 检测是否是中兴机器 */ 91 | public static boolean isZte() { 92 | return getDeviceModel().toLowerCase().indexOf("zte") != -1; 93 | } 94 | 95 | /** 判断是否是三星的手机 */ 96 | public static boolean isSamsung() { 97 | return getManufacturer().toLowerCase().indexOf("samsung") != -1; 98 | } 99 | 100 | /** 检测是否HTC手机 */ 101 | public static boolean isHTC() { 102 | return getManufacturer().toLowerCase().indexOf("htc") != -1; 103 | } 104 | 105 | /** 106 | * 检测当前设备是否是特定的设备 107 | * 108 | * @param devices 109 | * @return 110 | */ 111 | public static boolean isDevice(String... devices) { 112 | String model = DeviceUtils.getDeviceModel(); 113 | if (devices != null && model != null) { 114 | for (String device : devices) { 115 | if (model.indexOf(device) != -1) { 116 | return true; 117 | } 118 | } 119 | } 120 | return false; 121 | } 122 | 123 | /** 124 | * 获得设备型号 125 | * 126 | * @return 127 | */ 128 | public static String getDeviceModel() { 129 | return StringUtils.trim(Build.MODEL); 130 | } 131 | 132 | /** 获取厂商信息 */ 133 | public static String getManufacturer() { 134 | return StringUtils.trim(Build.MANUFACTURER); 135 | } 136 | 137 | /** 138 | * 判断是否是平板电脑 139 | * 140 | * @param context 141 | * @return 142 | */ 143 | public static boolean isTablet(Context context) { 144 | return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE; 145 | } 146 | 147 | /** 148 | * 检测是否是平板电脑 149 | * 150 | * @param context 151 | * @return 152 | */ 153 | public static boolean isHoneycombTablet(Context context) { 154 | return hasHoneycomb() && isTablet(context); 155 | } 156 | 157 | public static int dipToPX(final Context ctx, float dip) { 158 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, ctx.getResources().getDisplayMetrics()); 159 | } 160 | 161 | /** 162 | * 获取CPU的信息 163 | * 164 | * @return 165 | */ 166 | public static String getCpuInfo() { 167 | String cpuInfo = ""; 168 | try { 169 | if (new File("/proc/cpuinfo").exists()) { 170 | FileReader fr = new FileReader("/proc/cpuinfo"); 171 | BufferedReader localBufferedReader = new BufferedReader(fr, 8192); 172 | cpuInfo = localBufferedReader.readLine(); 173 | localBufferedReader.close(); 174 | 175 | if (cpuInfo != null) { 176 | cpuInfo = cpuInfo.split(":")[1].trim().split(" ")[0]; 177 | } 178 | } 179 | } catch (IOException e) { 180 | } catch (Exception e) { 181 | } 182 | return cpuInfo; 183 | } 184 | 185 | /** 判断是否支持闪光灯 */ 186 | public static boolean isSupportCameraLedFlash(PackageManager pm) { 187 | if (pm != null) { 188 | FeatureInfo[] features = pm.getSystemAvailableFeatures(); 189 | if (features != null) { 190 | for (FeatureInfo f : features) { 191 | if (f != null && PackageManager.FEATURE_CAMERA_FLASH.equals(f.name)) //判断设备是否支持闪光灯 192 | return true; 193 | } 194 | } 195 | } 196 | return false; 197 | } 198 | 199 | /** 检测设备是否支持相机 */ 200 | public static boolean isSupportCameraHardware(Context context) { 201 | if (context != null && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) { 202 | // this device has a camera 203 | return true; 204 | } else { 205 | // no camera on this device 206 | return false; 207 | } 208 | } 209 | 210 | /** 获取屏幕宽度 */ 211 | @SuppressWarnings("deprecation") 212 | public static int getScreenWidth(Context context) { 213 | Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 214 | return display.getWidth(); 215 | } 216 | 217 | @SuppressWarnings("deprecation") 218 | public static int getScreenHeight(Context context) { 219 | Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 220 | return display.getHeight(); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/yixia/camera/util/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.yixia.camera.util; 2 | 3 | import android.os.Environment; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.FileNotFoundException; 9 | import java.io.FileOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | import java.math.BigInteger; 14 | import java.net.FileNameMap; 15 | import java.net.URLConnection; 16 | import java.security.MessageDigest; 17 | import java.security.NoSuchAlgorithmException; 18 | 19 | public class FileUtils { 20 | 21 | /** 拼接路径 22 | * concatPath("/mnt/sdcard", "/DCIM/Camera") => /mnt/sdcard/DCIM/Camera 23 | * concatPath("/mnt/sdcard", "DCIM/Camera") => /mnt/sdcard/DCIM/Camera 24 | * concatPath("/mnt/sdcard/", "/DCIM/Camera") => /mnt/sdcard/DCIM/Camera 25 | * */ 26 | public static String concatPath(String... paths) { 27 | StringBuilder result = new StringBuilder(); 28 | if (paths != null) { 29 | for (String path : paths) { 30 | if (path != null && path.length() > 0) { 31 | int len = result.length(); 32 | boolean suffixSeparator = len > 0 && result.charAt(len - 1) == File.separatorChar;//后缀是否是'/' 33 | boolean prefixSeparator = path.charAt(0) == File.separatorChar;//前缀是否是'/' 34 | if (suffixSeparator && prefixSeparator) { 35 | result.append(path.substring(1)); 36 | } else if (!suffixSeparator && !prefixSeparator) {//补前缀 37 | result.append(File.separatorChar); 38 | result.append(path); 39 | } else { 40 | result.append(path); 41 | } 42 | } 43 | } 44 | } 45 | return result.toString(); 46 | } 47 | 48 | /** 计算文件的md5值 */ 49 | public static String calculateMD5(File updateFile) { 50 | MessageDigest digest; 51 | try { 52 | digest = MessageDigest.getInstance("MD5"); 53 | } catch (NoSuchAlgorithmException e) { 54 | e.printStackTrace(); 55 | return null; 56 | } 57 | 58 | InputStream is; 59 | try { 60 | is = new FileInputStream(updateFile); 61 | } catch (FileNotFoundException e) { 62 | e.printStackTrace(); 63 | return null; 64 | } 65 | 66 | //DigestInputStream 67 | 68 | byte[] buffer = new byte[8192]; 69 | int read; 70 | try { 71 | while ((read = is.read(buffer)) > 0) { 72 | digest.update(buffer, 0, read); 73 | } 74 | byte[] md5sum = digest.digest(); 75 | BigInteger bigInt = new BigInteger(1, md5sum); 76 | String output = bigInt.toString(16); 77 | // Fill to 32 chars 78 | output = String.format("%32s", output).replace(' ', '0'); 79 | return output; 80 | } catch (IOException e) { 81 | throw new RuntimeException("Unable to process file for MD5", e); 82 | } finally { 83 | try { 84 | is.close(); 85 | } catch (IOException e) { 86 | e.printStackTrace(); 87 | } 88 | } 89 | } 90 | 91 | /** 计算文件的md5值 */ 92 | public static String calculateMD5(File updateFile, int offset, int partSize) { 93 | MessageDigest digest; 94 | try { 95 | digest = MessageDigest.getInstance("MD5"); 96 | } catch (NoSuchAlgorithmException e) { 97 | e.printStackTrace(); 98 | return null; 99 | } 100 | 101 | InputStream is; 102 | try { 103 | is = new FileInputStream(updateFile); 104 | } catch (FileNotFoundException e) { 105 | e.printStackTrace(); 106 | return null; 107 | } 108 | 109 | //DigestInputStream 110 | final int buffSize = 8192;//单块大小 111 | byte[] buffer = new byte[buffSize]; 112 | int read; 113 | try { 114 | if (offset > 0) { 115 | is.skip(offset); 116 | } 117 | int byteCount = Math.min(buffSize, partSize), byteLen = 0; 118 | while ((read = is.read(buffer, 0, byteCount)) > 0 && byteLen < partSize) { 119 | digest.update(buffer, 0, read); 120 | byteLen += read; 121 | //检测最后一块,避免多读数据 122 | if (byteLen + buffSize > partSize) { 123 | byteCount = partSize - byteLen; 124 | } 125 | } 126 | byte[] md5sum = digest.digest(); 127 | BigInteger bigInt = new BigInteger(1, md5sum); 128 | String output = bigInt.toString(16); 129 | // Fill to 32 chars 130 | output = String.format("%32s", output).replace(' ', '0'); 131 | return output; 132 | } catch (IOException e) { 133 | throw new RuntimeException("Unable to process file for MD5", e); 134 | } finally { 135 | try { 136 | is.close(); 137 | } catch (IOException e) { 138 | e.printStackTrace(); 139 | } 140 | } 141 | } 142 | 143 | /** 检测文件是否可用 */ 144 | public static boolean checkFile(File f) { 145 | if (f != null && f.exists() && f.canRead() && (f.isDirectory() || (f.isFile() && f.length() > 0))) { 146 | return true; 147 | } 148 | return false; 149 | } 150 | 151 | /** 检测文件是否可用 */ 152 | public static boolean checkFile(String path) { 153 | if (StringUtils.isNotEmpty(path)) { 154 | File f = new File(path); 155 | if (f != null && f.exists() && f.canRead() && (f.isDirectory() || (f.isFile() && f.length() > 0))) 156 | return true; 157 | } 158 | return false; 159 | } 160 | 161 | /** 获取sdcard路径 */ 162 | public static String getExternalStorageDirectory() { 163 | String path = Environment.getExternalStorageDirectory().getPath(); 164 | if (DeviceUtils.isZte()) { 165 | // if (!Environment.getExternalStoragePublicDirectory( 166 | // Environment.DIRECTORY_DCIM).exists()) { 167 | path = path.replace("/sdcard", "/sdcard-ext"); 168 | // } 169 | } 170 | return path; 171 | } 172 | 173 | public static long getFileSize(String fn) { 174 | File f = null; 175 | long size = 0; 176 | 177 | try { 178 | f = new File(fn); 179 | size = f.length(); 180 | } catch (Exception e) { 181 | e.printStackTrace(); 182 | } finally { 183 | f = null; 184 | } 185 | return size < 0 ? null : size; 186 | } 187 | 188 | public static long getFileSize(File fn) { 189 | return fn == null ? 0 : fn.length(); 190 | } 191 | 192 | public static String getFileType(String fn, String defaultType) { 193 | FileNameMap fNameMap = URLConnection.getFileNameMap(); 194 | String type = fNameMap.getContentTypeFor(fn); 195 | return type == null ? defaultType : type; 196 | } 197 | 198 | public static String getFileType(String fn) { 199 | return getFileType(fn, "application/octet-stream"); 200 | } 201 | 202 | public static String getFileExtension(String filename) { 203 | String extension = ""; 204 | if (filename != null) { 205 | int dotPos = filename.lastIndexOf("."); 206 | if (dotPos >= 0 && dotPos < filename.length() - 1) { 207 | extension = filename.substring(dotPos + 1); 208 | } 209 | } 210 | return extension.toLowerCase(); 211 | } 212 | 213 | public static boolean deleteFile(File f) { 214 | if (f != null && f.exists() && !f.isDirectory()) { 215 | return f.delete(); 216 | } 217 | return false; 218 | } 219 | 220 | public static void deleteDir(File f) { 221 | if (f != null && f.exists() && f.isDirectory()) { 222 | for (File file : f.listFiles()) { 223 | if (file.isDirectory()) 224 | deleteDir(file); 225 | file.delete(); 226 | } 227 | f.delete(); 228 | } 229 | } 230 | 231 | public static void deleteDir(String f) { 232 | if (f != null && f.length() > 0) { 233 | deleteDir(new File(f)); 234 | } 235 | } 236 | 237 | public static boolean deleteFile(String f) { 238 | if (f != null && f.length() > 0) { 239 | return deleteFile(new File(f)); 240 | } 241 | return false; 242 | } 243 | 244 | /** 245 | * read file 246 | * 247 | * @param charsetName 248 | * The name of a supported {@link java.nio.charset.Charset 249 | * charset} 250 | * @return if file not exist, return null, else return content of file 251 | * @throws RuntimeException 252 | * if an error occurs while operator BufferedReader 253 | */ 254 | public static String readFile(File file, String charsetName) { 255 | StringBuilder fileContent = new StringBuilder(""); 256 | if (file == null || !file.isFile()) { 257 | return fileContent.toString(); 258 | } 259 | 260 | BufferedReader reader = null; 261 | try { 262 | InputStreamReader is = new InputStreamReader(new FileInputStream(file), charsetName); 263 | reader = new BufferedReader(is); 264 | String line = null; 265 | while ((line = reader.readLine()) != null) { 266 | if (!fileContent.toString().equals("")) { 267 | fileContent.append("\r\n"); 268 | } 269 | fileContent.append(line); 270 | } 271 | reader.close(); 272 | } catch (IOException e) { 273 | throw new RuntimeException("IOException occurred. ", e); 274 | } finally { 275 | if (reader != null) { 276 | try { 277 | reader.close(); 278 | } catch (IOException e) { 279 | throw new RuntimeException("IOException occurred. ", e); 280 | } 281 | } 282 | } 283 | return fileContent.toString(); 284 | } 285 | 286 | public static String readFile(String filePath, String charsetName) { 287 | return readFile(new File(filePath), charsetName); 288 | } 289 | 290 | public static String readFile(File file) { 291 | return readFile(file, "utf-8"); 292 | } 293 | 294 | /** 295 | * 文件拷贝 296 | * 297 | * @param from 298 | * @param to 299 | * @return 300 | */ 301 | public static boolean fileCopy(String from, String to) { 302 | boolean result = false; 303 | 304 | int size = 1 * 1024; 305 | 306 | FileInputStream in = null; 307 | FileOutputStream out = null; 308 | try { 309 | in = new FileInputStream(from); 310 | out = new FileOutputStream(to); 311 | byte[] buffer = new byte[size]; 312 | int bytesRead = -1; 313 | while ((bytesRead = in.read(buffer)) != -1) { 314 | out.write(buffer, 0, bytesRead); 315 | } 316 | out.flush(); 317 | result = true; 318 | } catch (FileNotFoundException e) { 319 | e.printStackTrace(); 320 | } catch (IOException e) { 321 | e.printStackTrace(); 322 | } catch (Exception e) { 323 | e.printStackTrace(); 324 | } finally { 325 | try { 326 | if (in != null) { 327 | in.close(); 328 | } 329 | } catch (IOException e) { 330 | } 331 | try { 332 | if (out != null) { 333 | out.close(); 334 | } 335 | } catch (IOException e) { 336 | } 337 | } 338 | return result; 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/yixia/camera/util/Log.java: -------------------------------------------------------------------------------- 1 | package com.yixia.camera.util; 2 | 3 | public class Log { 4 | 5 | private static boolean gIsLog; 6 | private static final String TAG = "VCamera"; 7 | 8 | public static void setLog(boolean isLog) { 9 | Log.gIsLog = isLog; 10 | } 11 | 12 | public static boolean getIsLog() { 13 | return gIsLog; 14 | } 15 | 16 | public static void d(String tag, String msg) { 17 | if (gIsLog) { 18 | android.util.Log.d(tag, msg); 19 | } 20 | } 21 | 22 | public static void d(String msg) { 23 | if (gIsLog) { 24 | android.util.Log.d(TAG, msg); 25 | } 26 | 27 | } 28 | 29 | /** 30 | * 31 | * @param tag 32 | * Used to identify the source of a log message. It usually 33 | * identifies the class or activity where the log call occurs. 34 | * @param msg 35 | * The message you would like logged. 36 | * @param tr 37 | * An exception to log 38 | */ 39 | public static void d(String tag, String msg, Throwable tr) { 40 | if (gIsLog) { 41 | android.util.Log.d(tag, msg, tr); 42 | } 43 | } 44 | 45 | public static void i(String tag, String msg) { 46 | if (gIsLog) { 47 | android.util.Log.i(tag, msg); 48 | } 49 | } 50 | 51 | /** 52 | * 53 | * @param tag 54 | * Used to identify the source of a log message. It usually 55 | * identifies the class or activity where the log call occurs. 56 | * @param msg 57 | * The message you would like logged. 58 | * @param tr 59 | * An exception to log 60 | */ 61 | public static void i(String tag, String msg, Throwable tr) { 62 | if (gIsLog) { 63 | android.util.Log.i(tag, msg, tr); 64 | } 65 | 66 | } 67 | 68 | /** 69 | * 70 | * @param tag 71 | * Used to identify the source of a log message. It usually 72 | * identifies the class or activity where the log call occurs. 73 | * @param msg 74 | * The message you would like logged. 75 | */ 76 | public static void e(String tag, String msg) { 77 | if (gIsLog) { 78 | android.util.Log.e(tag, msg); 79 | } 80 | } 81 | 82 | public static void e(String msg) { 83 | if (gIsLog) { 84 | android.util.Log.e(TAG, msg); 85 | } 86 | } 87 | 88 | /** 89 | * 90 | * @param tag 91 | * Used to identify the source of a log message. It usually 92 | * identifies the class or activity where the log call occurs. 93 | * @param msg 94 | * The message you would like logged. 95 | * @param tr 96 | * An exception to log 97 | */ 98 | public static void e(String tag, String msg, Throwable tr) { 99 | if (gIsLog) { 100 | android.util.Log.e(tag, msg, tr); 101 | } 102 | } 103 | 104 | public static void e(String msg, Throwable tr) { 105 | if (gIsLog) { 106 | android.util.Log.e(TAG, msg, tr); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/yixia/camera/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.yixia.camera.util; 2 | 3 | import android.text.TextPaint; 4 | import android.text.TextUtils; 5 | 6 | import java.text.SimpleDateFormat; 7 | import java.util.ArrayList; 8 | import java.util.Calendar; 9 | import java.util.Date; 10 | import java.util.Iterator; 11 | import java.util.TimeZone; 12 | 13 | /** 14 | * 字符串工具类 15 | * 16 | * @author tangjun 17 | * 18 | */ 19 | public class StringUtils { 20 | 21 | public static final String EMPTY = ""; 22 | 23 | private static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd"; 24 | private static final String DEFAULT_DATETIME_PATTERN = "yyyy-MM-dd hh:mm:ss"; 25 | /** 用于生成文件 */ 26 | private static final String DEFAULT_FILE_PATTERN = "yyyy-MM-dd-HH-mm-ss"; 27 | private static final double KB = 1024.0; 28 | private static final double MB = 1048576.0; 29 | private static final double GB = 1073741824.0; 30 | public static final SimpleDateFormat DATE_FORMAT_PART = new SimpleDateFormat( 31 | "HH:mm"); 32 | 33 | public static String currentTimeString() { 34 | return DATE_FORMAT_PART.format(Calendar.getInstance().getTime()); 35 | } 36 | 37 | public static char chatAt(String pinyin, int index) { 38 | if (pinyin != null && pinyin.length() > 0) 39 | return pinyin.charAt(index); 40 | return ' '; 41 | } 42 | 43 | /** 获取字符串宽度 */ 44 | public static float GetTextWidth(String Sentence, float Size) { 45 | if (isEmpty(Sentence)) 46 | return 0; 47 | TextPaint FontPaint = new TextPaint(); 48 | FontPaint.setTextSize(Size); 49 | return FontPaint.measureText(Sentence.trim()) + (int) (Size * 0.1); // 留点余地 50 | } 51 | 52 | /** 53 | * 格式化日期字符串 54 | * 55 | * @param date 56 | * @param pattern 57 | * @return 58 | */ 59 | public static String formatDate(Date date, String pattern) { 60 | SimpleDateFormat format = new SimpleDateFormat(pattern); 61 | return format.format(date); 62 | } 63 | 64 | public static String formatDate(long date, String pattern) { 65 | SimpleDateFormat format = new SimpleDateFormat(pattern); 66 | return format.format(new Date(date)); 67 | } 68 | 69 | /** 70 | * 格式化日期字符串 71 | * 72 | * @param date 73 | * @return 例如2011-3-24 74 | */ 75 | public static String formatDate(Date date) { 76 | return formatDate(date, DEFAULT_DATE_PATTERN); 77 | } 78 | 79 | public static String formatDate(long date) { 80 | return formatDate(new Date(date), DEFAULT_DATE_PATTERN); 81 | } 82 | 83 | /** 84 | * 获取当前时间 格式为yyyy-MM-dd 例如2011-07-08 85 | * 86 | * @return 87 | */ 88 | public static String getDate() { 89 | return formatDate(new Date(), DEFAULT_DATE_PATTERN); 90 | } 91 | 92 | /** 生成一个文件名,不含后缀 */ 93 | public static String createFileName() { 94 | Date date = new Date(System.currentTimeMillis()); 95 | SimpleDateFormat format = new SimpleDateFormat(DEFAULT_FILE_PATTERN); 96 | return format.format(date); 97 | } 98 | 99 | /** 100 | * 获取当前时间 101 | * 102 | * @return 103 | */ 104 | public static String getDateTime() { 105 | return formatDate(new Date(), DEFAULT_DATETIME_PATTERN); 106 | } 107 | 108 | /** 109 | * 格式化日期时间字符串 110 | * 111 | * @param date 112 | * @return 例如2011-11-30 16:06:54 113 | */ 114 | public static String formatDateTime(Date date) { 115 | return formatDate(date, DEFAULT_DATETIME_PATTERN); 116 | } 117 | 118 | public static String formatDateTime(long date) { 119 | return formatDate(new Date(date), DEFAULT_DATETIME_PATTERN); 120 | } 121 | 122 | /** 123 | * 格林威时间转换 124 | * 125 | * @param gmt 126 | * @return 127 | */ 128 | public static String formatGMTDate(String gmt) { 129 | TimeZone timeZoneLondon = TimeZone.getTimeZone(gmt); 130 | return formatDate(Calendar.getInstance(timeZoneLondon) 131 | .getTimeInMillis()); 132 | } 133 | 134 | /** 135 | * 拼接数组 136 | * 137 | * @param array 138 | * @param separator 139 | * @return 140 | */ 141 | public static String join(final ArrayList array, 142 | final String separator) { 143 | StringBuffer result = new StringBuffer(); 144 | if (array != null && array.size() > 0) { 145 | for (String str : array) { 146 | result.append(str); 147 | result.append(separator); 148 | } 149 | result.delete(result.length() - 1, result.length()); 150 | } 151 | return result.toString(); 152 | } 153 | 154 | public static String join(final Iterator iter, 155 | final String separator) { 156 | StringBuffer result = new StringBuffer(); 157 | if (iter != null) { 158 | while (iter.hasNext()) { 159 | String key = iter.next(); 160 | result.append(key); 161 | result.append(separator); 162 | } 163 | if (result.length() > 0) 164 | result.delete(result.length() - 1, result.length()); 165 | } 166 | return result.toString(); 167 | } 168 | 169 | /** 170 | * 判断字符串是否为空 171 | * 172 | * @param str 173 | * @return 174 | */ 175 | public static boolean isEmpty(String str) { 176 | return str == null || str.length() == 0 || str.equalsIgnoreCase("null"); 177 | } 178 | 179 | public static boolean isNotEmpty(String str) { 180 | return !isEmpty(str); 181 | } 182 | 183 | /** 184 | * 185 | * @param str 186 | * @return 187 | */ 188 | public static String trim(String str) { 189 | return str == null ? EMPTY : str.trim(); 190 | } 191 | 192 | /** 193 | * 转换时间显示 194 | * 195 | * @param time 196 | * 毫秒 197 | * @return 198 | */ 199 | public static String generateTime(long time) { 200 | int totalSeconds = (int) (time / 1000); 201 | int seconds = totalSeconds % 60; 202 | int minutes = (totalSeconds / 60) % 60; 203 | int hours = totalSeconds / 3600; 204 | 205 | return hours > 0 ? String.format("%02d:%02d:%02d", hours, minutes, 206 | seconds) : String.format("%02d:%02d", minutes, seconds); 207 | } 208 | 209 | public static boolean isBlank(String s) { 210 | return TextUtils.isEmpty(s); 211 | } 212 | 213 | /** 根据秒速获取时间格式 */ 214 | public static String gennerTime(int totalSeconds) { 215 | int seconds = totalSeconds % 60; 216 | int minutes = (totalSeconds / 60) % 60; 217 | return String.format("%02d:%02d", minutes, seconds); 218 | } 219 | 220 | /** 221 | * 转换文件大小 222 | * 223 | * @param size 224 | * @return 225 | */ 226 | public static String generateFileSize(long size) { 227 | String fileSize; 228 | if (size < KB) 229 | fileSize = size + "B"; 230 | else if (size < MB) 231 | fileSize = String.format("%.1f", size / KB) + "KB"; 232 | else if (size < GB) 233 | fileSize = String.format("%.1f", size / MB) + "MB"; 234 | else 235 | fileSize = String.format("%.1f", size / GB) + "GB"; 236 | 237 | return fileSize; 238 | } 239 | 240 | /** 查找字符串,找到返回,没找到返回空 */ 241 | public static String findString(String search, String start, String end) { 242 | int start_len = start.length(); 243 | int start_pos = StringUtils.isEmpty(start) ? 0 : search.indexOf(start); 244 | if (start_pos > -1) { 245 | int end_pos = StringUtils.isEmpty(end) ? -1 : search.indexOf(end, 246 | start_pos + start_len); 247 | if (end_pos > -1) 248 | return search.substring(start_pos + start.length(), end_pos); 249 | } 250 | return ""; 251 | } 252 | 253 | /** 254 | * 截取字符串 255 | * 256 | * @param search 257 | * 待搜索的字符串 258 | * @param start 259 | * 起始字符串 例如: 260 | * @param end 261 | * 结束字符串 例如: 262 | * @param defaultValue 263 | * @return 264 | */ 265 | public static String substring(String search, String start, String end, 266 | String defaultValue) { 267 | int start_len = start.length(); 268 | int start_pos = StringUtils.isEmpty(start) ? 0 : search.indexOf(start); 269 | if (start_pos > -1) { 270 | int end_pos = StringUtils.isEmpty(end) ? -1 : search.indexOf(end, 271 | start_pos + start_len); 272 | if (end_pos > -1) 273 | return search.substring(start_pos + start.length(), end_pos); 274 | else 275 | return search.substring(start_pos + start.length()); 276 | } 277 | return defaultValue; 278 | } 279 | 280 | /** 281 | * 截取字符串 282 | * 283 | * @param search 284 | * 待搜索的字符串 285 | * @param start 286 | * 起始字符串 例如: 287 | * @param end 288 | * 结束字符串 例如: 289 | * @return 290 | */ 291 | public static String substring(String search, String start, String end) { 292 | return substring(search, start, end, ""); 293 | } 294 | 295 | /** 296 | * 拼接字符串 297 | * 298 | * @param strs 299 | * @return 300 | */ 301 | public static String concat(String... strs) { 302 | StringBuffer result = new StringBuffer(); 303 | if (strs != null) { 304 | for (String str : strs) { 305 | if (str != null) 306 | result.append(str); 307 | } 308 | } 309 | return result.toString(); 310 | } 311 | 312 | /** 313 | * Helper function for making null strings safe for comparisons, etc. 314 | * 315 | * @return (s == null) ? "" : s; 316 | */ 317 | public static String makeSafe(String s) { 318 | return (s == null) ? "" : s; 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/java/com/yixia/videoeditor/adapter/UtilityAdapter.java: -------------------------------------------------------------------------------- 1 | package com.yixia.videoeditor.adapter; 2 | 3 | import android.media.AudioFormat; 4 | import android.media.AudioManager; 5 | import android.media.AudioTrack; 6 | import android.util.Log; 7 | 8 | public class UtilityAdapter { 9 | static { 10 | System.loadLibrary("utility"); 11 | } 12 | 13 | /** 初始化底层库 */ 14 | public static native void FFmpegInit(Object context, String settings); 15 | 16 | /** 获取当前转码时间 */ 17 | public static native int FFmpegVideoGetTransTime(int flag); 18 | 19 | /** 开始播放器数据的录制 */ 20 | public static native boolean VitamioStartRecord(String yuv, String pcm); 21 | 22 | /** 停止播放器数据的录制 */ 23 | public static native int VitamioStopRecord(int flag); 24 | 25 | /** 获取视频回调指针 */ 26 | public static native int GetVitamioVideoCallbackPointer(int flag); 27 | 28 | /** 获取音频回调指针 */ 29 | public static native int GetVitamioAudioCallbackPointer(int flag); 30 | 31 | /** 获取视频旋转信息 */ 32 | public static native int VideoGetMetadataRotate(String filename); 33 | 34 | /** 35 | * 执行ffmpeg命令 tag 任务的唯一标识,如果标识为"",以阻塞方式运行,否则以异步方式运行 FFmpegRun("", 36 | * "ffmpeg -i \"生成的mp4\" -y -f image2 -ss 1 -t 0.001 -s 480x480 \"输出.jpg\" " 37 | * ) 38 | * 39 | * @param strtag 40 | * 任务的唯一标识,如果标识为"",以阻塞方式运行,否则以异步方式运行 41 | * @param strcmd 42 | * 命令行 43 | * @return 返回执行结果 44 | */ 45 | public static native int FFmpegRun(String tag, String cmd); 46 | 47 | /** 结束异步执行的ffmpeg */ 48 | public static native void FFmpegKill(String tag); 49 | 50 | /** 检测ffmpeg实例是否正在运行 */ 51 | public static native boolean FFmpegIsRunning(String tag); 52 | 53 | /** 获取视频信息,相当于调用ffprobe */ 54 | public static native String FFmpegVideoGetInfo(String filename); 55 | 56 | /** 57 | * 传入参数width,height为surfaceview创建时给出的,后面的outwidth,outheight为输出的视频高宽,初始化时传入0,0,返回纹理id 58 | * @param width surfaceview创建时给出的 59 | * @param height surfaceview创建时给出的 60 | * @return 61 | */ 62 | public static native int RenderViewInit(int width, int height); 63 | 64 | /** 设置摄像头预览数据尺寸,摄像头旋转、翻转设置 */ 65 | public static final int FLIPTYPE_NORMAL = 0x0; 66 | /** 设置摄像头预览数据尺寸,摄像头旋转、翻转设置 */ 67 | public static final int FLIPTYPE_HORIZONTAL = 0x1; 68 | /** 设置摄像头预览数据尺寸,摄像头旋转、翻转设置 */ 69 | public static final int FLIPTYPE_VERTICAL = 0x2; 70 | 71 | /** 72 | * 设置输入参数 73 | * 74 | * @param inw 视频输入宽 75 | * @param inh 视频输入高 76 | * @param org 后置摄像头0,前置摄像头180 77 | * @param flip 后置摄像头FLIPTYPE_NORMAL,前摄像头FLIPTYPE_HORIZONTAL 78 | */ 79 | public static native void RenderInputSettings(int inw, int inh, int org, int flip); 80 | 81 | //设置输出视频流尺寸,采样率 82 | public static final int OUTPUTFORMAT_YUV = 0x1; 83 | public static final int OUTPUTFORMAT_RGBA = 0x2; 84 | public static final int OUTPUTFORMAT_MASK_ZIP = 0x4; 85 | public static final int OUTPUTFORMAT_MASK_NEED_LASTSNAP = 0x8; 86 | public static final int OUTPUTFORMAT_MASK_HARDWARE_ACC = 0x10; 87 | public static final int OUTPUTFORMAT_MASK_MP4 = 0x20; 88 | 89 | /** 90 | * 设置视频输出参数 91 | * 92 | * @param outw 视频输出宽 93 | * @param outh 视频输出高 94 | * @param outfps 视频输出帧率 95 | * @param format 视频输出格式,参考OUTPUTFORMAT_* 96 | */ 97 | public static native void RenderOutputSettings(int outw, int outh, int outfps, int format); 98 | 99 | //设置特效 100 | public static final int FILTERTYPE_FILTER = 0; 101 | public static final int FILTERTYPE_FRAME = 1; 102 | 103 | public static native void RenderSetFilter(int type, String filter); 104 | 105 | //进行显示 106 | public static native void RenderStep(); 107 | 108 | //提供摄像头数据 109 | public static native void RenderDataYuv(byte[] yuv); 110 | 111 | /** 提供录音数据,必须是44100Hz,1channel,16bit unsigned */ 112 | public static native void RenderDataPcm(byte[] pcm); 113 | 114 | /** 获取最后一帧数据,如果失败会返回一副全透明的图,如果内存失败,会返回空,alpha的值为0-1,0为全透明 */ 115 | public static native int[] RenderGetDataArgb(float alpha); 116 | 117 | /** 设置输出数据文件,设置完就开始录制 */ 118 | public static native boolean RenderOpenOutputFile(String video, String audio); 119 | 120 | /** 关闭输出数据文件,关闭后就停止录制 */ 121 | public static native void RenderCloseOutputFile(); 122 | 123 | /** 关闭输出数据文件,关闭后就停止录制 */ 124 | public static native boolean RenderIsOutputJobFinish(); 125 | 126 | /** 暂停录制 */ 127 | public static native void RenderPause(boolean pause); 128 | 129 | /** 暂停特效 */ 130 | public static void FilterParserPause(boolean pause) { 131 | if (mAudioTrack != null) { 132 | if (pause) { 133 | mAudioTrack.pause(); 134 | } else { 135 | mAudioTrack.play(); 136 | } 137 | } 138 | RenderPause(pause); 139 | } 140 | 141 | /** 142 | * 特效处理器 143 | * 144 | * @param settings 特效设置: inv=/sdcard/v.rgb; ina=/sdcard/p.pcm; out=/sdcard/o.mp4; text=/sdcard/txt.png 145 | * @param surface Surface 146 | * @param holder SurfaceHolder 147 | */ 148 | public static native boolean FilterParserInit(String strings, Object surface); 149 | 150 | //查询目前特效处理信息 151 | public static final int FILTERINFO_PROCESSEDFRAME = 0; ///<从开始累计已处理的帧数 152 | public static final int FILTERINFO_CACHEDFRAME = 1; ///<目前可用的帧数 153 | public static final int FILTERINFO_STARTPLAY = 2; ///<开始播放 154 | public static final int FILTERINFO_PAUSEPLAY = 3; ///<暂停播放 155 | public static final int FILTERINFO_PROGRESS = 4; ///<当前处理进度 156 | public static final int FILTERINFO_TOTALMS = 5; ///<经特效处理后,文件的时长,单位毫秒 157 | public static final int FILTERINFO_CAUSEGC = 6; ///<清理渲染使用的缓存 158 | 159 | public static native int FilterParserInfo(int mode); 160 | 161 | /** 停止特效处理 */ 162 | public static native void FilterParserFree(); 163 | 164 | //特效组处理 165 | public static final int PARSERACTION_INIT = 0; ///<设置全局的属性,在一开始进入预览界面时调用 166 | public static final int PARSERACTION_UPDATE = 1; ///<设置摄像头相关属性,在摄像头打开时调用 167 | public static final int PARSERACTION_START = 2; ///<设置开始捕捉,并指定保存的文件 168 | public static final int PARSERACTION_STOP = 3; ///<设置停止捕捉 169 | public static final int PARSERACTION_FREE = 4; ///<释放占用,这时没完成的进度也会被取消 170 | public static final int PARSERACTION_PROGRESS = 5; ///<查询处理的进度 171 | 172 | /** 173 | * 特效处理 174 | * 175 | * @param settings 176 | * @param actiontype 177 | * @return 178 | */ 179 | public static native int FilterParserAction(String settings, int actiontype); 180 | 181 | public static native boolean SaveData(String filename, int[] data, int flag); 182 | 183 | private static volatile boolean gInitialized; 184 | 185 | public static boolean isInitialized(){ 186 | return gInitialized; 187 | } 188 | 189 | /** 初始化 */ 190 | public static void initFilterParser() { 191 | if (!gInitialized) { 192 | gInitialized = true; 193 | FilterParserAction("", PARSERACTION_INIT); 194 | } 195 | } 196 | 197 | public static void freeFilterParser() { 198 | gInitialized = false; 199 | FilterParserAction("", PARSERACTION_FREE); 200 | } 201 | 202 | /** 203 | * 变声 204 | * 205 | * @param inPath wav音频输入 206 | * @param outPath wav音频输出 207 | * @param tempoChange 变速(语速增加%xx) 208 | * @param pitch // 音幅变调 209 | * @param pitchSemitone //音程变调 210 | */ 211 | public static native int SoundEffect(String inPath, String outPath, float tempoChange, float pitch, int pitchSemitone); 212 | 213 | protected static AudioTrack mAudioTrack; 214 | 215 | /** 底层音频初始化 */ 216 | @SuppressWarnings("deprecation") 217 | public static boolean ndkAudioInit() { 218 | int desiredFrames = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);// * 8; 219 | //desiredFrames = 101376 220 | if (mAudioTrack == null) { 221 | mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, desiredFrames, AudioTrack.MODE_STREAM); 222 | 223 | // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid 224 | // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java 225 | // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() 226 | 227 | if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { 228 | mAudioTrack = null; 229 | Log.w("ndkAudio", "Init failed!"); 230 | return false; 231 | } 232 | mAudioTrack.play(); 233 | } 234 | return true; 235 | } 236 | 237 | /** 底层音频输出 */ 238 | public static void ndkAudioWrite(short[] buffer, int cnt) { 239 | int limitcount = 100; 240 | int result; 241 | for (int i = 0; i < cnt;) { 242 | limitcount--; 243 | if (limitcount <= 0) { 244 | Log.e("ndkAudio", "avoid dead loop"); 245 | break; 246 | } 247 | result = mAudioTrack.write(buffer, i, cnt - i); 248 | if (result > 0) { 249 | i += result; 250 | } else if (result == 0) { 251 | try { 252 | Thread.sleep(1); 253 | } catch (InterruptedException e) { 254 | // Nom nom 255 | } 256 | } else { 257 | Log.w("ndkAudio", "write failed!"); 258 | return; 259 | } 260 | } 261 | } 262 | 263 | public static void ndkAudioQuit() { 264 | if (mAudioTrack != null) { 265 | mAudioTrack.stop(); 266 | mAudioTrack.release(); 267 | mAudioTrack = null; 268 | } 269 | } 270 | 271 | //key 272 | public static final int NOTIFYKEY_PLAYSTATE = 1; 273 | /** 播放发生缓冲时报告 */ 274 | public static final int NOTIFYVALUE_BUFFEREMPTY = 0; 275 | /** 恢复播放时报告 */ 276 | public static final int NOTIFYVALUE_BUFFERFULL = 1; 277 | /** 播放完成时报告 */ 278 | public static final int NOTIFYVALUE_PLAYFINISH = 2; 279 | /** 无法播放时报告 */ 280 | public static final int NOTIFYVALUE_HAVEERROR = 3; 281 | 282 | /** 底层回调 */ 283 | public static int ndkNotify(int key, int value) { 284 | if (mOnNativeListener != null) { 285 | mOnNativeListener.ndkNotify(key, value); 286 | } else { 287 | Log.e("ndkNotify", "ndkNotify key:" + key + ", value: " + value); 288 | } 289 | return 0; 290 | } 291 | 292 | /** 注册监听回调 */ 293 | public static void registerNativeListener(OnNativeListener l) { 294 | mOnNativeListener = l; 295 | } 296 | 297 | private static OnNativeListener mOnNativeListener; 298 | 299 | /** 底层通知 */ 300 | public static interface OnNativeListener { 301 | public void ndkNotify(int key, int value); 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/color/dialog_pro_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/drawable/color1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/drawable/color2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/drawable/color3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/drawable/color4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/drawable/color5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/drawable/yuanjiao.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/layout/activity_edit_video.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 18 | 22 | 26 | 27 | 28 | 33 | 37 | 42 | 43 | 50 | 66 | 67 | 68 | 76 | 84 | 93 | 103 | 112 | 113 | 114 | 125 | 129 | 135 | 140 | 146 | 147 | 152 | 158 | 159 | 164 | 169 | 170 | 171 | 172 | 178 | 179 | 187 | 192 | 196 | 201 | 202 | 203 | 204 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 16 | 22 | 28 | 34 | 39 | 40 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/layout/activity_video_play.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/layout/dialog_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 18 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/back.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/back2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/back2.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/color_click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/color_click.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/edit_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/edit_delete.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression1.jpg -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression2.jpg -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression3.jpg -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression4.jpg -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression5.jpg -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression6.jpg -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression7.jpg -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/expression8.jpg -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/finish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/finish.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/icon.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/icon_click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/icon_click.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/pen.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/pen_click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/pen_click.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/text.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/text_click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/text_click.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/video_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/video_delete.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xhdpi/video_finish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xhdpi/video_finish.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #ddd 8 | #3394EC 9 | 10 | #fff 11 | #f00 12 | #FECD52 13 | #145CB9 14 | #21AE58 15 | 16 | 17 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 5dp 7 | 3dp 8 | 10dp 9 | 20dp 10 | 25dp 11 | 80dp 12 | 100dp 13 | 14 | 15 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | WeiXinRecordedDemo 3 | 4 | -------------------------------------------------------------------------------- /WeiXinRecorded/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /WeiXinRecorded/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /WeiXinRecorded/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /WeiXinRecorded/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailovewei/WeiXinRecordedDemo/d7aebf3d84fbb09e558ed5654d7065843d427efc/WeiXinRecorded/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /WeiXinRecorded/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /WeiXinRecorded/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /WeiXinRecorded/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /WeiXinRecorded/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------