Real Time Messaging Protocol(RTMP)即实时消息传输协议,是 Adobe 公司开发的一个基于 TCP 的应用层协议,目前国内的视频云服务都是以 RTMP 为主要推流协议。
关于RTMP推流组件
EasyRTMP是一套调用简单、功能完善、运行高效稳定的RTMP推流功能组件,经过多年客户实战和线上运行打造,支持RTMP推送断线重连、环形缓冲、智能丢帧、网络事件回调,支持Windows、Linux、ARM、Android、iOS平台,支持市面上绝大部分的RTMP流媒体服务器,能够完美应用于各种行业的直播需求,手机直播、桌面直播、摄像机直播、课堂直播等方面。结合EasyDSS流媒体服务器,为开发者提供专业、稳定的直播推流、转码、分发服务,全面满足低超低延迟、超高画质、超大并发访问量的要求。
EasyRTMP-Android介绍屏幕推流及其注意点
解决问题
1、录屏
VirtualDisplay类代表一个虚拟显示器,需要调用DisplayManager 类的 createVirtualDisplay()方法,将虚拟显示器的内容渲染在一个Surface控件上,当进程终止时虚拟显示器会被自动的释放,并且所有的Window都会被强制移除。当不再使用时,需要调用release() 方法来释放资源。
1)获取MediaProjectionManager实例
mMpmngr = (MediaProjectionManager) getApplicationContext()
.getSystemService(MEDIA_PROJECTION_SERVICE);
2)在Android 6.0后,Android需要动态获取权限,若没有权限,则不启用该service:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
return;
}
}
3)MediaProjection是Android5.0后才开放的屏幕采集接口,通过系统级服务MediaProjectionManager进行管理。
if (mMpj == null) {
mMpj = mMpmngr.getMediaProjection(StreamActivity.mResultCode,
StreamActivity.mResultIntent);
StreamActivity.mResultCode = 0;
StreamActivity.mResultIntent = null;
}
4)通过MediaProjection对象的createVirtualDisplay方法,拿到VirtureDisplay对象,拿这个对象的时候,需要把Surface对象传进去,Surface是由MediaCodec获得。
mVirtualDisplay = mMpj.createVirtualDisplay(
"record_screen",
windowWidth,
windowHeight,
screenDensity,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION,
mSurface,
null,
null);
2、编码及推流
使用Android硬编码MediaCodec,将VirtualDisplay获取到的视频数据编码后通过EasyRTMP推送出去。
1)初始化MediaCodec,同时获取Surface对象,并启动编码器
mMediaCodec = MediaCodec.createByCodecName(ci.mName);
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc",
windowWidth, windowHeight);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
mediaFormat.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER,
20000000);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
mMediaCodec.configure(mediaFormat, null, null,
MediaCodec.CONFIGURE_FLAG_ENCODE);
// 获取Surface对象
mSurface = mMediaCodec.createInputSurface();
mMediaCodec.start();
2)音频编码的工作直接使用AudioStream,
final AudioStream audioStream =
AudioStream.getInstance(EasyApplication.getEasyApplication(),
SPUtil.getEnableAudio(EasyApplication.getEasyApplication()));
3)启动编码线程,不停的将获取的音频帧和视频帧,编码并推流。详情见代码注释:
// 启动线程,
mPushThread.start();
// 初始化推流器
mEasyPusher = new EasyRTMP(mHevc ? EasyRTMP.VIDEO_CODEC_H265 :
EasyRTMP.VIDEO_CODEC_H264, RTMP_KEY);
// 视频的硬编码
ByteBuffer outputBuffer;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex);
} else {
outputBuffer = outputBuffers[outputBufferIndex];
}
outputBuffer.position(bufferInfo.offset);
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
try {
boolean sync = false;
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
sync = (bufferInfo.flags&MediaCodec.BUFFER_FLAG_SYNC_FRAME)!=0;
if (!sync) {
byte[] temp = new byte[bufferInfo.size];
outputBuffer.get(temp);
mPpsSps = temp;
continue;
} else {
mPpsSps = new byte[0];
}
}
sync |= (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
int len = mPpsSps.length + bufferInfo.size;
if (len > h264.length) {
h264 = new byte[len];
}
// 将编码后的视频数据发送出去
if (sync) {
System.arraycopy(mPpsSps, 0, h264, 0, mPpsSps.length);
outputBuffer.get(h264, mPpsSps.length, bufferInfo.size);
mEasyPusher.push(h264, 0, mPpsSps.length + bufferInfo.size, bufferInfo.presentationTimeUs / 1000, 2);
} else {
outputBuffer.get(h264, 0, bufferInfo.size);
mEasyPusher.push(h264, 0, bufferInfo.size, bufferInfo.presentationTimeUs / 1000, 1);
}