簡(jiǎn)介
RTMPDump是一個(gè)用來(lái)處理RTMP流媒體的工具包,是一個(gè)C++的開源工程。而我們需要將Android平臺(tái)下直接使用RTMPDump來(lái)進(jìn)行RTMP推流,這里就涉及兩個(gè)方便內(nèi)容:第一,需要使用NDK對(duì)RTMPDump進(jìn)行交叉編譯。第二,如何在Android平臺(tái)下使用RTMPDump。今天這篇文章主要是教會(huì)大家如何將RTMPDump移植到Android平臺(tái),讓大家可以把代碼跑起來(lái)看到直觀的效果,至于具體RTMPDump的使用后面再詳細(xì)介紹,當(dāng)然網(wǎng)上也有很多教程,但第一步一般最容易把大家卡住,我就先和大家把第一步走好。
Linux使用ndk編譯RTMPDump
網(wǎng)上對(duì)這一步有很多介紹,但我都沒編出來(lái),折磨了好久。但只要花時(shí)間,還是可以搞出來(lái)的,這里我詳細(xì)介紹下,當(dāng)然最后也會(huì)提供完成可用的源碼。
編譯環(huán)境
- CentOS Linux release 7.4.1708 (Core)
- 下載配置android sdk manager,下載ndk配置環(huán)境變量,參考Linux下Android構(gòu)建環(huán)境
- gcc 、openssh等如果發(fā)現(xiàn)缺少依賴軟件,直接用yum安裝即可。
下載配置RTMPDump
RTMPDump直接到官網(wǎng)下載

我們直接下載最新的版本2.3

源碼在librtmp文件夾下。然后我們新建一個(gè)文件夾rtmp后面用來(lái)進(jìn)行編譯:

新建一個(gè)jni目錄,目錄結(jié)構(gòu)如上。然后將下載RTMPDump的頭文件(.h)拷貝到include中,將.c文件拷貝到src中。如下圖:

然后需要在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的根目錄下執(zhí)行
[root@MiWiFi-R2D-srv home]# ndk-build
當(dāng)然你得把ndk配置到環(huán)境變量中
最后生成libs目錄,這樣so包就OK了,基本所有平臺(tái)都支持

這里我給出編譯項(xiàng)目地址 RtmpDump-Android,大家可以直接clone使用。當(dāng)然已經(jīng)編譯好的動(dòng)態(tài)庫(kù)在libs下,大家可以直接使用。
Android平臺(tái)下使用RTMPDump
前面我們已經(jīng)把要使用的so編譯出來(lái)了。接下來(lái)就是如何在Android平臺(tái)下使用,這里還是在這個(gè)專題的同步項(xiàng)目中寫代碼FFmpegSample。本篇文章對(duì)應(yīng)的代碼是v1.4,大家一定注意版本。

設(shè)計(jì)RTMPDump的代碼并不多。我把新增的文件標(biāo)記出來(lái):

- cpp/include下的librtmp就是我們?cè)诰幾gRTMPDump時(shí)候用到的頭文件,這里直接copy過(guò)來(lái)即可。
- rtmp_handle.cpp 是真正業(yè)務(wù)實(shí)現(xiàn)的地方
- RtmpHandle.java 是提供native的調(diào)用入口
接下來(lái)講具體集成流程:
第一步
將RTMPDump頭文件copy到cpp/include下,在用動(dòng)態(tài)庫(kù)的時(shí)候頭文件當(dāng)然是必須的
第二步
將前面編譯得到的librtmp.so復(fù)制到項(xiàng)目的jniLibs/armeabi目錄下。注意:這里我測(cè)試使用的是arm機(jī)器,所以就直接使用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)
修改鏈接庫(kù),添加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的庫(kù),提供native調(diào)用方法
第五步
新建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);方法調(diào)用就是真正的推流邏輯了。
第六步
在MainActivity.java中加一個(gè)按鈕來(lái)觸發(fā)推流
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();
}
既然是耗時(shí)操作,當(dāng)然也是得開線程的。這里推送的文件是《東風(fēng)破》——周杰倫,這個(gè)視頻也是前面flv格式詳解+實(shí)例剖析有用到過(guò)。
到這里移植基本完成,上一張完成圖:

RTMPDump的使用細(xì)節(jié)后面文章我們?cè)儆懻摗8兄x大家關(guān)注!
//
// 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(×tamp, 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);
}