https://www.qcloud.com/document/product/267/4735
腾讯视频云RTMP SDK由两部分构成:推流器 + 播放器,本文将主要介绍推流器的相关信息。
该SDK遵循标准RTMP视频推送协议,可以对接包括腾讯云在内的标准视频直播服务器。与此同时,SDK内部囊括了腾讯音视频团队多年的技术积累,在视频压缩、硬件加速、美颜滤镜、音频降噪、码率控制等方面都做了很多的优化处理。
如果您是一位刚刚接触视频直播的合作伙伴,您只需要几行代码就可以完成对接流程,而如果您是一位资深的移动端软件开发工程师,SDK所提供的丰富的设置接口,亦可让您能够定制出最符合需求的表现。
SDK开发包附带的推流器DEMO界面如下:
腾讯视频云RTMP SDK的使用特别简单,您只需要在您的App里添加如下几行代码就可以完成对接工作了。目前SDK内部的默认参数设置参考直播场景精心校调过的。
为了能够展示推流预览的界面,您需要在您的布局xml文件里加入如下一段代码:
<com.tencent.rtmp.ui.TXCloudVideoView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:visibility="gone"/>
先创建一个Pusher对象,它是所有SDK调用接口的承载者。
TXLivePusher mLivePusher = new TXLivePusher(getActivity());
用下面这段代码就可以完成推流了:
String rtmpUrl = "rtmp://2157.livepush.myqcloud.com/live/xxxxxx";
mLivePusher.startPusher(rtmpUrl);
TXCloudVideoView mCaptureView = (TXCloudVideoView) view.findViewById(R.id.video_view);
mLivePusher.startCameraPreview(mCaptureView);
其中 startPusher 使用来告诉SDK视频流要推到哪个服务器地址去,而 startCameraPreview 则是将界面元素和Pusher对象关联起来,从而能够将手机摄像头采集到的画面渲染到屏幕上。
【小细节】
传进来的self.view将会作为画面渲染view的父view,建议此父view专门作为渲染使用,如果您想要在摄像头画面之上加弹幕、献花之类的UI控件,请另行创建一个与self.view平级的view,并将该view叠加在self.view之上。
如果您是定位美女秀场,美颜是必不可少的一个功能点,本SDK提供了一种简单版实现,包含磨皮(level 1 -> level 10)和美白 (level 1 -> level 3)两个功能。您可以在您的APP得用户操作界面上使用滑竿等控件来让用户选择美颜效果,或者推荐您也可以先用Demo里的滑竿进行,达到您满意的效果后,将此时的数值固定到程序的设置参数里。
接口函数setBeautyFilterDepth可以动态调整美颜及美白级别(不支持Neon指令优化的极个别Android手机无法开启):
if (!mLivePusher.setBeautyFilter(mBeautyLevel, mWhiteningLevel)) {
Toast.makeText(getActivity().getApplicationContext(), "当前机型的性能无法支持美颜功能",
Toast.LENGTH_SHORT).show();
}
// 默认是前置摄像头
mLivePusher.switchCamera();
//mFlashTurnOn为true表示打开,否则表示关闭
if (!mLivePusher.turnOnFlashLight(mFlashTurnOn)) {
Toast.makeText(getActivity().getApplicationContext(),
"打开闪光灯失败:绝大部分手机不支持前置闪光灯!", Toast.LENGTH_SHORT).show();
}
mLivePushConfig.setTouchFocus(mTouchFocus);
这里要特别说明一下,因为腾讯云支持两种方式设置水印:一种是在推流SDK进行设置,原理是在SDK内部进行视频编码前就给画面打上水印。另一种方式是在云端打水印,也就是云端对视频进行解析并添加水印Logo。
这里我们特别建议您使用SDK添加水印
,因为在云端打水印有三个明显的问题:
(1)这是一种很耗机器的服务,会拉高您的费用成本;
(2)在云端打水印对于推流期间切换分辨率等情况的兼容并不理想,会有很多花屏的问题发生。
(3)在云端打水印会引入额外的3s以上的视频延迟,这是转码服务所引入的。
SDK所要求的水印图片格式为png,因为png这种图片格式有透明度信息,因而能够更好地处理锯齿等问题。(您可千万别把jpg图片在windows下改个后缀名就塞进去了,专业的png图标都是需要由专业的美工设计师处理的)
//设置视频水印
mLivePushConfig.setWatermark(BitmapFactory.decodeResource(getResources(),R.drawable.watermark), 10, 10);
mLivePusher.setConfig(mLivePushConfig);
通过PushConfig里的setHardwareAcceleration接口可以开启硬件编码。
if (!HWSupportList.isHWVideoEncodeSupport()){
Toast.makeText(getActivity().getApplicationContext(),
"当前手机型号未加入白名单或API级别过低(最低16),请慎重开启硬件编码!",
Toast.LENGTH_SHORT).show();
}
mLivePushConfig.setHardwareAcceleration(mHWVideoEncode);
mLivePusher.setConfig(mLivePushConfig);
是否硬件编码?
如果您的产品定位美女秀场,主用360*640这种分辨率,硬件编码并不比软编码好,因为Android的硬编码不确定性比IOS要高很多,所以建议您不要开。
如果您的产品定位高清场景,540p或者720p高清推流,那就建议尽量开启,因为Android手机的CPU降频策略,以及核心多但每个核心晶体管都很少的现状,注定了软编码处理540p都很吃力,所以硬件编码才能把帧率撑到20帧以上。白名单策略
目前我们在Demo的HWSupportList.java文件里有一个白名单列表,这里是我们自己团队测试过的,可以放心开启硬件加速的Android机型,后续时间里我们会持续增加这个列表的机型数量。目前RTMP SDK测试团队已经测试过的机型以及通过情况见 机型列表,供您参考。
刚才讲的是最基本的使用方法,能满足绝大部分需求。
如果您是一位资深的软件开发工程师,可能还有更专业的要求,比如您可能会关心SDK的运行状态,或者会尝试做一些视频参数的定制等等,接下来我们看一下进阶使用:
首先,您需要了解一下视频云RTMP SDK的内部原理,在推流模式下,SDK内部的状态机制如下:
简单描述就是在您调用startPusher之后,RTMP SDK就会尝试连接网络,并且启动摄像头和麦克风的音视频采集,如果一切顺利,就会进入推流主循环,之后如果一切正常,SDK内部会按照每秒一次的频率通知当前的内部状态(net status),如果中途出现什么问题,则会以 event、 warning 或者 error 的形式通知出来。
想要获得RTMP SDK的状态通知,您可以提供一个Listener给刚才提到的Pusher对象,之后SDK的所有信息都会通过这个Listener反馈给您的App.
public class MyTestActivity implements ITXLivePushListener{
@Override
public void onPushEvent(int event, Bundle param) {
// your code
}
public void onNetStatus(Bundle status) {
// your code
}
}
mLivePusher.setPushListener(this);
事件ID | 数值 | 含义说明 |
---|---|---|
PUSH_EVT_CONNECT_SUCC | 1001 | 已经成功连接到腾讯云推流服务器 |
PUSH_EVT_PUSH_BEGIN | 1002 | 与服务器握手完毕,一切正常,准备开始推流 |
PUSH_EVT_OPEN_CAMERA_SUCC | 1003 | 推流器已成功打开摄像头(Android部分手机这个过程需要1-2秒) |
事件ID | 数值 | 含义说明 |
---|---|---|
PUSH_WARNING_NET_BUSY | 1101 | 网络状况不佳:上行带宽太小,上传数据受阻 |
PUSH_WARNING_RECONNECT | 1102 | 网络断连, 已启动自动重连 (自动重连连续失败超过三次会放弃) |
PUSH_WARNING_HW_ACCELERATION_FAIL | 1103 | 硬编码启动失败,采用软编码 |
PUSH_WARNING_DNS_FAIL | 3001 | RTMP -DNS解析失败(会触发重试流程) |
PUSH_WARNING_SEVER_CONN_FAIL | 3002 | RTMP服务器连接失败(会触发重试流程) |
PUSH_WARNING_SHAKE_FAIL | 3003 | RTMP服务器握手失败(会触发重试流程) |
事件ID | 数值 | 含义说明 |
---|---|---|
PUSH_ERR_OPEN_CAMERA_FAIL | -1301 | 打开摄像头失败 |
PUSH_ERR_OPEN_MIC_FAIL | -1302 | 打开麦克风失败 |
PUSH_ERR_VIDEO_ENCODE_FAIL | -1303 | 视频编码失败 |
PUSH_ERR_AUDIO_ENCODE_FAIL | -1304 | 音频编码失败 |
PUSH_ERR_UNSUPPORTED_RESOLUTION | -1305 | 不支持的视频分辨率 |
PUSH_ERR_UNSUPPORTED_SAMPLERATE | -1306 | 不支持的音频采样率 |
PUSH_ERR_NET_DISCONNECT | -1307 | 网络断连,且经三次抢救无效,可以放弃治疗,更多重试请自行重启推流 |
事件定义请参阅头文件“TXLiveConstants.java”
onNetStatus 通知每秒都会被触发一次,目的是实时反馈当前的推流器状态:
评估参数 | 含义说明 |
---|---|
NET_STATUS_VIDEO_BITRATE | 当前视频编码器输出的比特率,也就是编码器每秒生产了多少视频数据,单位 kbps |
NET_STATUS_AUDIO_BITRATE | 当前音频编码器输出的比特率,也就是编码器每秒生产了多少音频数据,单位 kbps |
NET_STATUS_VIDEO_FPS | 当前视频帧率,也就是视频编码器每条生产了多少帧画面 |
NET_STATUS_NET_SPEED | 当前的发送速度 |
NET_STATUS_NET_JITTER | 网络抖动情况,抖动越大,网络越不稳定 |
NET_STATUS_CACHE_SIZE | 缓冲区大小,缓冲区越大,说明当前上行带宽不足以消费掉已经生产的视频数据 |
如果您希望定制视频编码参数,音频编码参数等等,您可以通过设置Config对象实现您的自定义需求,目前我们支持的setting接口如下:
参数名 | 含义 | 默认值 |
---|---|---|
setAudioSampleRate | 音频采样率:录音设备在一秒钟内对声音信号的采集次数 | 44100 |
setANS | 噪声抑制:开启后可以滤掉背景杂音(32000以下采样率有效) | 关闭 |
setVideoFPS | 视频帧率:即视频编码器每秒生产出多少帧画面,注意由于大部分安卓机器摄像头不支持30FPS以上的采集,推荐您设置FPS为20 | 20 |
setVideoResolution | 视频分辨率:目前提供三种16:9分辨率可供您选择 | 640 * 360 |
setVideoBitrate | 视频比特率:即视频编码器每秒生产出多少数据,单位 kbps | 800 |
setVideoEncodeGop | 关键帧间隔(单位:秒)即多少秒出一个I帧 | 5s |
setAutoAdjustBitrate | 带宽自适应:该功能会根据当前网络情况,自动调整视频比特率,避免视频数据超出发送能力而导致画面卡顿 | 开 |
setMaxVideoBitrate | 最大输出码率:只有开启自适应码率, 该设置项才能启作用 | 1200 |
setMinVideoBitrate | 最小输出码率:只有开启自适应码率, 该设置项才能启作用 | 800 |
setWatermark | 设置水印图片以及其相对屏幕左上角位置 | 腾讯云Logo(demo) |
setHomeOrientation | 设置视频图像旋转角度,比如是否要横屏推流 | home在右边(0)home在下面(1)home在左面(2)home在上面(3) |
这些参数的设置推荐您在启动推流之前就指定,因为大部分设置项是在重新推流之后才能生效的。参考代码如下:
// 成员变量中声明 config 和 pusher
private TXLivePushConfig mLivePushConfig = new TXLivePushConfig();
private TXLivePusher mLivePusher = new TXLivePusher(getActivity());
// 修改参数设置
mLivePushConfig.setVideoResolution(TXLiveConstants.VIDEO_RESOLUTION_640_360);
mLivePushConfig.setAutoAdjustBitrate(false);
mLivePushConfig.setVideoBitrate(800);
//设置视频水印
mLivePushConfig.setWatermark(BitmapFactory.decodeResource(
getResources(),R.drawable.watermark), 10, 10);
mLivePusher.setConfig(mLivePushConfig);
有些研发能力比较强的客户,会有自定义图像处理的需求(比如自定义图像滤镜),同时又希望复用rtmp sdk的整体流程,如果是这样,您可以按照如下攻略进行定制。
Step1. 实现一个图像处理的so
您需要自己实现一个so,比如test.so,然后按照如下定义导出一个C风格的函数,之所以强制使用C而不是java是因为图像处理的效率C和C++比较容易胜任。您实现的PVideoProcessHookFunc 处理时间不能过长,试想,如果该函数的处理时间超过50ms,那就意味着SDK推出的视频流,其帧率不可能达到20FPS。
/* @brief 客户自定义的视频预处理函数原型
* @param yuv_buffer:视频YUV数据,格式固定是YUV420 Planar
* @param len_buffer:数据长度
* @param width: 视频width
* @param height: 视频height
* @return
* @remark (1)该函数会被SDK同步调用,故您需要同步返回预处理后的数据
* (2)处理后的数据长度必须和处理前保持一致
* (3)您或者直接处理yuv_buffer,或者将处理后的数据memcpy到yuv_buffer所指的内存区域,
* 这块内存的生命期由SDK负责管理(也就是释放)
*/
typedef void (*PVideoProcessHookFunc)(unsigned char * yuv_buffer, int len_buffer, int width, int height);
Step2. 设置 setCustomModeType + setCustomVideoPreProcessLibrary
它们是位于PushConfig中的两个设置项:
setCustomModeType设置项用来声明“您希望自定义滤镜”,setCustomVideoPreProcessLibrary用来指定您自己的so的文件路径以及导出函数的名字。
//libtest.so是step1中您自己用C/C++实现的图像处理函数库
//MyVideoProcessFunc是该so导出的函数的名字,该函数必须符合step1中的PVideoProcessHookFunc定义
int customMode |= TXLiveConstants.CUSTOM_MODE_VIDEO_PREPROCESS;
String path = this.getActivity().getApplicationInfo().dataDir + "/lib";
mLivePushConfig.setCustomModeType(customMode);
mLivePushConfig.setCustomVideoPreProcessLibrary(path +"/libtest.so", "MyVideoProcessFunc");
类似视频数据处理思路,但是具体的函数和参数名称要换成音频相关的,java层示例代码如下:
customMode |= TXLiveConstants.CUSTOM_MODE_AUDIO_PREPROCESS; //可以和VIDEO_PREPROCESS一起设置
String path = this.getActivity().getApplicationInfo().dataDir + "/lib";
mLivePushConfig.setCustomModeType(customMode);
mLivePushConfig.setCustomAudioPreProcessLibrary(path +"/libtest.so", "MyAudioProcessFunc");
其中MyAudioProcessFunc应当遵循如下的函数声明:
/* @brief 客户自定义的音频预处理函数原型
* @param pcm_buffer: 音频PCM数据
* @param len_buffer: 数据长度
* @param sample_rate: 采样频率
* @param channels: 声道数
* @param bit_size: 采样位宽
* @return
* @remark (1)该函数会被SDK同步调用,故您需要同步返回预处理后的数据
* (2)处理后的数据长度必须和处理前保持一致
* (3)您或者直接处理pcm_buffer,或者将处理后的数据memcpy到pcm_buffer所指的内存区域,
* 这块内存的生命期由SDK负责管理(也就是释放)
*/
typedef void (*PAudioProcessHookFunc)(unsigned char * pcm_buffer, int len_buffer,
int sample_rate, int channels, int bit_size);
也有客户只是希望拿SDK用来推流,音视频采集部分由自己的代码控制,SDK用来做音视频编码和推流就可以了。
如果是这样,您可以按如下步骤实现:
Step1. 设置 setCustomModeType 和相关参数
这里需要将CustomMode设置为CUSTOM_MODE_VIDEO_CAPTURE,含义是“不需要SDK采集音视频数据”,同时还需要设置视频分辨率。
// (1)将 CustomMode 设置为:自己采集视频数据,SDK只负责编码发送
int customMode |= TXLiveConstants.CUSTOM_MODE_VIDEO_CAPTURE;
mLivePushConfig.setCustomModeType(customMode);
//
// (2)设置视频编码参数,比如720p,相比如普通模式,VIDEO_CAPTURE模式您有六种分辨率可供选择
// VIDEO_RESOLUTION_TYPE_360_640: pYUVBuff的分辨率必须符合360*640
// VIDEO_RESOLUTION_TYPE_540_960: pYUVBuff的分辨率必须符合540*960
// VIDEO_RESOLUTION_TYPE_720_1280: pYUVBuff的分辨率必须符合720*1280
// VIDEO_RESOLUTION_TYPE_640_360: pYUVBuff的分辨率必须符合640*360
// VIDEO_RESOLUTION_TYPE_960_540: pYUVBuff的分辨率必须符合960*540
// VIDEO_RESOLUTION_TYPE_1280_720: pYUVBuff的分辨率必须符合1280*720
mLivePushConfig.setVideoResolution(TXLiveConstants.VIDEO_RESOLUTION_TYPE_1280_720);
Step2. 使用 sendCustomYUVData 向SDK填充数据
之后的工作就是向SDK塞入您自己准备好的视频数据(YUV420 Planar),剩下的编码和网络发送等工作交给SDK来解决。
//(1)先启动推流,不然您给SDK数据它也不会处理
mLivePusher.startPusher(rtmpUrl.trim());
//
//(2)此处示例代码我们用一个线程来模拟YUV数据发送
new Thread() {
@Override
public void run() {
while (true) {
try {
FileInputStream in = new FileInputStream("/sdcard/test_1280_720.yuv");
int len = 1280 * 720 * 3 / 2; //yuv格式为i420
byte buffer[] = new byte[len];
int count = 0;
while ((count = in.read(buffer)) != -1) {
if (len == count) {
mLivePusher.sendCustomYUVData(buffer);
} else {
break;
}
sleep(50, 0);
}
in.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}.start();
// (1)将 CustomMode 设置为:自己采集音频数据,SDK只负责编码&发送
int customMode |= TXLiveConstants.CUSTOM_MODE_AUDIO_PREPROCESS;
mLivePushConfig.setCustomModeType(customMode);
//
// (2)设置音频编码参数:音频采样率和声道数
mLivePushConfig.setAudioSampleRate(44100);
mLivePushConfig.setAudioChannels(1);
之后,调用sendCustomPCMData向SDK塞入您自己的PCM数据即可。以上信息是否解决您的问题?