技术背景
好多开发者都知道我们有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服务,感兴趣的开发者,可以单独跟我交流。