一、录屏H264编码简单流程
1. H264编码配置类
public class VideoUtils {
// 360*640(1000_000) 540*960(高清 1500_000) 720*1280(超清 1800_000) 1080*1920(蓝光 2500_000)
// MIMETYPE_VIDEO_AVC MIMETYPE_VIDEO_MPEG4
public static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
public static final int DECODE_HEIGHT = 1080;
public static final int DECODE_WIDTH = 1920;
public static final int BIT_RATE = 2500_000; // 码率
// 码率控制方式 CQP(恒定质量),CRF(恒定码率),VBR(平均码率)。 VBR好一些,不会出现花点
public static final int BITRATE_MODE = MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR;
public static final int FRAME_RATE = 30; // I帧刷新频率
public static final int I_FRAME_INTERVAL = 1; // 每秒刷新
}
2. 录屏H264编码代码
/**
* H264视频编码
* MediaProjection(录屏数据) + MediaCodec H264编码
*/
public class MediaVideoEncoder implements Runnable {
private static final String MIME_TYPE = VideoUtils.MIME_TYPE;
private AtomicBoolean isStart = new AtomicBoolean(false);
private MediaProjection mediaProjection;
private MediaCodec mediaCodec;
private Surface surface;
// 录频相关
private VirtualDisplay virtualDisplay;
private byte[] sps;
private byte[] pps;
private OnFrameListener onFrameListener;
private int timeoutUs = 30;
public MediaVideoEncoder(MediaProjection mediaProjection) {
this.mediaProjection = mediaProjection;
initMediaCode();
}
private void initMediaCode() {
try {
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, VideoUtils.DECODE_WIDTH, VideoUtils.DECODE_HEIGHT);
// 码率控制方式 CQP(恒定质量),CRF(恒定码率),VBR(平均码率)。 VBR好一些,不会出现花点
format.setInteger(MediaFormat.KEY_BITRATE_MODE, VideoUtils.BITRATE_MODE);
format.setInteger(MediaFormat.KEY_BIT_RATE, VideoUtils.BIT_RATE);
format.setInteger(MediaFormat.KEY_FRAME_RATE, VideoUtils.FRAME_RATE);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, VideoUtils.I_FRAME_INTERVAL);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
mediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
surface = mediaCodec.createInputSurface();
// 录频关联MediaCodec
virtualDisplay = mediaProjection.createVirtualDisplay(
"VirtualDisplay", VideoUtils.DECODE_WIDTH, VideoUtils.DECODE_HEIGHT, 1,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
surface, null, null);
} catch (Exception e) {
e.printStackTrace();
isStart.set(false);
}
}
public void start() {
if (isStart.compareAndSet(false, true)) {
Executors.newSingleThreadExecutor().execute(this);
}
}
public void stop() {
isStart.set(false);
}
@Override
public void run() {
if (mediaCodec == null) {
Log.e("TAG", "initMediaCode 失败...");
return;
}
Log.e("TAG", "start h264 encode...");
mediaCodec.start();
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
// 计算PTS
// DTS表示解码时间戳,在什么时候解码这一帧的数据.
// PTS表示显示时间戳 ,在什么时候显示这一帧。
long pts = 0;
while (isStart.get()) {
int index = mediaCodec.dequeueOutputBuffer(info, timeoutUs);
if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// sps pps
MediaFormat outputFormat = mediaCodec.getOutputFormat();
ByteBuffer csd0 = outputFormat.getByteBuffer("csd-0");
ByteBuffer csd1 = outputFormat.getByteBuffer("csd-1");
if (csd1 != null) {
sps = new byte[csd0.remaining()];
pps = new byte[csd1.remaining()];
csd0.get(sps, 0, csd0.remaining());
csd1.get(pps, 0, csd1.remaining());
}
} else if (index >= 0) {
// 其它帧
ByteBuffer buffer = mediaCodec.getOutputBuffer(index);
buffer.position(info.offset);
buffer.limit(info.size);
if (pts == 0) {
pts = info.presentationTimeUs;
}
info.presentationTimeUs -= pts;
// 处理帧数据
frameData(info, buffer, info.presentationTimeUs);
mediaCodec.releaseOutputBuffer(index, false);
}
}
// 回收
release();
Log.e("TAG", "stop h264 encode...");
}
// 回收
private void release() {
if (mediaCodec != null) {
mediaCodec.stop();
mediaCodec.release();
}
if (virtualDisplay != null) {
virtualDisplay.release();
}
if (mediaProjection != null) {
mediaProjection.stop();
}
mediaCodec = null;
virtualDisplay = null;
mediaProjection = null;
onFrameListener = null;
sps = null;
pps = null;
}
private void frameData(MediaCodec.BufferInfo info, ByteBuffer buffer, long time) {
// I B P及其它帧
byte[] data = new byte[info.size];
buffer.get(data);
int type = info.flags;
// 1. 如果是I帧就发sps pps
if (type == MediaCodec.BUFFER_FLAG_KEY_FRAME) {
// 1. 发个sps pps
if (sps != null && pps != null) {
if (onFrameListener != null) {
onFrameListener.onSpsPpsListener(type, sps, sps.length, pps, pps.length);
}
}
}
// 2. 发帧数据
if (onFrameListener != null) {
onFrameListener.onFameListener(type, data, data.length, time);
}
}
public interface OnFrameListener {
// sps pps 回调
void onSpsPpsListener(int type, byte[] sps, int spsLen, byte[] pps, int ppsLen);
// I B P 帧回调 type = 1 时是I帧
void onFameListener(int type, byte[] data, int len, long time);
}
public void setOnFrameListener(OnFrameListener onFrameListener) {
this.onFrameListener = onFrameListener;
}
}
- 使用测试类
// 录屏推流类
public class RtmpPushActivity extends AppActivity implements ProjectionObserver, XPermissionListener {
private ProjectionUtils mProjectionUtils;
private MediaVideoEncoder mVideoEncoder;
@Override
protected void initTitle(DefTitleBar titleBar) {
titleBar.setTitle("RtmpPush");
}
@Override
public int getContentLayout() {
return R.layout.activity_push_rtmp;
}
@Override
public void initData(Bundle savedInstanceState) {
// 录屏帮助类
mProjectionUtils = new ProjectionUtils();
// 创建录屏MediaProjection类成功回调
ProjectionObservable.getInstance().register(this);
}
// 1. 开启录屏申请
public void start(View view) {
// 1. 方法调用后,到->onActivityResult
mProjectionUtils.start(this);
}
// 2.处理录屏申请
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 2. 处理录屏申请 -> 到onChange(MediaProjection mediaProjection)
mProjectionUtils.onActivityResult(requestCode, resultCode, data);
}
// 3. 录屏创建MediaProjection成功回调
@Override
public void onChange(MediaProjection mediaProjection) {
if (mVideoEncoder != null){
mVideoEncoder.stop();
}
// 录屏H264编码
mVideoEncoder = new MediaVideoEncoder(mediaProjection);
// 开启录屏编码
mVideoEncoder.start();
}
// 4.停止录频
public void stop(View view) {
if (mProjectionUtils != null) {
mProjectionUtils.stop();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mProjectionUtils != null) {
mProjectionUtils.stop();
}
if (mVideoEncoder != null){
mVideoEncoder.stop();
}
// 反注册录屏回调
ProjectionObservable.getInstance().unregister(this);
}
}
4.补充说明
- MediaProjection 这个类是屏幕服务创建的。具体请看Android RTMP录频直播一(录屏)。
- MediaCodec进行H264编码时,sps pps只会编一次,得保存起来,在每次I帧发送前,先发sps pps,否则播放端解码时拿不到sps pps,播放不了视频。