音视频开发路线:
Android 音视频开发入门指南_Jhuster的专栏的技术博客_51CTO博客_android 音视频开发入门
demo地址:
GitHub - wygsqsj/videoPath: 音视频学习路线demo
录屏功能
录屏需要通过系统构建的Intent再通过startActivityForResult跳转,从回调中拿到MediaProjection,并为他指定Surface,我们的录屏数据就会写入到这个Surface中
1.获取录屏API
private MediaProjection mMediaProjection; //录屏api
private MediaProjectionManager mediaManager;
//google新版请求回调,用于替换startActivityForResult
private ActivityResultLauncher<Intent> resultLauncher;
private void initMediaProjection() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mediaManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
//代替startActivityForResult
resultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getData() != null) {
mMediaProjection = mediaManager.getMediaProjection(result.getResultCode(), result.getData());
startDisplay();
}
});
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void screenRecording(View view) {
if (mScreenEncode.getText().toString().contains("开始录屏")) {
resultLauncher.launch(mediaManager.createScreenCaptureIntent());
} else {
screenThread.stopEncode();
mScreenEncode.setText("开始录屏");
screenThread = null;
}
}
2.将MediaProjection传递给编码线程配置
out264File = new File(demo6Activity.getExternalFilesDir(Environment.DIRECTORY_MOVIES), "cameraOfScreen.h264");
out264File.createNewFile();
fos = new FileOutputStream(out264File);
//构建对应的MeidaFormat
MediaFormat mediaFormat = MediaFormat.createVideoFormat(encodeMine, width, height);
//注意此处要设置成surface类型
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
//比特率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);
//描述视频格式的帧速率
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
//关键帧之间的间隔,此处指定为1秒
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
//构建编码h264MediaCodec
encodeCodec = MediaCodec.createEncoderByType(encodeMine);
encodeCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
inputSurface = encodeCodec.createInputSurface();
initVirtualDisplay();
MediaCodec.BufferInfo encodeBufferInfo = new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息
//启动编码器
encodeCodec.start();
MideaFormat的配置项也要相应的改变:
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void initVirtualDisplay() {
mMediaProjection.createVirtualDisplay(
LOG_TAG, //virtualDisplay 的名字,随意写
width,
height,
demo6Activity.getResources().getDisplayMetrics().densityDpi, // virtualDisplay 的 dpi 值,这里都跟应用保持一致即可
// 显示的标志位,不同的标志位
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
inputSurface, //获取内容的 surface
null, //回调
null); //回调执行的handler
}
将MediaCodec构建的Surface传递给MediaProjection,这样MediaCodec只需要从OutPutBuffer中取出编码好的264数据即可,不再需要手动为MediaCodec输入数据了
3.取出数据写入文件
while (isEncode) {
int outputIndex = encodeCodec.dequeueOutputBuffer(encodeBufferInfo, 10000);//返回当前筐的标记
switch (outputIndex) {
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
Log.i(LOG_TAG, "输出的format已更改" + encodeCodec.getOutputFormat());
break;
case MediaCodec.INFO_TRY_AGAIN_LATER:
Log.i(LOG_TAG, "超时,没获取到");
break;
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
Log.i(LOG_TAG, "输出缓冲区已更改");
break;
default:
Log.i(LOG_TAG, "获取到surface中的数据,当前解析后的数据长度为:" + encodeBufferInfo.size);
//获取所有的筐
ByteBuffer[] outputBuffers = encodeCodec.getOutputBuffers();
//拿到当前装满火腿肠的筐
ByteBuffer outputBuffer;
if (Build.VERSION.SDK_INT >= 21) {
outputBuffer = encodeCodec.getOutputBuffer(outputIndex);
} else {
outputBuffer = outputBuffers[outputIndex];
}
//将数据读取到outData中
byte[] outData = new byte[encodeBufferInfo.size];
outputBuffer.get(outData);
//当前是初始化编解码器数据,不是媒体数据,sps、pps等初始化数据
if (encodeBufferInfo.flags == BUFFER_FLAG_CODEC_CONFIG) {
Log.i(LOG_TAG, "获取到初始化编解码器数据,长度为:" + encodeBufferInfo.size);
configByte = new byte[encodeBufferInfo.size];
configByte = outData;
} else if (encodeBufferInfo.flags == BUFFER_FLAG_KEY_FRAME) {//当前的数据中包含关键帧数据
Log.i(LOG_TAG, "获取到I帧数据,长度为:" + encodeBufferInfo.size);
//将初始化数据和当前的关键帧数据合并后写入到h264文件中
byte[] keyframe = new byte[encodeBufferInfo.size + configByte.length];
System.arraycopy(configByte, 0, keyframe, 0, configByte.length);
//把编码后的视频帧从编码器输出缓冲区中拷贝出来
System.arraycopy(outData, 0, keyframe, configByte.length, outData.length);
fos.write(keyframe, 0, keyframe.length);
} else {
//写到文件中
fos.write(outData, 0, outData.length);
}
//把筐放回工厂里面
encodeCodec.releaseOutputBuffer(outputIndex, false);
break;
}
}