一、前言在之前已经详细介绍了Android中的一种视频数据源:Camera,不了解的同学可以点击进入:Android中Camera使用详解 ,在这篇文章中我们介绍了如何采集摄像头的每一帧数据,然后进行二次处理,例子中主要使用了对每一帧数据进行格式转化:NV21转化成ARGB格式,然后生成一张图片,然后添加水印效果。那么本文就来开始介绍如何将摄像头视频源数据的每一帧进行二次处理,然后推流到服务端。推流现在有很多种方式,但是更多的是基于FFMpeg,而现在各个云服务平台已经推出了直播SDK了,本文就来介绍一下金山云SDK,看看如何使用金山云SDK来进行推流,但是在使用之前,我们肯定得先了解他的原理,不过先来看一张效果图: 看到了吧,这里使用手机后置摄像头采集数据,然后推流到服务端,然后本地使用VLC进行观看,因为制作gif的关系,所以画面会有点模糊和卡顿。并不是是本生视频数据采集和推流造成的。 二、分析金山云SDK的功能上面也看到效果,下面就来分析他的实现原理,因为他只有demo,和几个jar包,而文档写的也是很简单: 那么本文就采用反编译的方法来解读他的jar文件,他有三个jar包: 本文只看直播推流工具包ksylive3.0.jar,这里还可以看到了,他有一些so文件,有两个文件比较起眼,一个是libksystreamer.so他是底层的推流核心功能,我们使用IDA打开查看其native方法: 看到了,这里和Java层的FFmpegWrapper类交互,推流编码等核心功能就在这里实现了,而这里会看到他底层使用了现阶段开源的视频编解码工具类FFmpeg,以及添加水印等视频二次处理效果。 还有一个就是libksyyuv.so,这个主要是用于YUV数据格式转化,在之前的一篇文章中介绍了,获取到视频的每一帧数据之后是需要做格式转化的,但是这些转化工作放在Java层效率是个问题,一般都会放到native层做处理的。 三、金山云SDK的核心类在讲解他的原理实现之前,先来看一下SDK的简单用法吧,用法很简单, 1、KSYStreamer类,进行编码推流。 2、KSYBgmPlayer类,是播放背景音乐流,可以在推流的过程中给视频添加背景音乐。 3、KSYMediaPlayer类,播放流类,这个类主要适用于画中画效果的。 4、KSYStreamerConfig.Builder类,设置推流的配置信息类。 第一个:KSYStreamerConfig.Builder类 KSYStreamerConfig.Builder builder = new KSYStreamerConfig.Builder(); 1、setmUrl方法:设置推流地址 2、setFrameRate方法:设置推流帧率 3、setMaxAverageVideoBitrate方法:设置最高码率,即目标码率 4、setMinAverageVideoBitrate方法:设置最低码率 5、setInitAverageVideoBitrate方法:设置初始码率 6、setAudioBitrate方法:设置音频码率 7、setVideoResolution方法:设置推流视频的尺寸:360P/480P/540P/720P 8、setEncodeMethod方法:设置视频编码方式:硬编码/软编码 9、setSampleAudioRateInHz方法:设置音频的频率 10、setDefaultLandscape方法:设置推流视频方向:横屏/竖屏 11、setFrontCameraMirror方法:设置摄像头方向:前置/后置 第二个:KSYStreamer类 KSYStreamer mStreamer = new KSYStreamer(Context context); 1、setDisplayPreview方法:设置预览界面,参数是GLSurfaceView 2、setOnStatusListener方法:设置推流状态回调, 3、setOnLogListener方法:设置日志回调 4、setOnAudioRawDataListener方法:设置音频原始数据回调 5、setOnPreviewFrameListener方法:设置每一帧数据回调 6、enableDebugLog方法:是否开启日志信息 7、setMuteAudio方法:设置静音 8、setEnableEarMirror方法:设置清耳模式 9、setBeautyFilter方法:设置滤镜 10、showWaterMarkLogo方法::设置水印logo图片效果 11、showWaterMarkTime方法:设置时间戳水印效果 12、hideWaterMarkLogo方法:隐藏水印logo图片 13、hideWaterMarkTime方法:隐藏水印时间戳 14、startStream方法:开始推流 15、stopStream方法:停止推流 16、setBgmPlayer方法:设置背景音乐 17、startMixMusic方法:设置背景音乐路径 18、stopMixMusic方法:停止背景音乐 19、getRtmpHostIP方法:获取推流ip地址 20、getUploadedKBytes方法:获取已经推流的字节 21、getEncodedFrames方法:获取编码帧率 22、getCurrentBitrate方法:获取当前的码率 23、setPipPlayer方法:设置画中画视频播放功能 23、setPipLocation方法:设置画中画视频位置 第三、KSYBgmPlayer类 KSYBgmPlayer mKsyBgmPlayer = KSYBgmPlayer.getInstance(); 1、setOnBgmPlayerListener方法:设置推流背景音乐播放回调 2、setVolume方法:设置音乐音量 3、setMute方法:静音 第四、KSYMediaPlayer类 KSYMediaPlayermKsyMediaPlayer = new KSYMediaPlayer.Builder(Context context).build(); 1、setOnCompletionListener方法:设置视频播放回调 2、setOnInfoListener方法:设置视频播放信息回调 3、setOnSeekCompleteListener方法:设置视频播放拖动回调 4、setScreenOnWhilePlaying方法:设置全屏播放 5、setLooping方法:设置循环播放 6、setVolume方法:设置播放音量 7、setPlayerMute方法:设置播放静音 四、分析金山云SDK中的核心方法上面分析完了推流SDK中重要的四个类,下面就来详细分析几个重要的常用功能方法: 1、第一个方法:添加水印效果 看到了,这里添加了水印效果,水印效果有两种:一种是时间戳,一种是一张静态图片 我们在推流的过程中有时候为了推广自己或者其他效果,会在视频中添加水印功能,这个也是在推流的过程中比较重要的功能,下面来通过源码分析他是如何进行添加水印的:showWaterMarkLogo方法和showWaterMarkTime方法: 1》添加时间戳水印效果:showWaterMarkTime 这个方法有五个参数: 第一个参数:水印展示的X坐标 接着看wmiShowTime方法: 这里正好是一个定时器,每隔1s执行一次: 这里看到了,原来是把时间戳文案变成一张图片,在之前的一篇文章中我们添加水印也是这么做的。然后在调用KSYStreamer中的setWaterMarkTime方法: 这个方法看到了,干了两件事,其实想想也是合理的,添加水印需要两个地方都需要添加: 第一个是预览界面GLSurfaceView中,这个是给主播看到的 第二个是视频编码推流中,这个是给用户拉流看到的 再看看w变量: 调用了a方法: 然后调用z方法: 到这里,核心方法就有了,这里开始计算坐标,图片尺寸等信息了,这里看到paramFloat3是按照指定的宽度乘以比例系数,然后在根据高宽比算出图片的高度,这些比例在初始化的使用设置的: 这里有这些比例的,也是我们之前看到KSYStreamerConfig.Builder类的方法setVideoResolution提供的设置360P/480P/540P/720P 计算好比例之后,开始调用e对象的setWmiTime方法了,再来看看e对象: 原来e对象是FFStreamer类型的,而我们在上面分析SDK功能包的时候,知道这个类是和底层ffmepg功能交互的: 而底层,也可以看到是直接使用ffmpeg进行编码,加水印效果的。 到这里就分析完了,SDK中添加时间戳水印的原理,他内部定义一个定时器每隔1s执行任务,然后把当前时间戳文案使用Canvas变成一张图片,接着通过传递进来的参数,这些参数都是比例系数,所以肯定是小于1的,计算坐标,图片大小,最后在调用FFStreamer类的setWmiTime方法,在底层使用ffmpeg进行添加水印效果。 上面分析完了,编码中的水印效果,下面再来看看预览画面中添加水印效果: 看看y类型: 看看这个类: 这个类中没有找到t方法,去父类a中也没有找到,a继承x,在x类中找到了t()方法: 看看y类型: 继承了KSYImageFilter类,这个类是用来做图片滤镜效果的基本类: 看到了,这里计算数据之后,再次调用a方法开始绘制: 果然到这里,开始调用OpenGL绘制图形了,也就是水印效果,以及图片滤镜效果等都是调用OpenGL技术来绘制的,然后在回显到界面中的,所以这里肯定得用GLSurfaceView预览界面了,支持OpenGL功能,还能进行图片二次处理再次回显到界面上,再来看看上面的最开始的a类,继承了Render类了,我们知道这个类是和GLSurfaceView息息相关的,渲染器类: 还记得在第一篇文章中,介绍基础知识大纲的时候,不了解的同学可以点击这里:基础知识大纲概要 有一张图片(图片不清晰,可以下载查看): 这里看到了Render类有几个回调方法: 1》onSurfaceCreated(GL10 gl, EGLConfig config) GLSurfaceView->setRender->onSurfaceCreated回调方法中构造一个SurfaceTexture对象,然后设置到Camera预览中->SurfaceTexture中的回调方法onFrameAvailable来得知一帧的数据准备好了->requestRender通知Render来绘制数据->在Render的回调方法onDrawFrame中调用SurfaceTexture的updateTexImage方法获取一帧数据,然后开始使用GL来进行绘制。 GLSurfaceView和SurfaceView还有一个区别是,onDrawFrame在自己的单独线程中操作绘制的,而SurfaceView如果要绘制的话就在主线程中操作了。 好了到这里,我们分析完了预览中添加水印的效果,结合GLSurfaceView和Render使用OpenGL来进行图片的叠加绘制,以及后面涉及到的滤镜效果都是在这里进行处理的,当然还需要结合GPUImage类发挥效果,这个后悔会单独介绍OpenGL知识,如何处理图片技巧的,本文就不介绍了。 2》添加Logo图片水印效果:showWaterMarkLogo 这个方法的参数为: 第一个参数:图片路径 通过这些参数来看,和上面添加水印只多了一个图片高度比的比例系数,其他和水印效果一样。而且在分析上面添加水印的方法差不多,添加时间戳水印效果也是先把时间戳文案转化成一张图片,那么后续的流程就是添加一张图片水印效果了: 所以这里就在太多的介绍了。 2、第二个方法:摄像头每一帧数据处理这里还有一个摄像头每一帧数据的回调,因为我们从SDK可以看到,摄像头Camera是在内部定义的,所以为了特定需求,需要把这个接口放出来,看看源码: 这里的ksyStreamer是h类型: 这里的w是d类型: 设置回调方法,d类实现了Camera.PreviewCallback接口 然后在回调方法中做处理,首先调用外部回调接口,把数据抛出去,然后在处理原始数据进行推流。 所以,SDK内部定义了一个Camera,自己实现了每一帧获取数据的回调接口,然后在回调方法中把数据抛出去给外面,同时在对每一帧数据进行二次处理,使用FFStreamer进行编码,加水印推流。 五、金山云SDK用法到这里,我们就介绍完了金山云SDK推流的原理,因为底层涉及到的ffmpeg进行视频编码,加水印效果,后续一篇文章在介绍如何操作。下面就来简单看一个完整的例子: 1、构造KSYStreamerConfig.Builder类 2、构造KSYStreamer类 3、构造KSYMediaPlayer类 4、构造KSYBgmPlayer类 5、添加水印 6、处理Camear每一帧数据回调方法 六、技术概要到这里我们就介绍了金山云SDK,如何进行推流操作,本文重要介绍了两种方法: 第一个是添加水印效果 添加水印有两个地方,一个是在推流之后的视频添加水印效果,这个是为了能够让用户拉流看到,起到宣传作用,这个主要是借助底层的ffmepg功能库,进行添加水印效果。还一个就是在预览中添加水印效果,这个是给主播看到的,主要利用GLSurfaceView的OpenGL技术进行添加水印,然后再把画面回显出来即可。 第二个是Camera每一帧数据回调 SDK内部定义了一个Camera摄像头,而这个没有暴露出来,但是有些需求我们想得到摄像头的每一帧数据,那么这时候可以通过KSYStreamer的回调方法来处理每一帧数据。 在用一张图来看看SDK的大体功能结构图: 项目下载地址:http://download.csdn.net/detail/jiangwei0910410003/9590380 七、总结本文介绍完之后,可以进行正常的推流工作了,但是还有一些知识点没有介绍到,比如ffmpeg如何编码加水印效果,这个是后续的一篇文章中详细介绍的。但是到这里我们整个推流的流程就可以走通了,从服务器搭建到摄像头原理解析再到现在的推流实现,至少对于一个推流app来说,大体的原理是了解了。后面还会继续介绍编码推流的相关知识。 更多内容:点击这里 关注微信公众号,最新Android技术实时推送 |
|