Android平台下RTMPDump的使用

简介

RTMPDump是一个用来处理RTMP流媒体的工具包,是一个C++的开源工程。而我们需要将Android平台下直接使用RTMPDump来进行RTMP推流,这里就涉及两个方便内容:第一,需要使用NDK对RTMPDump进行交叉编译。第二,如何在Android平台下使用RTMPDump。今天这篇文章主要是教会大家如何将RTMPDump移植到Android平台,让大家可以把代码跑起来看到直观的效果,至于具体RTMPDump的使用后面再详细介绍,当然网上也有很多教程,但第一步一般最容易把大家卡住,我就先和大家把第一步走好。

Linux使用ndk编译RTMPDump

网上对这一步有很多介绍,但我都没编出来,折磨了好久。但只要花时间,还是可以搞出来的,这里我详细介绍下,当然最后也会提供完成可用的源码。

编译环境

  • CentOS Linux release 7.4.1708 (Core)
  • 下载配置android sdk manager,下载ndk配置环境变量,参考Linux下Android构建环境
  • gcc 、openssh等如果发现缺少依赖软件,直接用yum安装即可。

下载配置RTMPDump

RTMPDump直接到官网下载

1.png

我们直接下载最新的版本2.3

2.png

源码在librtmp文件夹下。然后我们新建一个文件夹rtmp后面用来进行编译:
3.png

新建一个jni目录,目录结构如上。然后将下载RTMPDump的头文件(.h)拷贝到include中,将.c文件拷贝到src中。如下图:
4.png

然后需要在jni目录下新建Android.mk和Application.mk文件
Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := librtmp
LOCAL_SRC_FILES := \
    src/amf.c \
    src/hashswf.c \
    src/log.c \
    src/parseurl.c \
    src/rtmp.c \

LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
LOCAL_CFLAGS := -Wall -O2 -DSYS=posix -DNO_CRYPTO
TARGET_PLATFORM := android-23

### librtmp library ###
### shared library use the first line
### static library use the second line
### !!! only one line can be used !!! ###

include $(BUILD_SHARED_LIBRARY)
#include $(BUILD_STATIC_LIBRARY)

Application.mk

APP_ABI := all

到此就配置完成了在rtmp的根目录下执行

[root@MiWiFi-R2D-srv home]# ndk-build

当然你得把ndk配置到环境变量中
最后生成libs目录,这样so包就OK了,基本所有平台都支持

5.png

这里我给出编译项目地址 RtmpDump-Android,大家可以直接clone使用。当然已经编译好的动态库在libs下,大家可以直接使用。

Android平台下使用RTMPDump

前面我们已经把要使用的so编译出来了。接下来就是如何在Android平台下使用,这里还是在这个专题的同步项目中写代码FFmpegSample。本篇文章对应的代码是v1.4,大家一定注意版本。

7.png

设计RTMPDump的代码并不多。我把新增的文件标记出来:
8.png

  • cpp/include下的librtmp就是我们在编译RTMPDump时候用到的头文件,这里直接copy过来即可。
  • rtmp_handle.cpp 是真正业务实现的地方
  • RtmpHandle.java 是提供native的调用入口

接下来讲具体集成流程:

第一步

将RTMPDump头文件copy到cpp/include下,在用动态库的时候头文件当然是必须的

第二步

将前面编译得到的librtmp.so复制到项目的jniLibs/armeabi目录下。注意:这里我测试使用的是arm机器,所以就直接使用armeabi下的so

第三步

修改CmakeList.txt
新增

add_library(rtmp SHARED IMPORTED)
set_target_properties(rtmp
  PROPERTIES IMPORTED_LOCATION
  ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/librtmp.so)

修改链接库,添加rtmp

