预览
在页面中添加SurfaceView,同时添加SurfaceHolder.Callback:
1 2 3
| mSurfaceView = (SurfaceView) findViewById(R.id.surface_view); mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.addCallback(mSurfaceCallback);
|
在SurfaceHolder.Callback中的surfaceCreated方法中初始化相机,并开始预览:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| mSurfaceCallback = new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { boolean suc = true; if (mCamera == null) { suc = initCamera(); } if (suc) { startPreview(); } }
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override public void surfaceDestroyed(SurfaceHolder holder) {
} };
|
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
| private boolean initCamera() { int num = Camera.getNumberOfCameras(); if (num <= 0) { return false; } boolean open = true; try { mCamera = Camera.open(0); mCamera.setPreviewCallback(mPreviewCallback); mParameters = mCamera.getParameters(); mParameters.setRotation(90); mParameters.setPreviewFormat(ImageFormat.NV21); List<Camera.Size> list = mCamera.getParameters().getSupportedPreviewSizes(); if (list != null && !list.isEmpty()) { Camera.Size size = getClosestSupportedSize(list, 720, 1080); mParameters.setPreviewSize(size.width, size.height); mCamera.setParameters(mParameters); mCamera.setDisplayOrientation(90); return true; } } catch (Exception e) {
} return false; }
|
相机开启预览:
1 2
| mCamera.setPreviewDisplay(mSurfaceHolder); mCamera.startPreview();
|
视频数据保存
在相机初始化中添加PreviewCallback,每次预览画面更新时会回调onPreviewFrame方法:
1 2 3 4 5 6 7 8 9 10 11 12
| mPreviewCallback = (bytes, camera) -> { if (mIsRecording) { Frame frame = new Frame(); frame.mData = bytes; frame.mTime = System.nanoTime() / 1000; if (frame.mTime - mPreviewImgTime > 1000 * 1000) { mPreviewImgTime = frame.mTime; } mVideoEncoder.addFrame(bytes); } };
|
1 2 3 4 5 6 7
| private void startRecord() { mMuxer = new MMuxer(getSaveVideoPath()); mVideoEncoder = new VideoEncoder2(mMuxer); mVideoEncoder.prepare(); mVideoEncoder.start(); mIsRecording = true; }
|
将onPreviewFrame回调的data,传到VideoEncoder进行编码保存。
VideoEncoder
VideoEncoder的prepare方法会初始化MediaCodecInfo和MediaFormat,同时设置相应的帧率码率,并创建MediaCodec。
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 39 40 41 42 43
| public void prepare() { MediaCodecInfo codecInfo = selectCodec(MIME_TYPE); if (codecInfo == null) { return; } mColorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar; checkColorFormat(codecInfo, MIME_TYPE); Log.i(TAG, "colorformat=" + mColorFormat); MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, 720, 1080); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat); try { mMediaCodec = MediaCodec.createByCodecName(codecInfo.getName()); } catch (Exception e) { Log.i(TAG, e + ""); } try { if (!mIsAllKeyFrame) { mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); } else { mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 0); } mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); Log.i(TAG, "success configure-----------"); } catch (Exception e) { Log.v(TAG, "config failed " + e); try { mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); Log.i(TAG, "config second success"); } catch (Exception e1) { Log.i(TAG, "config second failed " + e1); } }
try { mMediaCodec.start(); } catch (Exception e) { Log.i(TAG, "start error--" + e); } mTrackIndex = -1; }
|
videoEncoder实现runnable接口。在start方法中会启动线程:
1 2 3 4
| public void start() { mIsRecording = true; new Thread(this).start(); }
|
在run方法中,会从帧队列中取出一帧输入,同时会间隔一段时间进行循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public void run() { int count = 0; while (true) { count++; if (mFrameList.size() > 0) { Frame frame = mFrameList.remove(0); input(frame); } try { Thread.sleep(mLoopInterval); } catch (InterruptedException e) { e.printStackTrace(); } if (!mIsRecording) { break; } } }
|
addFrame主要是想队列中添加帧数据
1 2 3 4 5 6
| public void addFrame(byte[] data) { Frame frame = new Frame(); frame.mTime = System.nanoTime() / 1000; frame.mData = data; mFrameList.add(frame); }
|
input方法一个是将数据进行转化,同时向MediaCodec的inputBuffers添加数据,然后会调用output方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| private void input(Frame frame) { ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers(); int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC); byte[] dst = null; if (frame.mData != null) { dst = new byte[frame.mData.length]; NV21toI420SemiPlanar(frame.mData, dst, 720, 1080); } if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); if (dst != null) { inputBuffer.put(dst); mMediaCodec.queueInputBuffer(inputBufferIndex, 0, dst.length, frame.mTime, 0); output(false); } else { mMediaCodec.queueInputBuffer(inputBufferIndex, 0, 0, frame.mTime, MediaCodec.BUFFER_FLAG_END_OF_STREAM); output(true); } } }
|
output方法主要是向MMuxer添加轨道,同时从MediaCodec获取数据并写入到MMuxer,而MMuxer跟音视频的录制(一)中相同。
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| public void output(boolean isEos) { String tag = TAG + "-output"; ByteBuffer[] outputBuffers = null; int count = 0; int outputIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); try { outputBuffers = mMediaCodec.getOutputBuffers(); do { if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { Log.i(tag, "output from encoder not available"); if (!isEos) { count++; if (count >= 10) {
} } } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { outputBuffers = mMediaCodec.getOutputBuffers(); Log.i(tag, "encoder output buffers changed"); } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { addTrack(); Log.i(tag, "encoder output format change"); } else if (outputIndex < 0) { Log.e(tag, "output buffer wrong " + outputIndex); } else { ByteBuffer outputBuffer = outputBuffers[outputIndex]; if (outputBuffer == null) { Log.e(tag, "output buffer null"); return; } if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { mBufferInfo.size = 0; } Log.d(tag, "buffer size=" + mBufferInfo.size + " pts=" + mBufferInfo.presentationTimeUs); if (mBufferInfo.size != 0) { if (!mMuxer.isVideoTrackAdd()) { addTrack(); } if (!mMuxer.isStarted()) { mMuxer.start(); } outputBuffer.position(mBufferInfo.offset); outputBuffer.limit(mBufferInfo.offset + mBufferInfo.size); mMuxer.writeSampleData(mTrackIndex, outputBuffer, mBufferInfo); } mMediaCodec.releaseOutputBuffer(outputIndex, false); if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { mIsRecording = false; stopMuxer(); release(); break; } } outputIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
} while (outputIndex >= 0); } catch (Exception e) { }
}
|