技术背景
好多开发者都知道我们有Windows、Android、Linux平台的RTSP转RTMP推送模块,实际上,iOS平台我们也有,并在2016年就已发布。我们都知道,一个好的RTSP转RTMP推送模块,需要足够稳定的前提下,还要低延迟、灵活、有状态反馈机制、资源占用低,方便进行多路转发。
技术设计

1. 拉流:通过RTSP直播播放SDK的数据回调接口,拿到音视频数据;
2. 转推:通过RTMP直播推送SDK的编码后数据输入接口,把回调上来的数据,传给RTMP直播推送模块,实现RTSP数据流到RTMP服务器的转发;
3. 录像:如果需要录像,借助RTSP直播播放SDK,拉到音视频数据后,直接存储MP4文件即可;
4. 快照:如果需要实时快照,拉流后,解码调用播放端快照接口,生成快照,因为快照涉及到video数据解码,如无必要,可不必开启,不然会额外消耗性能。
5. 拉流预览:如需预览拉流数据,只要调用播放端的播放接口,即可实现拉流数据预览;
6. 数据转AAC后转发:考虑到好多监控设备出来的音频可能是PCMA/PCMU的,如需要更通用的音频格式,可以转AAC后,在通过RTMP推送;
7. 转推RTMP实时静音:只需要在传audio数据的地方,加个判断即可;
8. 拉流速度反馈:通过RTSP播放端的实时码率反馈event,拿到实时带宽占用即可;
9. 整体网络状态反馈:考虑到有些摄像头可能会临时或异常关闭,RTMP服务器亦是,可以通过推拉流的event回调状态,查看那整体网络情况,如此界定:是拉不到流,还是推不到RTMP服务器。
接口实现
再说iOS平台RTSP转RTMP技术实现细节,先说拉流:
调用InitPlayer的时候,我们可以设置audio转AAC在推送:
// ViewController.m
// SmartiOSRelayDemo
//
// Author: daniulive.com
-(bool)InitPlayer
{
NSLog(@"InitPlayer++");
if(is_inited_player_)
{
NSLog(@"InitPlayer: has inited before..");
return true;
}
_smart_player_sdk = [[SmartPlayerSDK alloc] init];
if (_smart_player_sdk ==nil ) {
NSLog(@"SmartPlayerSDK init failed..");
return false;
}
if (playback_url_.length == 0) {
NSLog(@"_streamUrl with nil..");
return false;
}
if (_smart_player_sdk.delegate == nil)
{
_smart_player_sdk.delegate = self;
NSLog(@"SmartPlayerSDK _player.delegate:%@", _smart_player_sdk);
}
NSInteger initRet = [_smart_player_sdk SmartPlayerInitPlayer];
if ( initRet != DANIULIVE_RETURN_OK )
{
NSLog(@"SmartPlayerSDK call SmartPlayerInitPlayer failed, ret=%ld", (long)initRet);
return false;
}
[_smart_player_sdk SmartPlayerSetPlayURL:playback_url_];
//超低延迟模式
is_low_latency_mode_ = YES;
[_smart_player_sdk SmartPlayerSetLowLatencyMode:(NSInteger)is_low_latency_mode_];
buffer_time_ = 0;
if(buffer_time_ >= 0)
{
[_smart_player_sdk SmartPlayerSetBuffer:buffer_time_];
}
is_fast_startup_ = YES;
[_smart_player_sdk SmartPlayerSetFastStartup:(NSInteger)is_fast_startup_];
NSLog(@"[relayDemo]is_fast_startup_:%d, buffer_time_:%ld", is_fast_startup_, (long)buffer_time_);
[_smart_player_sdk SmartPlayerSetRTSPTcpMode:is_rtsp_tcp_mode_];
NSInteger rtsp_timeout = 10; //RTSP超时时间设置
[_smart_player_sdk SmartPlayerSetRTSPTimeout:rtsp_timeout];
NSInteger is_auto_switch_tcp_udp = 1; //RTSP TCP/UDP模式自动切换设置
[_smart_player_sdk SmartPlayerSetRTSPAutoSwitchTcpUdp:is_auto_switch_tcp_udp];
NSInteger image_flag = 1;
[_smart_player_sdk SmartPlayerSaveImageFlag:image_flag];
//如需查看实时流量信息,可打开以下接口
//NSInteger is_report = 1;
//NSInteger report_interval = 1;
//[_player SmartPlayerSetReportDownloadSpeed:is_report report_interval:report_interval];
NSInteger is_rec_trans_code = 1;
[_smart_player_sdk SmartPlayerSetRecorderAudioTranscodeAAC:is_rec_trans_code];
NSInteger is_pull_trans_code = 1;
[_smart_player_sdk SmartPlayerSetPullStreamAudioTranscodeAAC:is_pull_trans_code];
is_inited_player_ = YES;
NSLog(@"InitPlayer--");
return true;
}对应的设置接口设计如下:
NSInteger is_pull_trans_code = 1;
[_smart_player_sdk SmartPlayerSetPullStreamAudioTranscodeAAC:is_pull_trans_code];设置音视频数据callback如下,音视频数据回调设置,需要在调用SmartPlayerStartPullStream()接口之前完成:
-(void)StartStreamDataCallback
{
//_smart_player_sdk.pullStreamVideoDataBlock = nil; //如不需要回调视频数据
if(is_stream_data_callback_started)
{
NSLog(@"StartStreamDataCallback: has inited before..");
return;
}
if(_smart_player_sdk == nil)
{
NSLog(@"StartStreamDataCallback failed, _smart_player_sdk is null..");
return;
}
__weak __typeof(self) weakSelf = self;
_smart_player_sdk.pullStreamVideoDataBlock = ^(int video_codec_id, unsigned char *data, int size, int is_key_frame, unsigned long long timestamp, int width, int height, unsigned char *parameter_info, int parameter_info_size, unsigned long long presentation_timestamp)
{
//NSLog(@"[pullStreamVideoDataBlock]videoCodecID:%d, is_key_frame:%d, size:%d, width:%d, height:%d, ts:%lld",
// video_codec_id, is_key_frame, size, width, height, timestamp);
[weakSelf OnPostVideoEncodedData:video_codec_id data:data size:size is_key_frame:is_key_frame timestamp:timestamp pts:presentation_timestamp];
};
//_smart_player_sdk.pullStreamAudioDataBlock = nil; //如不需要回调音频数据
_smart_player_sdk.pullStreamAudioDataBlock = ^(int audio_codec_id, unsigned char *data, int size, int is_key_frame, unsigned long long timestamp, int sample_rate, int channel, unsigned char *parameter_info, int parameter_info_size, unsigned long long reserve)
{
//NSLog(@"[pullStreamAudioDataBlock]audioCodecID:%x, is_key_frame:%d, size:%d, parameter_info_size:%d",
// audio_codec_id, is_key_frame, size, parameter_info_size);
[weakSelf OnPostAudioEncodedData:audio_codec_id data:data size:size is_key_frame:is_key_frame timestamp:timestamp parameter_info:parameter_info parameter_info_size:parameter_info_size];
};
//设置拉流视频数据回调
bool isEnablePSVideoDataBlock = true;
[_smart_player_sdk SmartPlayerSetPullStreamVideoDataBlock:isEnablePSVideoDataBlock];
//设置拉流音频数据回调
bool isEnablePSAudioDataBlock = true;
[_smart_player_sdk SmartPlayerSetPullStreamAudioDataBlock:isEnablePSAudioDataBlock];
if([_smart_player_sdk SmartPlayerStartPullStream] != DANIULIVE_RETURN_OK)
{
NSLog(@"Call SmartPlayerStartPullStream failed..");
}
is_stream_data_callback_started = YES;
}获取到的RTSP编码后的音视频数据投递到推送模块:
//如需转发video数据
- (void)OnPostVideoEncodedData:(NSInteger)codec_id data:(unsigned char*)data size:(NSInteger)size is_key_frame:(NSInteger)is_key_frame timestamp:(unsigned long long)timestamp pts:(unsigned long long)pts
{
if((is_pulling_ || isRTSPPublisherRunning) && _smart_publisher_sdk != nil )
{
[_smart_publisher_sdk SmartPublisherPostVideoEncodedData:codec_id data:data size:size is_key_frame:is_key_frame timestamp:timestamp pts:pts];
}
}
//如需转发audio数据
- (void)OnPostAudioEncodedData:(NSInteger)codec_id data:(unsigned char*)data size:(NSInteger)size is_key_frame:(NSInteger)is_key_frame timestamp:(unsigned long long)timestamp parameter_info:(unsigned char*)parameter_info parameter_info_size:(NSInteger)parameter_info_size
{
if((is_pulling_ || isRTSPPublisherRunning) && _smart_publisher_sdk != nil )
{
[_smart_publisher_sdk SmartPublisherPostAudioEncodedData:codec_id data:data size:size is_key_frame:is_key_frame timestamp:timestamp parameter_info:parameter_info parameter_info_size:parameter_info_size];
}
}如果转推RTMP,转推的部分实现如下,需要注意的是,转推的时候,audio_opt和video_opt设置2即可:
//推送端接口封装
-(bool)InitPublisher
{
NSLog(@"InitPublisher++");
if(is_inited_publisher_)
{
NSLog(@"InitPublisher: has inited before..");
return true;
}
if(_smart_publisher_sdk != nil)
{
NSLog(@"InitPublisher, publisher() has inited before..");
return true;
}
_smart_publisher_sdk = [[SmartPublisherSDK alloc] init];
if (_smart_publisher_sdk == nil )
{
NSLog(@"_smart_publisher_sdk with nil..");
return false;
}
if(_smart_publisher_sdk.delegate == nil)
{
_smart_publisher_sdk.delegate = self;
}
NSInteger audio_opt = 2;
NSInteger video_opt = 2;
if([_smart_publisher_sdk SmartPublisherInit:audio_opt video_opt:video_opt] != DANIULIVE_RETURN_OK)
{
NSLog(@"Call SmartPublisherInit failed..");
_smart_publisher_sdk = nil;
return false;
}
is_inited_publisher_ = YES;
NSLog(@"InitPublisher--");
return true;
}
-(bool)StartPushRTMP
{
NSLog(@"StartPushRTMP++");
if ( _smart_publisher_sdk == nil )
{
NSLog(@"StartPushRTMP, publisher SDK with nil");
return false;
}
NSInteger errorCode = [_smart_publisher_sdk SmartPublisherStartPublisher:relay_url_];
NSLog(@"rtmp pusher url: %@", relay_url_);
if(errorCode != DANIULIVE_RETURN_OK)
{
NSLog(@"Call SmartPublisherStartPublisher failed..ret:%ld", (long)errorCode);
return false;
}
NSLog(@"StartPushRTMP--");
return true;
}
-(bool)StopPushRTMP
{
NSLog(@"StopPushRTMP++");
if ( _smart_publisher_sdk == nil )
{
NSLog(@"StopPushRTMP, publiher SDK with nil");
return false;
}
[_smart_publisher_sdk SmartPublisherStopPublisher];
NSLog(@"StopPushRTMP--");
return true;
}总结
以上是iOS平台RTSP转RTMP推送模块大概设计思路,如果需要录像,可以调用录像接口,也可以实现实时快照,或者转推轻量级RTSP服务,感兴趣的开发者,可以单独跟我交流。
















