【Android RTMP】RTMPDump 推流过程 ( 独立线程推流 | 创建推流器 | 初始化操作 | 设置推流地址 | 启用写出 | 连接 RTMP 服务器 | 发送 RTMP 数据包 )





安卓直播推流专栏博客总结



Android RTMP 直播推流技术专栏 :


0 . 资源和源码地址 :


1. 搭建 RTMP 服务器 : 下面的博客中讲解了如何在 VMWare 虚拟机中搭建 RTMP 直播推流服务器 ;

2. 准备视频编码的 x264 编码器开源库 , 和 RTMP 数据包封装开源库 :

3. 讲解 RTMP 数据包封装格式 :

4. 图像数据采集 : 从 Camera 摄像头中采集 NV21 格式的图像数据 , 并预览该数据 ;

5. NV21 格式的图像数据编码成 H.264 格式的视频数据 :

6. 将 H.264 格式的视频数据封装到 RTMP 数据包中 :

7. 阶段总结 : 阿里云服务器中搭建 RTMP 服务器 , 并使用电脑软件推流和观看直播内容 ;

8. 处理 Camera 图像传感器导致的 NV21 格式图像旋转问题 :

9. 下面这篇博客比较重要 , 里面有一个快速搭建 RTMP 服务器的脚本 , 强烈建议使用 ;

10. 编码 AAC 音频数据的开源库 FAAC 交叉编译与 Android Studio 环境搭建 :

11. 解析 AAC 音频格式 :

12 . 将麦克风采集的 PCM 音频采样编码成 AAC 格式音频 , 并封装到 RTMP 包中 , 推流到客户端 :






Android 直播推流流程 : 手机采集视频 / 音频数据 , 视频数据使用 H.264 编码 , 音频数据使用 AAC 编码 , 最后将音视频数据都打包到 RTMP 数据包中 , 使用 RTMP 协议上传到 RTMP 服务器中 ;


Android 端中主要完成手机端采集视频数据操作 , 并将视频数据传递给 JNI , 在 NDK 中使用 x264 将图像转为 H.264 格式的视频 , 最后将 H.264 格式的视频打包到 RTMP 数据包中 , 上传到 RTMP 服务器中 ;


本篇博客中将介绍 , 使用 RTMPDump 开源库 , 将编码好的 RTMP 数据包 , 推送到远程 RTMP 服务器 ; 即 RTMPDump 推流过程 ;





一、 Java 层传入的 RTMP 推流地址处理



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




二、 RTMPDump 推流线程



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 对象



创建 RTMP 对象 , 如果创建失败 , 直接停止整个推流方法 ;

// 1. 创建 RTMP 对象, 申请内存
rtmp = RTMP_Alloc();
if (!rtmp) {
    __android_log_print(ANDROID_LOG_INFO, "RTMP", "申请 RTMP 内存失败");
    break;
}




四、 初始化 RTMP 对象



初始化 RTMP 对象 , 并设置超时时间 ;

// 2. 初始化 RTMP
RTMP_Init(rtmp);
// 设置超时时间 5 秒
rtmp->Link.timeout = 5;




五、 设置 RTMP 推流地址



设置 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 写出功能



启用 RTMP 写出功能 ;

// 4. 启用 RTMP 写出功能
RTMP_EnableWrite(rtmp);




七、 连接 RTMP 服务器



连接 RTMP 服务器 , 如果连接失败 , 直接退出该方法 ;

// 5. 连接 RTMP 服务器
ret = RTMP_Connect(rtmp, 0);
if (!ret) {
    __android_log_print(ANDROID_LOG_INFO, "RTMP", "连接 RTMP 服务器 %s 失败", pushPath);
    break;
}




八、 连接 RTMP 流



连接 RTMP 流 , 如果连接失败 , 退出方法 ;

// 6. 连接 RTMP 流
ret = RTMP_ConnectStream(rtmp, 0);
if (!ret) {
    __android_log_print(ANDROID_LOG_INFO, "RTMP", "连接 RTMP 流 %s 失败", pushPath);
    break;
}




九、 发送 RTMP 数据包



将 RTMP 数据包发送到服务器中 ;

// 7. 将 RTMP 数据包发送到服务器中
ret = RTMP_SendPacket(rtmp, packet, 1);




十、 断开 RTMP 连接并释放资源



推流结束后 , 关闭与 RTMP 服务器连接 , 释放资源 ;

// 8. 推流结束, 关闭与 RTMP 服务器连接, 释放资源
if(rtmp){
    RTMP_Close(rtmp);
    RTMP_Free(rtmp);
}




十一、 RTMPDump 推流代码



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;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android平台上实现RTMP推流可以通过使用第三方的库或者自己编写相关代码来实现。下面是一个简单的步骤来实现Android平台上的RTMP推流: 1. 导入第三方库:首先,需要将第三方库添加到Android项目中。目前较为常用的第三方库有librtmp、ffmpeg等。 2. 初始化推流参数:在开始推流之前,需要初始化相关的推流参数,例如RTMP服务器地址推流地址等。可以通过设置参数为其赋值,确保推流的正确性。 3. 创建推流线程:为了避免在主线程中执行推流操作导致界面卡顿,可以在新的线程中执行推流操作。可以通过创建一个推流线程来实现。 4. 连接RTMP服务器:使用已经设置好的RTMP服务器地址,建立与服务器连接连接成功后即可开始推流。 5. 采集视频、音频:通过Android平台提供的相应API,可以采集相机的视频数据和麦克风的音频数据。可以使用Camera和MediaRecorder类来进行视频的采集和编码,使用AudioRecord类来进行音频的采集和编码。 6. 推流:将采集到的视频、音频数据进行编码后,使用RTMP协议将数据发送服务器。可以使用librtmp库提供的接口或者使用第三方库提供的特定接口来实现推流操作。 7. 结束推流:当推流完成或者需要停止推流时,需要释放相关资源并断开与RTMP服务器连接。 需要注意的是,实现RTMP推流过程中需要根据具体需求来设置相应的配置并处理异常情况。同时,还需要对Android相机、音频等操作有一定的了解,并进行适当的错误处理和资源管理。 以上是一种简单的实现RTMP推流的方式,具体实现可能涉及的内容较多,还需根据具体的项目需求进行相应的调整和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值