这个方法是从其他地方看到的,我只是做了转载和整合,其中的原理和细节还不是很清楚。
实际上是执行adb shell命令screenrecord去录取屏幕,只不过是在手机端执行这条命令,因此需要root权限(在PC端执行不需要),录制视频最长3分钟,没有声音,所以要另外录音,最后再把音频和视频合成
直接上代码:
先看两个工具类
RootCmdUtils.java用于执行终端命令
package com.example.record;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import android.util.Log;
/**
* Android运行linux命令
*/
public final class RootCmdUtils {
private static final String TAG = "RootCmd";
private static boolean mHaveRoot = false;
/**
* 判断机器Android是否已经root,即是否获取root权限
*/
public static boolean haveRoot() {
if (!mHaveRoot) {
int ret = execRootCmdSilent("echo test"); // 通过执行测试命令来检测
if (ret != -1) {
Log.e(TAG, "have root!");
mHaveRoot = true;
} else {
Log.e(TAG, "not root!");
}
} else {
Log.e(TAG, "mHaveRoot = true, have root!");
}
return mHaveRoot;
}
/**
* 执行命令并且输出结果
*/
public static String execRootCmd(String cmd) {
String result = "";
DataOutputStream dos = null;
DataInputStream dis = null;
try {
Process p = Runtime.getRuntime().exec("su");// 经过Root处理的android系统即有su命令
dos = new DataOutputStream(p.getOutputStream());
dis = new DataInputStream(p.getInputStream());
Log.e(TAG, cmd);
dos.writeBytes(cmd + "\n");
dos.flush();
dos.writeBytes("exit\n");
dos.flush();
String line = null;
while ((line = dis.readLine()) != null) {
Log.e("result", line);
result += line;
}
p.waitFor();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (dis != null) {
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
/**
* 执行命令但不关注结果输出
*/
public static int execRootCmdSilent(String cmd) {
int result = -1;
DataOutputStream dos = null;
try {
Process p = Runtime.getRuntime().exec("su");
dos = new DataOutputStream(p.getOutputStream());
Log.e(TAG, cmd);
dos.writeBytes(cmd + "\n");
dos.flush();
dos.writeBytes("exit\n");
dos.flush();
p.waitFor();
result = p.exitValue();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
/**
* 执行命令但不关注结果输出
*/
public static void execRootCmdSilentOnThread(final String cmd) {
new Thread() {
public void run() {
DataOutputStream dos = null;
try {
Process p = Runtime.getRuntime().exec("su");
dos = new DataOutputStream(p.getOutputStream());
Log.e(TAG, cmd);
dos.writeBytes(cmd + "\n");
dos.flush();
dos.writeBytes("exit\n");
dos.flush();
p.waitFor();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}.start();
}
}
AudioRecoderUtils.java用于录音
package com.example.record;
import java.io.File;
import java.io.IOException;
import android.media.MediaRecorder;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.util.TimeUtils;
public class AudioRecoderUtils {
// 文件路径
private String filePath;
// 文件夹路径
// private String FolderPath;
private MediaRecorder mMediaRecorder;
private final String TAG = "fan";
public static final int MAX_LENGTH = 1000 * 60 * 10;// 最大录音时长1000*60*10;
private OnAudioStatusUpdateListener audioStatusUpdateListener;
/**
* 文件存储默认sdcard/record
*/
public AudioRecoderUtils(String filePath) {
// File path = new File(filePath);
// if (!path.exists())
// path.mkdirs();
this.filePath = filePath;
// this.FolderPath = filePath;
}
private long startTime;
private long endTime;
/**
* 开始录音 使用amr格式 录音文件
*
* @return
*/
public void startRecord() {
// 开始录音
/* ①Initial:实例化MediaRecorder对象 */
if (mMediaRecorder == null)
mMediaRecorder = new MediaRecorder();
try {
/* ②setAudioSource/setVedioSource */
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风
/* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
/*
* ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
* ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
*/
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
// filePath = FolderPath + "tmp" + ".aac";
/* ③准备 */
mMediaRecorder.setOutputFile(filePath);
mMediaRecorder.setMaxDuration(MAX_LENGTH);
mMediaRecorder.prepare();
/* ④开始 */
mMediaRecorder.start();
// AudioRecord audioRecord.
/* 获取开始时间* */
startTime = System.currentTimeMillis();
updateMicStatus();
Log.e("fan", "startTime" + startTime);
} catch (IllegalStateException e) {
Log.e(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
} catch (IOException e) {
Log.e(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
}
}
/**
* 停止录音
*/
public long stopRecord() {
if (mMediaRecorder == null)
return 0L;
endTime = System.currentTimeMillis();
// 有一些网友反应在5.0以上在调用stop的时候会报错,翻阅了一下谷歌文档发现上面确实写的有可能会报错的情况,捕获异常清理一下就行了,感谢大家反馈!
try {
mMediaRecorder.stop();
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
audioStatusUpdateListener.onStop(filePath);
filePath = "";
} catch (RuntimeException e) {
// mMediaRecorder.reset();
// mMediaRecorder.release();
// mMediaRecorder = null;
e.printStackTrace();
// File file = new File(filePath);
// if (file.exists())
// file.delete();
//
// filePath = "";
}
return endTime - startTime;
}
/**
* 取消录音
*/
public void cancelRecord() {
try {
mMediaRecorder.stop();
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
} catch (RuntimeException e) {
mMediaRecorder.reset();
mMediaRecorder.release();
mMediaRecorder = null;
}
File file = new File(filePath);
if (file.exists())
file.delete();
filePath = "";
}
private final Handler mHandler = new Handler();
private Runnable mUpdateMicStatusTimer = new Runnable() {
public void run() {
updateMicStatus();
}
};
private int BASE = 1;
private int SPACE = 100;// 间隔取样时间
public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
this.audioStatusUpdateListener = audioStatusUpdateListener;
}
/**
* 更新麦克状态
*/
private void updateMicStatus() {
if (mMediaRecorder != null) {
double ratio = (double) mMediaRecorder.getMaxAmplitude() / BASE;
double db = 0;// 分贝
if (ratio > 1) {
db = 20 * Math.log10(ratio);
if (null != audioStatusUpdateListener) {
audioStatusUpdateListener.onUpdate(db, System.currentTimeMillis() - startTime);
}
}
mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);
}
}
public interface OnAudioStatusUpdateListener {
/**
* 录音中...
*
* @param db 当前声音分贝
* @param time 录音时长
*/
public void onUpdate(double db, long time);
/**
* 停止录音
*
* @param filePath 保存路径
*/
public void onStop(String filePath);
}
}
MainActivity.java
package com.example.record;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.coremedia.iso.boxes.Container;
import com.googlecode.mp4parser.authoring.Movie;
import com.googlecode.mp4parser.authoring.Track;
import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private TextView tv;
private EditText et;
private String fileName = "";
private AudioRecoderUtils audioRecoderUtils;
private boolean isRecording = false;
private String folder;
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
AlertDialog dialog = new AlertDialog.Builder(MainActivity.this)
.setMessage("文件已保存在" + folder + fileName + "(合成).mp4").create();
dialog.show();
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv);
et = findViewById(R.id.et);
File file = new File("/sdcard", "0000test");
if (file.exists() == false) {
file.mkdirs();
}
folder = file.getAbsolutePath() + "/";
}
// 录屏
public void start(View view) {
if (isRecording == true) {
Toast.makeText(getApplicationContext(), "不要重复点击哦", Toast.LENGTH_LONG).show();
return;
}
if (RootCmdUtils.haveRoot() == false) {
Toast.makeText(getApplicationContext(), "手机没有root,不能录屏", Toast.LENGTH_LONG).show();
return;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss");
fileName = sdf.format(new Date());
String videoPath = folder + fileName + ".mp4";
String cmd = "screenrecord " + videoPath;
// 开始录屏
RootCmdUtils.execRootCmdSilentOnThread(cmd);
Log.e("录屏", "开始录屏");
Toast.makeText(MainActivity.this, "开始录屏", Toast.LENGTH_LONG).show();
// 开始录制声音
audioRecoderUtils = new AudioRecoderUtils(folder + fileName + ".aac");
audioRecoderUtils.startRecord();
Log.e("录屏", "开始录制声音");
isRecording = true;
}
public void stop(View view) {
if (isRecording == false) {
return;
}
String pids = RootCmdUtils.execRootCmd("ps | grep screenrecord");
tv.setText(pids);
if (pids.isEmpty()) {
return;
}
String[] split = pids.split("\\s+");
for (int i = 0; i < split.length; i++) {
// root是执行这个进程的用户,它后面一列是这个进程的pid
if (split[i].contains("root")) {
String cmd = "kill -2 " + split[i + 1];// 相当于ctrl+C结束进程
RootCmdUtils.execRootCmdSilent(cmd);
}
}
// 停止声音录制
audioRecoderUtils.stopRecord();
Toast.makeText(getApplicationContext(), "结束", Toast.LENGTH_LONG).show();
isRecording = false;
}
public void mux(View v) {
new Thread() {
public void run() {
try {
String videoPath = folder + fileName + ".mp4";
String audioPath = folder + fileName + ".aac";
String targetFile = folder + fileName + "(合成).mp4";
Movie countVideo = MovieCreator.build(videoPath);
Movie countAudioEnglish = MovieCreator.build(audioPath);
Track audioTrackEnglish = countAudioEnglish.getTracks().get(0);
countVideo.addTrack(audioTrackEnglish);
Container out = new DefaultMp4Builder().build(countVideo);
FileOutputStream fos = new FileOutputStream(new File(targetFile));
out.writeContainer(fos.getChannel());
fos.close();
handler.sendEmptyMessage(0);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
};
}.start();
}
}
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.record.MainActivity" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="start"
android:text="开始录制" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="stop"
android:text="结束录制" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="mux"
android:text="合并音视频" />
</LinearLayout>
<EditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ps | grep 'screenrecord'" />
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hello_world" />
</LinearLayout>
AndroidManifest.xml权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
另外,合并音视频用的是第三方jar包:(百度云下载)密码:acka
aspectjrt-1.8.0.jar、isoparser-1.0.1.jar