前言
今天向大家介绍一下如何使用 librtmp 库推拉音视频直播流。虽然Adobe已经宣布不在维护 flash 了,但 flash使用的 rtmp 协议目前仍然是直播系统中最常用的传输协议。
目前实现 rtmp 协议的有很多库,但在众多 rtmp 协议的实现中,librtmp 无疑是最好的一个。
今天我们就向大家介绍一下,如何在 iOS 中使用 librtmp。
交叉编译 librtmp
在开发移动端程序时,我们都会用到交叉编译。那么什么是交叉编译呢?交叉编译就是指在一种 CPU类型/操作系统 的机子上编译出另一种 CPU类型/操作系统 的机子上可以运行的程序就称为交叉编译。
以 iOS 为例,我们在 Mac 环境上(CPU类型为 Intel)编译出 iPhone 手机(CPU类型为 ARM)可执行的程序。这个工作我们几乎每天都在做,只是大家没有观注而已。
同样,我们想在 iOS 程序中使用 librtmp 库,就必须先将 librtmp 编译成 ARM 架构 的可执行的程序(也就是 iPhone 手机可执行的程序),这样才能在 iOS 程序中使用它。
Xcode 为我们提供了交叉编译的工具。可以通过 xcode-select --print-path
可获取交叉编译工具目录,在该目录的 user/bin 下就可以找到交叉编译的工具。最主要的两个工具是 gcc 编译器和 ld 链接器。
下面我们来详细介绍一下 gcc 及 ld 参数的含义:
- isysroot: 指定目标机SDK位置,也就是iPhone手机开发SDK的位置。
- arch: 指定目标机 CPU 架构。
- miphoneos-version-min: 指定可用的操作系统最小版本。
- fembed-bitcode: 苹果自带的参数。从 iOS7.0 开始支持。有兴趣的同学在线上查一上资料吧。
下面是编译 librtmp 的部分关键脚本:
代码语言:javascript复制DEVELOPER=$(xcode-select --print-path)
SDK_VERSION=$(xcrun -sdk iphoneos --show-sdk-version)
echo $SDK_VERSION
SDK_VERSION_MIN=7.0
DEVICE_PLATFORM="${DEVELOPER}/Platforms/iPhoneOS.platform"
SIMULATOR_PLATFORM="${DEVELOPER}/Platforms/iPhoneSimulator.platform"
DEVICE_SDK="${DEVICE_PLATFORM}/Developer/SDKs/iPhoneOS${SDK_VERSION}.sdk"
SIMULATOR_SDK="${SIMULATOR_PLATFORM}/Developer/SDKs/iPhoneSimulator${SDK_VERSION}.sdk"
...
CROSS_COMPILE="${DEVELOPER}/usr/bin/"
XCFLAGS="-g -O2 -isysroot ${SDK} -I${IOS_OPENSSL}/include -arch $ARCH -miphoneos-version-min=${SDK_VERSION_MIN} -fembed-bitcode"
XLDFLAGS="-isysroot ${SDK} -arch $ARCH -L${IOS_OPENSSL}/lib -lssl"
make SYS=darwin
make SYS=darwin prefix="/tmp/librtmp-$ARCH" install &> "/tmp/librtmp-$ARCH.log"
执行该脚本后,它就指定好了做交叉编译的编译器及链接器。并指定好了编译参数和链接参数。最后,Makefile 会使用指定的编译器对代码进行编译,生成可以在iPhone上运行的程序。
使用 librtmp 拉流
使用 librtmp 接流的基本步骤:
- 分配 RTMP 对象,并初始化 RTMP *rtmp=RTMP_Alloc(); RTMP_Init(rtmp);
- 设置 rtmp URL RTMP_SetupURL(rtmp,"rtmp://live.hkstv.hk.lxdns.com/live/hks")
- 设置缓冲区大小 //1hour RTMP_SetBufferMS(rtmp, 3600*1000);
- 连接流媒体服务器 ... RTMP_Connect(rtmp,NULL) ... RTMP_ConnectStream(rtmp,0) ...
- 拉流 while((nRead=RTMP_Read(rtmp,buf,bufsize)) > 0 { ... }
- 关闭连接 RTMP_Close(rtmp); RTMP_Free(rtmp);
librtmp 推流
使用 librtmp 推流与拉流的流程差不多。刚开始时也是分配并初始化 RTMP, 只是在连接流媒体服务器时需要打开写开关。代码如下:
代码语言:javascript复制//if unable,the AMF command would be 'play' instead of 'publish'
RTMP_EnableWrite(rtmp);
if (!RTMP_Connect(rtmp,NULL)){
...
}
if (!RTMP_ConnectStream(rtmp,0)){
...
}
连接到流媒体服务器后,设置并初始化 RTMPPacket,RTMPPacket 里边存放要发送的数据包。代码如下:
代码语言:javascript复制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;
下面开始将 FLV 文件推到流媒体服务器上。首先,跳过 FLV 头。
代码语言:javascript复制//jump over FLV Header
fseek(fp,9,SEEK_SET);
FLV 文件格式是由 FLV Header FLV Body组成的。其中 FLV Header一共 9 个字节。FLV Body 由 Tag Header Tag Body 组成。
然后,在一个 while 循环中不停的分析Tag,从 Tag Header 和 Tag Body中取出数据,构造好 RTMPPacket 后 push 到流媒体服务器。
代码语言:javascript复制...
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;
if (!RTMP_IsConnected(rtmp)){
...
}
if (!RTMP_SendPacket(rtmp,packet,0)){
...
}
...
最后,将 flv 文件全部推送到媒体服务器后,关闭 rtmp 连接。
小结
本文首先介绍了如何在 Mac 上编译出可以在 iOS 上可运行的 librtmp 库,然后介绍了如何从流媒体服务器上拉流,最后介绍了如何使用 librtmp 将 flv 文件推送到流媒体服务器上。