Android RTMP录频直播四(rtmpdump集成)

1. rtmpdump库,下载源码

rtmpdump库github下载地址

librtmp.png

2. 将librtmp文件夹下的源码集成到Android
  1. 下载rtmpdump源码,将librtmp文件夹拷贝到android cpp目录下。
  2. 删除掉没用的文件,只保留.c .h文件。
  3. 在librtmp目录下,创建并编写CMakeLists.txt。
  4. 在外层CMakeLists.txt配置rtmp。
    如下图所示:


    rtmp_cpp.png

    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使用流程
RTMP使用流程.png
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服务器。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容