├── 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 | 
22 |
23 | 界面风格高仿微信, 只不过微信的编辑处理是作用于图片, 而我们的是基于视频, 所以如果你有需求, 把视频编辑处理换成图片编辑, 更是简单.
24 |
25 | ##1.实现使用ffmpeg录制视频
26 | 
27 |
28 | 首先导入lib库和ffmpeg的录制java文件, 我使用的是第三方VCamera封装的ffmpeg, 他没有jar包, 所以需要将con.yixia包下的所有文件都copy过来,
29 |
30 | 
31 |
32 | 然后在application里面初始化VCamera:
33 |
34 | 
35 | 
36 |
37 | 这个时候, 你就可以在SurfaceView上看见拍摄预览界面了,
38 |
39 | 然后mMediaRecorder.startRecord()拍摄视频,
40 |
41 | 调用mMediaRecorder.stopRecord()停止录制视频,
42 |
43 | 因为拍摄出来的文件是ts视频流, 所以还要调用mMediaRecorder.startEncoding()开始合成MP4视频文件.
44 |
45 | MediaRecorderBase类还可以设置视频各个参数, 如:
46 |
47 | 
48 |
49 | ##2.自定义拍摄按钮, 长按放大并且显示拍摄进度
50 | 
51 |
52 | 自定义RecordedButton继承View, 在onDraw里分三部分绘制:
53 | 
54 |
55 | 在拍摄模式下, 改变radius(半径), 达到放大或者缩小外圈和内圈圆的效果, 不断增加girth值达到显示拍摄进度的效果, 是不是很简单.
56 |
57 | ##3.自定义view, 实现手绘涂鸦
58 |
59 | 
60 |
61 | 自定义TuyaView继承View, 重写onTouch(), 在手指点下和移动时实时绘制触摸轨迹:
62 |
63 | 
64 |
65 | 在手指按下时创建new Path()对象, 记录本次手指触摸移动轨迹, 并且实时调用invalidate() 达到不断调用onDraw()的目的, 然后使用canvas.drawPath(path,paint)绘制触摸路径, 是不是非常简单.
66 |
67 | ##4.自定义可触摸旋转缩放位移的表情文字view
68 |
69 | 
70 |
71 | 这个view稍微有点麻烦, 但我单独写了一篇文章点击跳转, 非常详细的讲解了这个view, 而且封装的非常好, 只要addView到布局中就可以使用了, 大家可以点击链接过去看一下.
72 |
73 | ##5.基于ffmpeg的图片和视频合成处理
74 |
75 | 这也是demo的最后一步, 将涂鸦,和表情文字全部合成到视频当中, 首先是得到需要合成的图片, 我们可以通过view.draw(Canvas canvas),得到布局的bitmap:
76 |
77 | 
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 | 
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 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
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 |
217 |
218 |
219 | $USER_HOME$/.subversion
220 |
221 |
222 |
223 |
224 |
225 | Android API 17 Platform
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
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 |
--------------------------------------------------------------------------------