1 . Java 传递字符串数据到 JNI : 启动推流时 , Java 层会将 RTMP 推流地址传递给 JNI ;
2 . jstring 类型转为 char* 类型 : 将 Java 字符串转为 C 字符串 ;
// 获取 Rtmp 推流地址
// 该 pushPathFromJava 引用是局部引用, 超过作用域就无效了
// 局部引用不能跨方法 , 跨线程调用
const char* pushPathFromJava = env->GetStringUTFChars(path, 0);
3 . 局部引用变量处理 : 该转换后的 const char* pushPathFromJava 字符串是局部引用变量 , 不能跨进程 , 跨作用域使用 , 之后的推流操作在独立的线程中使用 , 因此需要将字符串数据在堆内存中存储 ;
// 获取地址的长度, 加上 '\0' 长度
char * pushPathNative = new char[strlen(pushPathFromJava) + 1];
// 拷贝 pushPathFromJava 到堆内存 pushPathNative 中
// 局部引用不能跨方法 , 跨线程调用, 这里需要在线程中使用该地址
// 因此需要将该局部引用拷贝到堆内存中, 然后传递到对应线程中
strcpy(pushPathNative, pushPathFromJava);
4 . 释放局部引用 : JNI 中的局部引用变量 , 使用完毕后及时释放 ;
// 释放从 Java 层获取的字符串
// 释放局部引用
env->ReleaseStringUTFChars(path, pushPathFromJava);
1 . 独立线程推流 : RTMP 推流操作需要在一个独立的线程中完成 , 涉及到网络的操作都是耗时操作 , 在 Android 中都要在线程中执行 ;
2 . 线程 ID 声明 : 需要导入 #include <pthread.h>
包 , 之后才能使用线程 , 先声明线程 ID , pthread_t 类型 ;
/**
* 开始推流工作线程的线程 ID
*/
pthread_t startRtmpPushPid;
3 . 创建并执行线程 : 创建并执行线程 , 在线程中执行 startRtmpPush 方法 , 传入 pushPathNative 字符串参数 ;
// 创建线程
pthread_create(&startRtmpPushPid, 0, startRtmpPush, pushPathNative);
4 . 线程方法 : 定义线程方法 , 参数和返回值都是 void* 类型 , 在开始位置获取传入的参数 ;
void* startRtmpPush (void* args){
// 0. 获取 Rtmp 推流地址
char* pushPath = static_cast<char *>(args);
// ...
}
创建 RTMP 对象 , 如果创建失败 , 直接停止整个推流方法 ;
// 1. 创建 RTMP 对象, 申请内存
rtmp = RTMP_Alloc();
if (!rtmp) {
__android_log_print(ANDROID_LOG_INFO, "RTMP", "申请 RTMP 内存失败");
break;
}
初始化 RTMP 对象 , 并设置超时时间 ;
// 2. 初始化 RTMP
RTMP_Init(rtmp);
// 设置超时时间 5 秒
rtmp->Link.timeout = 5;
设置 RTMP 推流地址 , 如果设置失败 , 直接退出推流操作 ;
该地址就是 Java 层传给 JNI 的字符串 , 刚获取时是局部引用变量 , 将其拷贝到了堆内存中 , 才可以在推流线程中使用 ;
// 3. 设置 RTMP 推流服务器地址
int ret = RTMP_SetupURL(rtmp, pushPath);
if (!ret) {
__android_log_print(ANDROID_LOG_INFO, "RTMP", "设置 RTMP 推流服务器地址 %s 失败", pushPath);
break;
}
启用 RTMP 写出功能 ;
// 4. 启用 RTMP 写出功能
RTMP_EnableWrite(rtmp);
连接 RTMP 服务器 , 如果连接失败 , 直接退出该方法 ;
// 5. 连接 RTMP 服务器
ret = RTMP_Connect(rtmp, 0);
if (!ret) {
__android_log_print(ANDROID_LOG_INFO, "RTMP", "连接 RTMP 服务器 %s 失败", pushPath);
break;
}
连接 RTMP 流 , 如果连接失败 , 退出方法 ;
// 6. 连接 RTMP 流
ret = RTMP_ConnectStream(rtmp, 0);
if (!ret) {
__android_log_print(ANDROID_LOG_INFO, "RTMP", "连接 RTMP 流 %s 失败", pushPath);
break;
}
将 RTMP 数据包发送到服务器中 ;
// 7. 将 RTMP 数据包发送到服务器中
ret = RTMP_SendPacket(rtmp, packet, 1);
推流结束后 , 关闭与 RTMP 服务器连接 , 释放资源 ;
// 8. 推流结束, 关闭与 RTMP 服务器连接, 释放资源
if(rtmp){
RTMP_Close(rtmp);
RTMP_Free(rtmp);
}
RTMPDump 推流代码 :
/**
* 开始向远程 RTMP 服务器推送数据
*/
extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_rtmp_LivePusher_native_1startRtmpPush(JNIEnv *env, jobject thiz,
jstring path) {
if(isStartRtmpPush){
// 防止该方法多次调用, 如果之前调用过, 那么屏蔽本次调用
return;
}
// 执行过一次后, 马上标记已执行状态, 下一次就不再执行该方法了
isStartRtmpPush = TRUE;
// 获取 Rtmp 推流地址
// 该 pushPathFromJava 引用是局部引用, 超过作用域就无效了
// 局部引用不能跨方法 , 跨线程调用
const char* pushPathFromJava = env->GetStringUTFChars(path, 0);
// 获取地址的长度, 加上 '\0' 长度
char * pushPathNative = new char[strlen(pushPathFromJava) + 1];
// 拷贝 pushPathFromJava 到堆内存 pushPathNative 中
// 局部引用不能跨方法 , 跨线程调用, 这里需要在线程中使用该地址
// 因此需要将该局部引用拷贝到堆内存中, 然后传递到对应线程中
strcpy(pushPathNative, pushPathFromJava);
// 创建线程
pthread_create(&startRtmpPushPid, 0, startRtmpPush, pushPathNative);
// 释放从 Java 层获取的字符串
// 释放局部引用
env->ReleaseStringUTFChars(path, pushPathFromJava);
}
/**
* 开始推流任务线程
* 主要是调用 RTMPDump 进行推流
* @param args
* @return
*/
void* startRtmpPush (void* args){
// 0. 获取 Rtmp 推流地址
char* pushPath = static_cast<char *>(args);
// rtmp 推流器
RTMP* rtmp = 0;
// rtmp 推流数据包
RTMPPacket *packet = 0;
/*
将推流核心执行内容放在 do while 循环中
在出错后, 随时 break 退出循环, 执行后面的释放资源的代码
可以保证, 在最后将资源释放掉, 避免内存泄漏
避免执行失败, 直接 return, 导致资源没有释放
*/
do {
// 1. 创建 RTMP 对象, 申请内存
rtmp = RTMP_Alloc();
if (!rtmp) {
__android_log_print(ANDROID_LOG_INFO, "RTMP", "申请 RTMP 内存失败");
break;
}
// 2. 初始化 RTMP
RTMP_Init(rtmp);
// 设置超时时间 5 秒
rtmp->Link.timeout = 5;
// 3. 设置 RTMP 推流服务器地址
int ret = RTMP_SetupURL(rtmp, pushPath);
if (!ret) {
__android_log_print(ANDROID_LOG_INFO, "RTMP", "设置 RTMP 推流服务器地址 %s 失败", pushPath);
break;
}
// 4. 启用 RTMP 写出功能
RTMP_EnableWrite(rtmp);
// 5. 连接 RTMP 服务器
ret = RTMP_Connect(rtmp, 0);
if (!ret) {
__android_log_print(ANDROID_LOG_INFO, "RTMP", "连接 RTMP 服务器 %s 失败", pushPath);
break;
}
// 6. 连接 RTMP 流
ret = RTMP_ConnectStream(rtmp, 0);
if (!ret) {
__android_log_print(ANDROID_LOG_INFO, "RTMP", "连接 RTMP 流 %s 失败", pushPath);
break;
}
// 准备推流相关的数据, 如线程安全队列
readyForPush = TRUE;
// 记录推流开始时间
pushStartTime = RTMP_GetTime();
// 线程安全队列开始工作
packets.setWork(1);
while (isStartRtmpPush) {
// 从线程安全队列中
// 取出一包已经打包好的 RTMP 数据包
packets.pop(packet);
// 确保当前处于推流状态
if (!isStartRtmpPush) {
break;
}
// 确保不会取出空的 RTMP 数据包
if (!packet) {
continue;
}
// 设置直播的流 ID
packet->m_nInfoField2 = rtmp->m_stream_id;
// 7. 将 RTMP 数据包发送到服务器中
ret = RTMP_SendPacket(rtmp, packet, 1);
// RTMP 数据包使用完毕后, 释放该数据包
if (packet) {
RTMPPacket_Free(packet);
delete packet;
packet = 0;
}
if (!ret) {
__android_log_print(ANDROID_LOG_INFO, "RTMP", "RTMP 数据包推流失败");
break;
}
}
}while (0);
// 面的部分是收尾部分, 释放资源
// 8. 推流结束, 关闭与 RTMP 服务器连接, 释放资源
if(rtmp){
RTMP_Close(rtmp);
RTMP_Free(rtmp);
}
// 推流数据包 线程安全队列释放
// 防止中途退出导致没有释放资源, 造成内存泄漏
if (packet) {
RTMPPacket_Free(packet);
delete packet;
packet = 0;
}
// 释放推流地址
if(pushPath){
delete pushPath;
pushPath = 0;
}
return 0;
}