Real Time Messaging Protocol(RTMP)即实时消息传输协议,是 Adobe 公司开发的一个基于 TCP 的应用层协议,目前国内的视频云服务都是以 RTMP 为主要推流协议。

关于RTMP推流组件

EasyRTMP是一套调用简单、功能完善、运行高效稳定的RTMP推流功能组件,经过多年客户实战和线上运行打造,支持RTMP推送断线重连、环形缓冲、智能丢帧、网络事件回调,支持Windows、Linux、ARM、Android、iOS平台,支持市面上绝大部分的RTMP流媒体服务器,能够完美应用于各种行业的直播需求,手机直播、桌面直播、摄像机直播、课堂直播等方面。结合EasyDSS流媒体服务器,为开发者提供专业、稳定的直播推流、转码、分发服务,全面满足低超低延迟、超高画质、超大并发访问量的要求。

android拉取rtmp流 安卓rtmp拉流_网络直播

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);
	}