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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【Android RTMP】RTMPDump 推流过程 ( 独立线程推流 | 创建推流器 | 初始化操作 | 设置推流地址 | 启用写出 | 连接 RTMP 服务器 | 发送 RTMP 数据包 )

文章目录

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

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

二、 RTMPDump 推流线程

三、 创建 RTMP 对象

四、 初始化 RTMP 对象

五、 设置 RTMP 推流地址

六、 启用 RTMP 写出功能

七、 连接 RTMP 服务器

八、 连接 RTMP 流

九、 发送 RTMP 数据包

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

十一、 RTMPDump 推流代码





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


Android RTMP 直播推流技术专栏 :



0 . 资源和源码地址 :


资源下载地址 : 资源下载地址 , 服务器搭建 , x264 , faac , RTMPDump , 源码及交叉编译库 , 本专栏 Android 直播推流源码 ;

GitHub 源码地址 : han1202012 / RTMP_Pusher


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


【Android RTMP】RTMP 直播推流服务器搭建 ( Ubuntu 18.04.4 虚拟机 )

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


【Android RTMP】RTMPDumb 源码导入 Android Studio ( 交叉编译 | 配置 CMakeList.txt 构建脚本 )


【Android RTMP】Android Studio 集成 x264 开源库 ( Ubuntu 交叉编译 | Android Studio 导入函数库 )


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


【Android RTMP】RTMP 数据格式 ( FLV 视频格式分析 | 文件头 Header 分析 | 标签 Tag 分析 | 视频标签 Tag 数据分析 )


【Android RTMP】RTMP 数据格式 ( FLV 视频格式分析 | AVC 序列头格式解析 )


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


【Android RTMP】Android Camera 视频数据采集预览 ( 视频采集相关概念 | 摄像头预览参数设置 | 摄像头预览数据回调接口 )


【Android RTMP】Android Camera 视频数据采集预览 ( NV21 图像格式 | I420 图像格式 | NV21 与 I420 格式对比 | NV21 转 I420 算法 )


【Android RTMP】Android Camera 视频数据采集预览 ( 图像传感器方向设置 | Camera 使用流程 | 动态权限申请 )


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


【Android RTMP】x264 编码器初始化及设置 ( 获取 x264 编码参数 | 编码规格 | 码率 | 帧率 | B帧个数 | 关键帧间隔 | 关键帧解码数据 SPS PPS )


【Android RTMP】x264 图像数据编码 ( Camera 图像数据采集 | NV21 图像数据传到 Native 处理 | JNI 传输字节数组 | 局部引用变量处理 | 线程互斥 )


【Android RTMP】x264 图像数据编码 ( NV21 格式中的 YUV 数据排列 | Y 灰度数据拷贝 | U 色彩值数据拷贝 | V 饱和度数据拷贝 | 图像编码操作 )


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


【Android RTMP】RTMPDump 封装 RTMPPacket 数据包 ( 封装 SPS / PPS 数据包 )


【Android RTMP】RTMPDump 封装 RTMPPacket 数据包 ( 关键帧数据格式 | 非关键帧数据格式 | x264 编码后的数据处理 | 封装 H.264 视频数据帧 )


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


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


【Android RTMP】RTMP 直播推流 ( 阿里云服务器购买 | 远程服务器控制 | 搭建 RTMP 服务器 | 服务器配置 | 推流软件配置 | 直播软件配置 | 推流直播效果展示 )


【Android RTMP】RTMP 直播推流阶段总结 ( 服务器端搭建 | Android 手机端编码推流 | 电脑端观看直播 | 服务器状态查看 )


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


【Android RTMP】NV21 图像旋转处理 ( 问题描述 | 图像顺时针旋转 90 度方案 | YUV 图像旋转细节 | 手机屏幕旋转方向 )


【Android RTMP】NV21 图像旋转处理 ( 图像旋转算法 | 后置摄像头顺时针旋转 90 度 | 前置摄像头顺时针旋转 90 度 )


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


【Android RTMP】NV21 图像旋转处理 ( 快速搭建 RTMP 服务器 Shell 脚本 | 创建 RTMP 服务器镜像 | 浏览器观看直播 | 前置 / 后置摄像头图像旋转效果展示 )

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


【Android RTMP】音频数据采集编码 ( 音频数据采集编码 | AAC 高级音频编码 | FAAC 编码器 | Ubuntu 交叉编译 FAAC 编码器 )


【Android RTMP】音频数据采集编码 ( FAAC 头文件与静态库拷贝到 AS | CMakeList.txt 配置 FAAC | AudioRecord 音频采样 PCM 格式 )


