本小记继续上一篇的代码
主要实现功能为:对编码器编译压缩后输出的视频流用MediaMuxer封装到MP4容器。
//采用线程进行录屏的耗时操作
public class VideoThread extends Thread {
private Context mContext;
private static final String MIME_TYPE = "video/avc";
private VirtualDisplay virtualDisplay;
private MediaProjection projection;
private AtomicBoolean mQuit = new AtomicBoolean(false);
private int videoWidth;
private int videoHeight;
private Handler handler = null;
//构造函数,传入上下文和屏幕大小
public VideoThread(Context context, int width, int height){
mContext = context;
videoWidth = width;
videoHeight = height;
}
public final void quit(){
mQuit.set(true);
}
@Override
public void run(){
Surface surface = null;
MediaMuxer mediaMuxer = null; //MediaMuxer的使用要按照Constructor -> addTrack -> start -> writeSampleData -> stop 的顺序
MediaCodec mediaCodec = null;
//获取文件保存路径
long timeMillis = System.currentTimeMillis();
Date date = new Date(timeMillis);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
String path = "ScreenShot" + simpleDateFormat.format(date) + ".mp4";
File file = new File(Environment.getExternalStorageDirectory(), path);
File parentFile = file.getParentFile();
if (parentFile == null || !parentFile.exists()) {
parentFile.mkdir();
}
//初始化编码器、创建MediaFormat设置Media格式
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, videoWidth, videoHeight);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 32); //帧数
format.setInteger(MediaFormat.KEY_BIT_RATE, 2500000); //码流
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); //关键帧
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); //颜色格式
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 0); //音频的频道数(单声道或者双声道)
format.setInteger(MediaFormat.KEY_CAPTURE_RATE, 32); //捕获率 (当CAPTURE_RATE和FRAME_RATE不一样时,视频播放会加快或减慢)
format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / 15);
try {
try {//创建编码器(MediaCodec用于将音视频进行压缩编码,可以对Surface内容进行编码)
mediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
} catch (Exception e) {
e.printStackTrace();
}
//配置MedidaCodec解码器、获取surface作为输入(必须在MediaCodec解码器configure之后,start之前)
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
surface = mediaCodec.createInputSurface();//请求一个surface用于编码器的输入,而不是常用的inputbuffer输入
mediaCodec.start();
try {//MediaMuxer来封装编码后的视频流和音频流到mp4容器、通过Muxer指定视频文件输出路径和文件格式
mediaMuxer = new MediaMuxer(file.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
} catch (Exception e) {
e.printStackTrace();
mediaMuxer = null;
}
if (mediaMuxer == null) {
return;
}
/*创建虚拟屏幕以捕获屏幕内容
*
* MediaProjectionManager manager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
* startActivityForResult(manager.createScreenCaptureIntent(), 1); //返回一个必须传递给startActivityForResult()的意图,以便开始屏幕捕获。(该活动将提示用户是否允许屏幕捕获)
*
* @Override
* public void onActivityResult(int requestCode, int resultCode, Intent data){
* if (requestCode == 1) {
* if (resultCode != Activity.RESULT_OK) {
* return;
* }
* mResultCode = resultCode;
* mResultData = data;
* }
* }
*
* public static MediaProjection getProjection(){
* if (projection == null) {
* projection = manager.getMediaProjection(mResultCode, mResultData); //成功获取屏幕捕获请求后,检索MediaProjection。
* }
* return projection;
* }
*
*/
projection = MainActivity.getProjection();
virtualDisplay = projection.createVirtualDisplay("display", videoWidth, videoHeight, 1, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); //获取每一个输出数据buffer的元数据信息,例如偏差,在相关解码器中有效的数据大小
int videoTrackIndex = -1;
while (!mQuit.get()) {
int index = mediaCodec.dequeueOutputBuffer(bufferInfo, 10000); //尝试获取输出数据的信息,关于bytebuffer的信息将封装在bufferinfo里面,返回该bytebuffer在队列中的位置
//MediaCodec在一开始调用dequeueOutputBuffer()时会返回一次INFO_OUTPUT_FORMAT_CHANGED消息
switch (index) {
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
MediaFormat newFormat = mediaCodec.getOutputFormat(); //在dequeueOutputBuffer 返回 INFO_OUTPUT_FORMAT_CHANGED信息后调用,可以查看当前媒体格式信息
videoTrackIndex = mediaMuxer.addTrack(newFormat); //添加当前媒体格式的索引
mediaMuxer.start();
break;
case MediaCodec.INFO_TRY_AGAIN_LATER: //如果等待timeoutUs时间还没响应则跳过,返回TRY_AGAIN_LATER
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
break;
default:
ByteBuffer byteBuffer = mediaCodec.getOutputBuffer(index); //根据Index获取编码成功的bytebuffer
if (byteBuffer != null) {
byteBuffer.position(bufferInfo.offset); //设置buffer位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变改值,为下次读写作准备
byteBuffer.limit(bufferInfo.offset + bufferInfo.size); //limit表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的
mediaMuxer.writeSampleData(videoTrackIndex, byteBuffer, bufferInfo); //将当前解码器的buffer数据和buffer数据对应的bufferInfo写入Muxer
}
mediaCodec.releaseOutputBuffer(index, false); //释放刚刚从Codec取出数据的bytebuffer,供Codec继续放数据。
break;
}
}
}
finally {
//录制完成后,释放各项资源
try {
if (mediaMuxer != null) {
mediaMuxer.stop();
mediaMuxer.release();
}
}catch (Exception e){
e.printStackTrace();
}
try {
if (surface != null) {
surface.release();
}
}catch (Exception e){
e.printStackTrace();
}
try {
if (mediaCodec != null) {
mediaCodec.release();
}
}catch (Exception e){
e.printStackTrace();
}
File dstFile = new File(file.getAbsolutePath());
dstFile.setReadable(true, false); //第一个true表示是否可读,第二个参数(true : 对所有人有效 false:对文件拥有者有效)
}
if (file.exists()) {
/*用于线程中的消息处理,因为非主线程没有默认创建Looper对象,需要调用该方法启动Looper
* 通过Handler对象来与Looper进行交互的。Handler可看做是Looper的一个接口,用来向指定的Looper发送消息及定义处理方法。
* Looper.loop(); 让Looper开始工作,从消息队列里取消息,处理消息。
* 注意:写在Looper.loop()之后的代码不会被执行,这个函数内部应该是一个循环,当调用mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。
*/
Looper.prepare();
Message message = new Message();
android.os.Handler handler = new android.os.Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
switch (msg.what) {
case 1 :
Toast.makeText(mContext, "File is exists", Toast.LENGTH_SHORT).show();break;
}
}
};
message.what = 1;
handler.sendMessage(message);
Looper.loop();
}
}
}