最近搞了一个固定的帧率录屏的软件,硬生生把我一个对这一块的小白搞成了对这一块有深刻理解的人。
固定帧率录制就是MediaCodec中的Surface自定义一个,然后MediaCodec的Surface和自己定义的进行数据的传输。
Demo暂时不放了,出于公司的原因。关键代码已有,拷贝即可使用。
上代码:
1.录屏类:
public class MyShoter implements Runnable {
private static final String TAG = MyShoter.class.getSimpleName();
private static final String VIDEO_MIME_TYPE = "video/avc";
private static final int IFRAME_INTERVAL = 10;
private static final int TIMEOUT_US = 10000;
/**
* 录制的分辨率
*/
private int mWidth;
private int mHeight;
/**
* 录制的比特率
*/
private int mBitRate;
/**
* 录制的帧数
*/
private int mFrameRate; // 30 FPS
/**
* 这个数传 1 就可以
*/
private int mDpi;
/**
* 文件保存路径
*/
private String mSaveFilePath;
private MediaProjection mMediaProjection;
private MediaCodec mVideoEncoder;
private Surface mSurface;
private MediaMuxer mMuxer;
private boolean mMuxerStarted = false;
private int mVideoTrackIndex = -1;
private AtomicBoolean mAtomicQuit = new AtomicBoolean(false);
private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
private VirtualDisplay mVirtualDisplay;
private DisplayManager displayManager;
public MyShoter(int width, int height, int bitRate, int dpi, int fps,
MediaProjection mediaProjection, String filePath) {
this.mWidth = width;
this.mHeight = height;
this.mBitRate = bitRate;
this.mDpi = dpi;
this.mFrameRate = fps;
this.mMediaProjection = mediaProjection;
if (!filePath.endsWith(".mp4"))
filePath += ".mp4";
this.mSaveFilePath = filePath;
}
public void stopShot() {
if (eglRender != null) {
eglRender.stop();
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void run() {
Log.e(TAG, "run: " + mWidth + " " + mHeight + " " + mBitRate + " " + mDpi + " " +
mFrameRate);
try {
prepareEncoder();
mMuxer = new MediaMuxer(mSaveFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
Log.e(TAG, "run: mMuxer init");
int formatWidth = mWidth;
int formatHeight = mHeight;
if ((formatWidth & 1) == 1) {
formatWidth--;
}
if ((formatHeight & 1) == 1) {
formatHeight--;
}
if (mMediaProjection != null) {
mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen", formatWidth, formatHeight, mDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, eglRender.getDecodeSurface(), null, null);
} else {
mVirtualDisplay = displayManager.createVirtualDisplay("screen", formatWidth, formatHeight, mDpi,
eglRender.getDecodeSurface(), DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC);
}
startRecordScreen();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Muxur:::", e);
} finally {
releaseEncoder();
}
}
/**
* 开始录屏
*/
private void startRecordScreen() {
eglRender.start();
releaseEncoder();
}
private EGLRender eglRender;
private void prepareEncoder() throws IOException {
int formatWidth = mWidth;
int formatHeight = mHeight;
if ((formatWidth & 1) == 1) {
formatWidth--;
}
if ((formatHeight & 1) == 1) {
formatHeight--;
}
//类型
MediaFormat format =
MediaFormat.createVideoFormat(VIDEO_MIME_TYPE, formatWidth, formatHeight);
//颜色格式
//从Surface当中获取的
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
//码率
format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
//帧率
format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
//关键帧间隔
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
//编码器
mVideoEncoder = MediaCodec.createEncoderByType(VIDEO_MIME_TYPE);
//将参数配置给编码器
mVideoEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
//交给虚拟屏幕 通过OpenGL 将预览的纹理 会知道这一个虚拟屏幕中
//这样MediaCodec就会自动编码mInputSurface当中的图像了
mSurface = mVideoEncoder.createInputSurface();
//录屏的帧率
eglRender = new EGLRender(mSurface, formatWidth, formatHeight, mFrameRate);
eglRender.setCallBack(new EGLRender.onFrameCallBack() {
@Override
public void onUpdate() {
startEncode();
}
@Override
public void onCutScreen(Bitmap bitmap) {
}
});
mVideoEncoder.start();
}
private void startEncode() {
ByteBuffer[] byteBuffers = null;
if (Build.VERSION.SDK_INT< 21) {
byteBuffers = mVideoEncoder.getOutputBuffers();
}
int index = mVideoEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);
if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
resetOutputFormatNew();
} else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
Log.d("---", "retrieving buffers time out!");
try {
// wait 10ms
Thread.sleep(10);
} catch (InterruptedException e) {
}
} else if (index >= 0) {
if (Build.VERSION.SDK_INT< 21) {
encodeToVideoTrackNew(byteBuffers[index]);
} else {
encodeToVideoTrackNew(mVideoEncoder.getOutputBuffer(index));
}
mVideoEncoder.releaseOutputBuffer(index, false);
}
}
private byte[] sps=null;
private byte[] pps=null;
private void encodeToVideoTrackNew(ByteBuffer encodeData) {
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
mBufferInfo.size = 0;
}
if (mBufferInfo.size == 0) {
Log.d(TAG, "info.size == 0, drop it.");
encodeData = null;
} else {
Log.d(TAG, "got buffer, info: size=" + mBufferInfo.size
+ ", presentationTimeUs=" + mBufferInfo.presentationTimeUs
+ ", offset=" + mBufferInfo.offset);
}
if (encodeData != null) {
encodeData.position(mBufferInfo.offset);
encodeData.limit(mBufferInfo.offset + mBufferInfo.size);
mMuxer.writeSampleData(mVideoTrackIndex, encodeData, mBufferInfo);
byte[] bytes;
if (mBufferInfo.flags == MediaCodec.BUFFER_FLAG_KEY_FRAME) {
//todo 关键帧上添加sps,和pps信息
bytes = new byte[mBufferInfo.size + sps.length + pps.length];
System.arraycopy(sps