11. 解析 AAC 音频格式 :


【Android RTMP】音频数据采集编码 ( AAC 音频格式解析 | FLV 音频数据标签解析 | AAC 音频数据标签头 | 音频解码配置信息 )

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


【Android RTMP】音频数据采集编码 ( FAAC 音频编码参数设置 | FAAC 编码器创建 | 获取编码器参数 | 设置 AAC 编码规格 | 设置编码器输入输出参数 )


【Android RTMP】音频数据采集编码 ( FAAC 编码器编码 AAC 音频解码信息 | 封装 RTMP 音频数据头 | 设置 AAC 音频数据类型 | 封装 RTMP 数据包 )


【Android RTMP】音频数据采集编码 ( FAAC 编码器编码 AAC 音频采样数据 | 封装 RTMP 音频数据头 | 设置 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;
}


目录
相关文章
|
1月前
|
Ubuntu Linux Android开发
termux+anlinux+Rvnc viewer来使安卓手机(平板)变成linux服务器
本文介绍了如何在Android设备上安装Termux和AnLinux,并通过这些工具运行Ubuntu系统和桌面环境。
107 2
termux+anlinux+Rvnc viewer来使安卓手机(平板)变成linux服务器
|
5月前
|
人工智能 网络安全 开发工具
视觉智能开放平台操作报错合集之服务部署在pdd的服务器,调用报错:The SSL connection could not be established,该如何解决
在使用视觉智能开放平台时,可能会遇到各种错误和问题。虽然具体的错误代码和消息会因平台而异,但以下是一些常见错误类型及其可能的原因和解决策略的概述,包括但不限于:1. 认证错误、2. 请求参数错误、3. 资源超限、4. 图像质量问题、5. 服务不可用、6. 模型不支持的场景、7. 网络连接问题,这有助于快速定位和解决问题。
129 0
|
3月前
|
缓存 Java
异步&线程池 线程池的七大参数 初始化线程的4种方式 【上篇】
这篇文章详细介绍了Java中线程的四种初始化方式,包括继承Thread类、实现Runnable接口、实现Callable接口与FutureTask结合使用,以及使用线程池。同时,还深入探讨了线程池的七大参数及其作用,解释了线程池的运行流程,并列举了四种常见的线程池类型。最后,阐述了在开发中使用线程池的原因,如降低资源消耗、提高响应速度和增强线程的可管理性。
异步&线程池 线程池的七大参数 初始化线程的4种方式 【上篇】
|
4月前
|
文字识别 API 开发工具
印刷文字识别操作报错合集之服务器出现了临时故障,该怎么办
在使用印刷文字识别(OCR)服务时,可能会遇到各种错误。例如:1.Java异常、2.配置文件错误、3.服务未开通、4.HTTP错误码、5.权限问题(403 Forbidden)、6.调用拒绝(Refused)、7.智能纠错问题、8.图片质量或格式问题,以下是一些常见错误及其可能的原因和解决方案的合集。
|
5月前
|
弹性计算 网络协议 Serverless
Serverless 应用引擎操作报错合集之使用ecs,反代到函数的内网域名上,提示{"ErrorCode":"DomainNameNotFound",是什么原因
Serverless 应用引擎(SAE)是阿里云提供的Serverless PaaS平台,支持Spring Cloud、Dubbo、HSF等主流微服务框架,简化应用的部署、运维和弹性伸缩。在使用SAE过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
|
4月前
|
安全 固态存储 Linux
服务器linux操作系统重装的完整流程-傻瓜式教学
服务器linux操作系统重装的完整流程-傻瓜式教学
|
5月前
|
文字识别 算法 API
视觉智能开放平台产品使用合集之保存视频图片,存到自己服务器,该如何操作
视觉智能开放平台是指提供一系列基于视觉识别技术的API和服务的平台,这些服务通常包括图像识别、人脸识别、物体检测、文字识别、场景理解等。企业或开发者可以通过调用这些API,快速将视觉智能功能集成到自己的应用或服务中,而无需从零开始研发相关算法和技术。以下是一些常见的视觉智能开放平台产品及其应用场景的概览。
|
6月前
|
流计算
实时计算 Flink版操作报错之程序在idea跑没问题,打包在服务器跑就一直报错,是什么原因
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
6月前
|
SQL 关系型数据库 MySQL
实时计算 Flink版操作报错之遇到MySQL服务器的时区偏移量(比UTC晚18000秒)与配置的亚洲/上海时区不匹配,如何解决
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
5月前
|
前端开发 Java API
Android端通过HttpURLConnection上传文件到服务器
Android端通过HttpURLConnection上传文件到服务器
57 0