target_link_libraries( # Specifies the target library.
                       ffmpeg-handle

                       avcodec
                       avdevice
                       avfilter
                       avformat
                       avutil
                       swresample
                       swscale
                       rtmp
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

第四步

新建RtmpHandle.java

public class RtmpHandle {
    public static RtmpHandle mInstance;

    private RtmpHandle() {
    }

    public synchronized static RtmpHandle getInstance() {
        if (mInstance == null) {
            mInstance = new RtmpHandle();
        }
        return mInstance;
    }

    static {
        System.loadLibrary("rtmp");
    }

    public native void pushFile(String path);
}

加载rtmp的库,提供native调用方法

第五步

新建rtmp_handle.cpp,加入jni的方法

extern "C"
JNIEXPORT void JNICALL
Java_com_wangheart_rtmpfile_rtmp_RtmpHandle_pushFile(JNIEnv *env, jobject instance, jstring path_) {
    const char *path = env->GetStringUTFChars(path_, 0);
    logw(path);
    // TODO
    publish_using_packet(path);
    env->ReleaseStringUTFChars(path_, path);
}

publish_using_packet(path);方法调用就是真正的推流逻辑了。

第六步

在MainActivity.java中加一个按钮来触发推流

    public void librmtp(View view) {
        new Thread(){
            @Override
            public void run() {
                super.run();
                final String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "dongfengpo.flv";
                File file = new File(path);
                LogUtils.d(path + "  " + file.exists());
                RtmpHandle.getInstance().pushFile(path);
            }
        }.start();
    }

既然是耗时操作,当然也是得开线程的。这里推送的文件是《东风破》——周杰伦,这个视频也是前面flv格式详解+实例剖析有用到过。

到这里移植基本完成,上一张完成图:


6.png

RTMPDump的使用细节后面文章我们再讨论。感谢大家关注!

//
// Created by eric on 2017/11/24.
//

//
// Created by eric on 2017/11/1.
//
#include <jni.h>
#include <string>
#include<android/log.h>
#include <exception>

//定义日志宏变量
#define logw(content)   __android_log_write(ANDROID_LOG_WARN,"eric",content)
#define loge(content)   __android_log_write(ANDROID_LOG_ERROR,"eric",content)
#define logd(content)   __android_log_write(ANDROID_LOG_DEBUG,"eric",content)

extern "C" {
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "librtmp/rtmp_sys.h"
#include "librtmp/log.h"
#include <unistd.h>
#define HTON16(x)  ((x>>8&0xff)|(x<<8&0xff00))
#define HTON24(x)  ((x>>16&0xff)|(x<<16&0xff0000)|(x&0xff00))
#define HTON32(x)  ((x>>24&0xff)|(x>>8&0xff00)|\
    (x<<8&0xff0000)|(x<<24&0xff000000))
#define HTONTIME(x) ((x>>16&0xff)|(x<<16&0xff0000)|(x&0xff00)|(x&0xff000000))
}

#include <iostream>

using namespace std;


/*read 1 byte*/
int ReadU8(uint32_t *u8, FILE *fp) {
    if (fread(u8, 1, 1, fp) != 1)
        return 0;
    return 1;
}

/*read 2 byte*/
int ReadU16(uint32_t *u16, FILE *fp) {
    if (fread(u16, 2, 1, fp) != 1)
        return 0;
    *u16 = HTON16(*u16);
    return 1;
}

/*read 3 byte*/
int ReadU24(uint32_t *u24, FILE *fp) {
    if (fread(u24, 3, 1, fp) != 1)
        return 0;
    *u24 = HTON24(*u24);
    return 1;
}

/*read 4 byte*/
int ReadU32(uint32_t *u32, FILE *fp) {
    if (fread(u32, 4, 1, fp) != 1)
        return 0;
    *u32 = HTON32(*u32);
    return 1;
}

/*read 1 byte,and loopback 1 byte at once*/
int PeekU8(uint32_t *u8, FILE *fp) {
    if (fread(u8, 1, 1, fp) != 1)
        return 0;
    fseek(fp, -1, SEEK_CUR);
    return 1;
}

/*read 4 byte and convert to time format*/
int ReadTime(uint32_t *utime, FILE *fp) {
    if (fread(utime, 4, 1, fp) != 1)
        return 0;
    *utime = HTONTIME(*utime);
    return 1;
}

int InitSockets() {
/*    WORD version;
    WSADATA wsaData;
    version=MAKEWORD(2,2);
    return (WSAStartup(version, &wsaData) == 0);*/
    return 1;
}

void CleanupSockets() {
//    WSACleanup();
}

