由于工作中需要用到rtmp推流,参考了其他的一些博文,自己摸索着使用rtmpdump库,在此记录一下。
实现的功能
- 发送flv文件至流媒体服务器(读文件);
- 发送flv数据至流媒体服务器(编码封装后的一帧一帧的数据)。
准备工作
- RTMPDump直接到官网下载
- 实现MediaCodec进行H264硬编码,然后将编码后的数据封装到flv文件中
实现逻辑
- Android.mk 文件编写
# This is the Android makefile for libyuv for both platform and NDK.
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CPP_EXTENSION := .cc
LOCAL_SRC_FILES := \
librtmp/amf.c \
librtmp/hashswf.c \
librtmp/log.c \
librtmp/parseurl.c \
librtmp/rtmp.c
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_MODULE := librtmp
LOCAL_MODULE_TAGS := optional
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := rtmp-push
LOCAL_SRC_FILES := RtmpHandle.cpp RtmpUtil.cpp
LOCAL_STATIC_LIBRARIES := librtmp
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
- Application.mk文件编写
#对标准C++ stl库的支持
#APP_STL := gnustl_static stlport_static
APP_STL := stlport_static
APP_ABI := armeabi-v7a x86 arm64-v8a
APP_PLATFORM := android-9
APP_CPPFLAGS := -std=c++14 -fexceptions
APP_CFLAGS := -DSTDC_HEADERS -DNO_CRYPTO
- 编写RtmpHandle操作对象
/**
* Desc : RTMPDump调用类
*/
public class RtmpHandle {
private static RtmpHandle mInstance;
private RtmpHandle() {}
public synchronized static RtmpHandle getInstance() {
if (mInstance == null) {
mInstance = new RtmpHandle();
}
return mInstance;
}
static {
System.loadLibrary("rtmp");
System.loadLibrary("rtmp-push");
}
public native long initRtmpHandle(String url);
public native int pushFlvData(long rtmpHander,byte[] buf, int length);
public native int pushFlvFile(String url, String filePath);
public native void releaseRtmpHandle(long rtmpHander);
}
- 推送帧数据
int RtmpUtils::pushFlvData(unsigned char *buf, jint length) {
// TODO
if (length < 15) {
return -1;
}
//packet attributes
uint32_t type = 0;
uint32_t dataLength = 0;
uint32_t timeStamp = 0;
uint32_t streamId = 0;
memcpy(&type, buf, 1);
buf++;
memcpy(&dataLength, buf, 3);
dataLength = HTON24(dataLength);
buf += 3;
memcpy(&timeStamp, buf, 4);
timeStamp = HTONTIME(timeStamp);
buf += 4;
memcpy(&streamId, buf, 3);
streamId = HTON24(streamId);
buf += 3;
if(mCount % 50 == 0) {
mCount = 0;
__android_log_print(ANDROID_LOG_WARN, "WTOE PUSH_RTMP","解析包数据:%u,%u,%u,%u,%d",type, dataLength, timeStamp, streamId, length);
}
mCount++;
if (type != 0x08 && type != 0x09) {
return -2;
}
if (dataLength != (length - 11 - 4)) {
return -3;
}
memcpy(packet->m_body, buf, dataLength+11+4);
packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
packet->m_nTimeStamp = timeStamp;
packet->m_packetType = type;
packet->m_nBodySize = dataLength;
if (!RTMP_IsConnected(rtmp)) {
RTMP_Log(RTMP_LOGERROR, "rtmp is not connect\n");
return -4;
}
if (!RTMP_SendPacket(rtmp, packet, 0)) {
RTMP_Log(RTMP_LOGERROR, "Send Error\n");
return -5;
}
return 0;
}
- 推送文件
/*
* ===========================================================================
* 使用RTMPDump进行Flv文件推流
* ===========================================================================
*/
int RtmpUtils::pushFlvFile(const char *url, const char *path) {
logw("pushFlvFile");
RTMP *rtmp = NULL;
RTMPPacket *packet = NULL;
uint32_t start_time = 0;
//the timestamp of the previous frame
long pre_frame_time = 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");
loge("Open File Error.\n");
return -1;
}
rtmp = RTMP_Alloc();
RTMP_Init(rtmp);
//set connection timeout,default 30s
rtmp->Link.timeout = 5;
if (!RTMP_SetupURL(rtmp, const_cast<char *>(url))) {
RTMP_Log(RTMP_LOGERROR, "SetupURL Error\n");
loge("SetupURL Error.\n");
RTMP_Free(rtmp);
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 Error\n");
loge("Connect Error.\n");
RTMP_Free(rtmp);
return -1;
}
if (!RTMP_ConnectStream(rtmp, 0)) {
RTMP_Log(RTMP_LOGERROR, "ConnectStream Err\n");
loge("ConnectStream Error.\n");
RTMP_Close(rtmp);
RTMP_Free(rtmp);
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, static_cast<long>(RTMP_GetTime() - start_time));
__android_log_print(ANDROID_LOG_WARN, " wtoe JNI ",
"%ld,%ld", pre_frame_time,
static_cast<long>(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");
loge("rtmp is not connect.\n");
break;
}
if (!RTMP_SendPacket(rtmp, packet, 0)) {
RTMP_Log(RTMP_LOGERROR, "Send Error\n");
loge("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");
logd("\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;
}
return 0;
}
具体的代码就不在这里贴出来了,如有需要,可相互学习。
详细代码已上传至GitHub,RtmpDump4Android