1. rtmpdump库,下载源码
2. 将librtmp文件夹下的源码集成到Android
- 下载rtmpdump源码,将librtmp文件夹拷贝到android cpp目录下。
- 删除掉没用的文件,只保留.c .h文件。
- 在librtmp目录下,创建并编写CMakeLists.txt。
-
在外层CMakeLists.txt配置rtmp。
如下图所示:
librtmp目录下的CMakeLists.txt配置
cmake_minimum_required(VERSION 3.10.2)
project("librtmp")
# 只要有一个C++文件,就是属于C++ == CXX CMAKE_CXX_FLAGS
# 不修改源码的情况下,解决报错,添加宏 -D
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNO_CRYPTO")
file(GLOB SRC_FILE *.c)
add_library(rtmp SHARED ${SRC_FILE})
项目CMakeLists.txt配置
cmake_minimum_required(VERSION 3.10.2)
project("rtmppush")
file(GLOB SRC_FILE *.cpp *.h)
add_library(rtmppush SHARED ${SRC_FILE})
## 引入头文件目录
include_directories(librtmp)
## 配置librtmp
add_subdirectory(librtmp)
## rtmp链接到rtmppush总库
target_link_libraries(
rtmppush
rtmp
log)
3.rtmp使用流程
4. rtmp使用ndk代码
#include <jni.h>
#include <cstring>
#include <pthread.h>
#include <rtmp.h>
#include "safe_queue.h"
bool isStart = false;
bool isConnect = false;
char *url = nullptr;
pthread_t pid;
// RTMP包的队列
SafeQueue<RTMPPacket *> packets;
// 连接RTMP
void *start_task(void *args) {
// 连接RTMP服务器 , 要做好多事
RTMP *rtmp = nullptr;
do {
// 1. 申请RTMP内存
rtmp = RTMP_Alloc();
if (!rtmp) {
LOGE("rtmp申请存在失败");
break;
}
// 2. 初始化
RTMP_Init(rtmp);
// 3. 设置地址
int r = RTMP_SetupURL(rtmp, url);
// r 0代表失败
if (!r) {
LOGE("rtmp设置url失败,url:%s", url);
break;
}
// 4. 开启输出模式
RTMP_EnableWrite(rtmp);
// 5. 连接
r = RTMP_Connect(rtmp, nullptr);
if (!r) {
LOGE("rtmp连接失败:%d url=%s", r, url);
break;
}
r = RTMP_ConnectStream(rtmp, 0);
if (!r) {
LOGE("rtmp连接流失败:%d url=%s", r, url);
break;
}
LOGE(" ====== rtmp连接成功 ======");
isConnect = true;
// 这里要回调Java层
if (javaCallback) {
javaCallback->onStart(THREAD_CHILD);
}
// 队列开始工作
packets.setWork(1);
RTMPPacket *packet = nullptr;
while (isConnect) {
packet = nullptr;
// 去取队列里面的包,如果没有拿到会阻塞
packets.pop(packet);
if (!isConnect) {
break;
}
if (!packet) {
continue;
}
// 给RTMP推流的ID
packet->m_nInfoField2 = rtmp->m_stream_id;
// 成功取出数据包,发送
r = 0;
if (rtmp) {
r = RTMP_SendPacket(rtmp, packet, 1); // 1==true 开启内部缓冲
}
// packet 你都发给服务器了,可以大胆释放
releasePacket(&packet);
packet = nullptr;
if (!r) { // ret == 0 和 ffmpeg不同,0代表失败
LOGE("rtmp 发送包失败 自动断开服务器");
break;
}
}
// 如果跳出上面的循环,就释放包
if (packet) {
releasePacket(&packet);
}
} while (false);
LOGE(" ====== rtmp连接退出 ======");
isStart = false;
isConnect = false;
startTime = 0;
if (rtmp) {
RTMP_Close(rtmp);
RTMP_Free(rtmp);
rtmp = nullptr;
}
if (url) {
delete url;
url = nullptr;
}
packets.setWork(0);
packets.clear();
return 0;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_boardour_toupin_push_RtmpPush_nStart(JNIEnv *env, jobject thiz, jstring url_) {
if (isStart) {
return;
}
isStart = true;
const char *path = env->GetStringUTFChars(url_, 0);
url = new char[strlen(path) + 1];
strcpy(url, path);
pthread_create(&pid, 0, start_task, 0);
env->ReleaseStringUTFChars(url_, path);
}
5. SafeQueue(阻塞队列,生产消费者模式) ndk代码
#ifndef DERRY_SAFE_QUEUE_H
#define DERRY_SAFE_QUEUE_H
#include <queue>
#include <pthread.h>
using namespace std;
template<typename T>
class SafeQueue {
typedef void (*ReleaseCallback)(T *);
typedef void (*SyncHandle)(queue<T> &);
private:
queue<T> q;
pthread_mutex_t mutex;
pthread_cond_t cond;
int work = 0; // 标记队列是否工作
ReleaseCallback releaseCallback;
SyncHandle syncHandle;
int maxSize = 0x0fffffff;
public:
SafeQueue() {
pthread_mutex_init(&mutex, 0); // 动态初始化互斥锁
pthread_cond_init(&cond, 0);
}
~SafeQueue() {
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
}
/**
* 入队
* @param value
*/
void push(T value) {
pthread_mutex_lock(&mutex); // 先锁起来
if (work) {
// 工作状态需要push
if (size() < maxSize) {
q.push(value);
}
// 广播通知
pthread_cond_signal(&cond);
} else {
// 非工作状态
if (releaseCallback) {
releaseCallback(&value); // T无法释放, 让外界释放
}
}
pthread_mutex_unlock(&mutex); // 解锁
}
/**
* 出队
* @param value
* @return
*/
int pop(T &value) {
int ret = 0;
pthread_mutex_lock(&mutex); // 先锁起来
while (work && q.empty()) {
// 工作状态,说明确实需要pop,但是队列为空,需要等待
pthread_cond_wait(&cond, &mutex);
}
if (!q.empty()) {
value = q.front();
//弹出
q.pop();
ret = 1;
}
pthread_mutex_unlock(&mutex); // 解锁
return ret;
}
/**
* 设置队列的工作状态
* @param work
*/
void setWork(int work) {
pthread_mutex_lock(&mutex); // 先锁起来
this->work = work;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex); // 解锁
}
/**
* 设置队列缓存最大数量
* @param maxSize
*/
void setMaxSize(int maxSize) {
this->maxSize = maxSize;
}
/**
* 判断队列是否为空
* @return
*/
int empty() {
return q.empty();
}
/**
* 获取队列大小
* @return
*/
int size() {
return q.size();
}
/**
* 清空队列 队列中的元素如何释放? 让外界释放
*/
void clear() {
pthread_mutex_lock(&mutex); // 先锁起来
unsigned int size = q.size();
for (int i = 0; i < size; ++i) {
//取出队首元素
T value = q.front();
if (releaseCallback) {
releaseCallback(&value);
}
q.pop();
}
pthread_mutex_unlock(&mutex); // 解锁
}
void setReleaseCallback(ReleaseCallback releaseCallback) {
this->releaseCallback = releaseCallback;
}
void setSyncHandle(SyncHandle syncHandle) {
this->syncHandle = syncHandle;
}
/**
* 同步操作
*/
void sync() {
pthread_mutex_lock(&mutex); // 先锁起来
syncHandle(q);
pthread_mutex_unlock(&mutex); // 再解锁
}
};
#endif //DERRY_SAFE_QUEUE_H
RTMP链接服务器成功后,就会开启一个死循环,阻塞等待音视频包加入队列,如果有音视频包加入队列就会拿出来发送到RTMP服务器。