//Publish using RTMP_SendPacket()
int publish_using_packet(const char *path) {
    RTMP *rtmp = NULL;
    RTMPPacket *packet = NULL;
    uint32_t start_time = 0;
    uint32_t now_time = 0;
    //the timestamp of the previous frame
    long pre_frame_time = 0;
    long lasttime = 0;
    int bNextIsKey = 1;
    uint32_t preTagsize = 0;

    //packet attributes
    uint32_t type = 0;
    uint32_t datalength = 0;
    uint32_t timestamp = 0;
    uint32_t streamid = 0;

    FILE *fp = NULL;
    fp = fopen(path, "rb");
    if (!fp) {
        RTMP_LogPrintf("Open File Error.\n");
        CleanupSockets();
        return -1;
    }

    /* set log level */
    //RTMP_LogLevel loglvl=RTMP_LOGDEBUG;
    //RTMP_LogSetLevel(loglvl);

    if (!InitSockets()) {
        RTMP_LogPrintf("Init Socket Err\n");
        return -1;
    }

    rtmp = RTMP_Alloc();
    RTMP_Init(rtmp);
    //set connection timeout,default 30s
    rtmp->Link.timeout = 5;
    if (!RTMP_SetupURL(rtmp, "rtmp://192.168.31.127/live")) {
        RTMP_Log(RTMP_LOGERROR, "SetupURL Err\n");
        RTMP_Free(rtmp);
        CleanupSockets();
        return -1;
    }

    //if unable,the AMF command would be 'play' instead of 'publish'
    RTMP_EnableWrite(rtmp);

    if (!RTMP_Connect(rtmp, NULL)) {
        RTMP_Log(RTMP_LOGERROR, "Connect Err\n");
        RTMP_Free(rtmp);
        CleanupSockets();
        return -1;
    }

    if (!RTMP_ConnectStream(rtmp, 0)) {
        RTMP_Log(RTMP_LOGERROR, "ConnectStream Err\n");
        RTMP_Close(rtmp);
        RTMP_Free(rtmp);
        CleanupSockets();
        return -1;
    }

    packet = (RTMPPacket *) malloc(sizeof(RTMPPacket));
    RTMPPacket_Alloc(packet, 1024 * 64);
    RTMPPacket_Reset(packet);

    packet->m_hasAbsTimestamp = 0;
    packet->m_nChannel = 0x04;
    packet->m_nInfoField2 = rtmp->m_stream_id;

    RTMP_LogPrintf("Start to send data ...\n");

    //jump over FLV Header
    fseek(fp, 9, SEEK_SET);
    //jump over previousTagSizen
    fseek(fp, 4, SEEK_CUR);
    start_time = RTMP_GetTime();
    while (1) {
        //not quite the same as FLV spec
        if (!ReadU8(&type, fp))
            break;
        if (!ReadU24(&datalength, fp))
            break;
        if (!ReadTime(&timestamp, fp))
            break;
        if (!ReadU24(&streamid, fp))
            break;

        if (type != 0x08 && type != 0x09) {
            //jump over non_audio and non_video frame,
            //jump over next previousTagSizen at the same time
            fseek(fp, datalength + 4, SEEK_CUR);
            continue;
        }

        if (fread(packet->m_body, 1, datalength, fp) != datalength)
            break;

        packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
        packet->m_nTimeStamp = timestamp;
        packet->m_packetType = type;
        packet->m_nBodySize = datalength;
        pre_frame_time = timestamp;
        long delt = RTMP_GetTime() - start_time;
        printf("%ld,%ld\n", pre_frame_time, (RTMP_GetTime() - start_time));
        __android_log_print(ANDROID_LOG_WARN, "eric",
                            "%ld,%ld", pre_frame_time, (RTMP_GetTime() - start_time));
        if (delt < pre_frame_time) {
            usleep((pre_frame_time - delt)*1000);
        }
        if (!RTMP_IsConnected(rtmp)) {
            RTMP_Log(RTMP_LOGERROR, "rtmp is not connect\n");
            break;
        }
        if (!RTMP_SendPacket(rtmp, packet, 0)) {
            RTMP_Log(RTMP_LOGERROR, "Send Error\n");
            break;
        }

        if (!ReadU32(&preTagsize, fp))
            break;

        if (!PeekU8(&type, fp))
            break;
        if (type == 0x09) {
            if (fseek(fp, 11, SEEK_CUR) != 0)
                break;
            if (!PeekU8(&type, fp)) {
                break;
            }
            if (type == 0x17)
                bNextIsKey = 1;
            else
                bNextIsKey = 0;

            fseek(fp, -11, SEEK_CUR);
        }
    }

    RTMP_LogPrintf("\nSend Data Over\n");

    if (fp)
        fclose(fp);

    if (rtmp != NULL) {
        RTMP_Close(rtmp);
        RTMP_Free(rtmp);
        rtmp = NULL;
    }
    if (packet != NULL) {
        RTMPPacket_Free(packet);
        free(packet);
        packet = NULL;
    }

    CleanupSockets();
    return 0;
}


int main(int argc, char *argv[]) {
    //2 Methods:
//    publish_using_packet();
    //publish_using_write();
    return 0;
}

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

推荐阅读更多精彩内容