这篇博客应该是相当有分量的博客了。篇幅会比较长,因为内容很多。我尽力的想写的详细,而又不至于繁琐。这之间的程度是很难把握的,话不多说 进入主题。
首先,在这之前,需要对几个类,以及他们的方法的有所了解。
MediaCodec
谷歌对这个类的描述如下,MediaCodec类可用于访问底层媒体编解码器,即编码器/解码器组件。它是Android底层多媒体支持基础架构的一部分(通常与MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、MediaDrm、Image、Surface和AudioTrack一起使用)。重点是 编码解码器,因为系统产生的数据 ,都是原始的数据,需要他进行处理。
原理:
下面这张图,只需要粗略看一看,你只需要知道 MediaCodec 有两个ByteBuffer,一个是输入,一个是输出。这也很好理解。毕竟编码解码器,肯定是要你给它旧数据,它编码解码完,还给你一个新数据。两个ByteBuffer 就相当于两个篮子,接受发送数据。
重要方法:
//返回要用有效数据填充的输入缓冲区的索引,如果当前没有可用的缓冲区,则返回-1。如果timeoutUs == 0,该方法将立即返回;如果timeoutUs < 0,则无限期等待输入缓冲区的可用性;如果timeoutUs > 0,则等待“timeoutUs”微秒。
public int dequeueInputBuffer (long timeoutUs)
这个方法呢,就是返回 输入缓冲区的索引(mediaCodec可以通过索引找到缓冲区)。也就是上面的ByteBuffer。
//通过上面的索引,找到输入缓冲区。
public ByteBuffer getInputBuffer (int index)
注意 上面这个是input
//返回输出缓冲区队列索引,最多阻塞“timeoutUs”微秒。返回已成功解码的输出缓冲区和INFO_*常量之一的索引。
//info 就是描述输出缓冲区数据的,例如时间,大小
public int dequeueOutputBuffer (MediaCodec.BufferInfo info, long timeoutUs)
//通过上面的索引,找到输出缓冲区。
public ByteBuffer getOutputBuffer (int index)
注意 上面这个是output
//释放输出缓冲区 ,这个也好理解,你从输出缓冲区取完数据了,得要把缓冲区清空,放回去,取下一次的数据
public void releaseOutputBuffer (int index, boolean render)
MediaMuxer
谷歌描述:MediaMuxer为muxing基本流提供便利。目前MediaMuxer支持MP4、Webm和3GP文件作为输出。它还支持muxing b帧在MP4自从Android牛轧糖。
上面编码解码完的数据,还需要写入到文件里面,这个类呢,主要就是帮助我们写文件的。
重要方法
//添加具有指定格式的跟踪。
public int addTrack (MediaFormat format)
上面这个呢,如果你了解视频的话就知道,视频里面画面 和声音 是两个不同的东西,但是都在一个文件里面。所以,他们有一个叫信道的东西。比如,声音在1信道,画面在2信道之类的。MediaMuxer只有知道信道,才知道接下来的数据要写到哪里。
//写入数据的方法
public void writeSampleData (int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo)
上面这个 byteBuf参数 就是mediaCodec的输出缓冲区,info 也是mediaCodec输出缓冲区的,我们可以info里面的值进行更改,例如时间,这样就可以暂停视频
附加:
录制视频画面的方向。
public void setOrientationHint (int degrees)
要注意,应该在start()之前调用这个方法
MediaRecorder
这个类呢,综合了上面mediaCodec和mediaMuxer,使用这个类,你可以很轻松的录制到本地。这个只能视频录制到本地,不能用于直播,因为你取不到编码后的数据。
权限申请
录屏权限
录屏权限是需要申请的,每次录制都必须要申请。
//获取MediaProjectionManager ,通过这个类 申请权限,
// 录屏是一个危险的权限,所以每次录屏的时候都得这么申请,用户同意了才行
MediaProjectionManager projectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
//开启activity 然后在onActivityResult回调里面判断 用户是否同意录屏权限
startActivityForResult(projectionManager.createScreenCaptureIntent(), REQUSET_VIDEO);
申请后需要查看用户是否同意,在onActivityResult周期里面查看
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUSET_VIDEO && resultCode == RESULT_OK) {
//已经成功获取到权限
//这个是生成虚拟屏幕所需要的
MediaProjection projection = projectionManager.getMediaProjection(resultCode, data);
}
}
注意上面的 MediaProjection,我下面所有的代码,都会用它,一定要记得了。
录音和读写文件权限
在androidManiFest.xml 里面
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
从android6.0以后还需要 动态申请一下。
/**
* 请求录音读写权限
*/
void requestPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PERMISSION_DENIED) {
String[] permission = {
Manifest.permission.RECORD_AUDIO};
requestPermissions(permission, REQUSET_AUDIO);
}
}
在onRequestPermissionsResult周期里面得到结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUSET_AUDIO) {
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == PERMISSION_GRANTED) {
Toast.makeText(this, "获得权限" + permissions[i], Toast.LENGTH_SHORT).show();
}
}
}
}
MediaRecorder使用范例
注意将上面申请录屏后生成的MediaProjection 传入
//录制视频 放在子线程最好,所以线程
class MediaRecordThread extends Thread {
private int mWidth;//录制视频的宽
private int mHeight;//录制视频的高
private int mBitRate;//比特率 bits per second 这个经过我测试 并不是 一定能达到这个值
private int mDpi;//视频的DPI
private String mDstPath;//录制视频文件存放地点
private MediaRecorder mMediaRecorder;//通过这个类录制
private MediaProjection mediaProjection;//通过这个类 生成虚拟屏幕
private final int FRAME_RATE = 60;//视频帧数 一秒多少张画面 并不一定能达到这个值
private VirtualDisplay virtualDisplay;
MediaRecordThread(int width, int height, int bitrate, int dpi, MediaProjection mediaProjection, String dstPath) {
mWidth = width;
mHeight = height;
mBitRate = bitrate;
mDpi = dpi;
this.mediaProjection = mediaProjection;
mDstPath = dstPath;
}
@Override
public void run() {
try {
//先实例化
initMediaRecorder();
//下面这个方法的 width height 并不是录制视频的宽高。他更明显是虚拟屏幕的宽高
//注意 mediaRecorder.getSurface() 这里我们mediaRecorder的surface 传递给虚拟屏幕,
// 虚拟屏幕显示的内容就会反映在这个surface上面,自然也就可以录制了
virtualDisplay = mediaProjection.createVirtualDisplay("luing", mWidth, mHeight, mDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mediaRecorder.getSurface(), null, null
);
//开始
mMediaRecorder.start();
Zprint.log(this.getClass(), "录屏线程内部开始工作");
} catch (IllegalStateException