本小记继续上一篇的代码

主要实现功能为:对编码器编译压缩后输出的视频流用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();
        }
    }
}