iOS平台如何实现RTSP|RTMP播放端录像?
技术背景
我们在做RTSP、RTMP直播播放器的时候,有个比较重要的功能,就是拉流端实时录像,包括设置单个录像文件大小、文件前缀、audio转AAC、只录制视频或只录制音频、开始录像、停止录像事件状态回调等。
我们录像模块,覆盖了RTMP、轻量级RTSP服务、RTSP|RTMP播放端录像,平台覆盖了Windows、Linux、Android、iOS,主要功能设计如下:
- [拉流]支持拉取RTSP流录像;
- [拉流]支持拉取RTMP流录像;
- [推流端录像]支持RTMP|RTSP推送端同步录像;
- [轻量级RTSP服务录像]支持轻量级RTSP服务SDK同步录像;
- [推流端录像实时暂停/恢复]支持推送端录像过程中实时暂停录像、恢复录像;
- [逻辑分离]大牛直播录像SDK不同于普通录像接口,更智能,和推送、播放、转发、内置轻量级RTSP服务SDK功能完全分离,支持随时录像;
- [url切换]在录像过程中,支持切换不同URL,如两个URL配置一致,则可以录制到同一个MP4文件,如不一致,可自动分割到下一个文件;
- [参数设置]支持设置单个录像文件大小、录像路径等,并支持纯音频、纯视频、音视频录制模式;
- [音频转码]支持音频(PCMU/PCMA,Speex等)转AAC后再录像;
- [265支持]支持RTSP/RTMP H.265录制到MP4文件;
- [推送端265录像]推送端SDK支持H265录像;
- [推送端外部编码数据对接录像]支持推送端外部编码后数据(H.264/AAC)对接录像;
- [事件回调]从开始录像,到录像结束均有event callback上来,网络堵塞、音视频同步均做了非常友好的处理。
技术实现
本文以大牛直播SDK的iOS平台拉流端录像为例,大概介绍下相关接口的设计,废话不多说,先上代码:
- (void)RecorderBtn:(UIButton *)button {
NSLog(@"record Stream only++");
button.selected = !button.selected;
if (button.selected)
{
if(is_recording_)
return;
[self InitPlayer];
//设置录像目录
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *recorderDir = [paths objectAtIndex:0];
if([_smart_player_sdk SmartPlayerSetRecorderDirectory:recorderDir] != DANIULIVE_RETURN_OK)
{
NSLog(@"Call SmartPlayerSetRecorderDirectory failed..");
}
//每个录像文件大小
NSInteger size = 200;
if([_smart_player_sdk SmartPlayerSetRecorderFileMaxSize:size] != DANIULIVE_RETURN_OK)
{
NSLog(@"Call SmartPlayerSetRecorderFileMaxSize failed..");
}
[_smart_player_sdk SmartPlayerStartRecorder];
[recButton setTitle:@"停止录像" forState:UIControlStateNormal];
is_recording_ = YES;
}
else
{
[_smart_player_sdk SmartPlayerStopRecorder];
[recButton setTitle:@"开始录像" forState:UIControlStateNormal];
if(!is_playing_)
{
[self UnInitPlayer];
}
is_recording_ = NO;
}
}
对应的InitPlayer实现:
-(bool)InitPlayer
{
NSLog(@"InitPlayer++");
if(is_inited_player_)
{
NSLog(@"InitPlayer: has inited before..");
return true;
}
//NSString* in_cid = @"";
//NSString* in_key = @"";
//[SmartPlayerSDK SmartPlayerSetSDKClientKey:in_cid in_key:in_key reserve1:0 reserve2:nil];
_smart_player_sdk = [[SmartPlayerSDK alloc] init];
if (_smart_player_sdk ==nil ) {
NSLog(@"SmartPlayerSDK init failed..");
return false;
}
if (playback_url_.length == 0) {
NSLog(@"playback url is 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_];
//[self try_set_rtsp_url:playback_url_];
//超低延迟模式设置
[_smart_player_sdk SmartPlayerSetLowLatencyMode:(NSInteger)is_low_latency_mode_];
//buffer time设置
if(buffer_time_ >= 0)
{
[_smart_player_sdk SmartPlayerSetBuffer:buffer_time_];
}
//快速启动模式设置
[_smart_player_sdk SmartPlayerSetFastStartup:(NSInteger)is_fast_startup_];
NSLog(@"[SmartPlayerV2]is_fast_startup_:%d, buffer_time_:%ld", is_fast_startup_, (long)buffer_time_);
//RTSP TCP还是UDP模式
[_smart_player_sdk SmartPlayerSetRTSPTcpMode:is_rtsp_tcp_mode_];
//设置RTSP超时时间
NSInteger rtsp_timeout = 10;
[_smart_player_sdk SmartPlayerSetRTSPTimeout:rtsp_timeout];
//设置RTSP TCP/UDP自动切换
NSInteger is_tcp_udp_auto_switch = 1;
[_smart_player_sdk SmartPlayerSetRTSPAutoSwitchTcpUdp:is_tcp_udp_auto_switch];
//快照设置 如需快照 参数传1
[_smart_player_sdk SmartPlayerSaveImageFlag:save_image_flag_];
//如需查看实时流量信息,可打开以下接口
NSInteger is_report = 1;
NSInteger report_interval = 3;
[_smart_player_sdk SmartPlayerSetReportDownloadSpeed:is_report report_interval:report_interval];
//录像端音频,是否转AAC后保存
NSInteger is_transcode = 1;
[_smart_player_sdk SmartPlayerSetRecorderAudioTranscodeAAC:is_transcode];
//录制MP4文件 是否录制视频
NSInteger is_record_video = 1;
[_smart_player_sdk SmartPlayerSetRecorderVideo:is_record_video];
//录制MP4文件 是否录制音频
NSInteger is_record_audio = 1;
[_smart_player_sdk SmartPlayerSetRecorderAudio:is_record_audio];
is_inited_player_ = YES;
NSLog(@"InitPlayer--");
return true;
}
对应的event callback设计如下:
- (NSInteger) handleSmartPlayerEvent:(NSInteger)nID param1:(unsigned long long)param1 param2:(unsigned long long)param2 param3:(NSString*)param3 param4:(NSString*)param4 pObj:(void *)pObj;
{
NSString* player_event = @"";
NSString* lable = @"";
....
else if (nID == EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE)
{
lable = @"[event]录像写入新文件..文件名:";
player_event = [lable stringByAppendingFormat:@"%@", param3];
}
else if (nID == EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED)
{
lable = @"一个录像文件完成..文件名:";
player_event = [lable stringByAppendingFormat:@"%@", param3];
}
else
NSLog(@"[event]nID:%lx", (long)nID);
....
return 0;
}
相关接口的设计如下:
// SmartPlayerSDK.h
// Created by daniusdk.com on 16/01/03.
/**
* 录像相关:
*
* @param path 录像文件存放目录
*
* @return {0} if successful
*/
- (NSInteger)SmartPlayerSetRecorderDirectory:(NSString*)path;
/**
* 录像相关:
*
* @param size 每个录像文件的大小 (5~500M), 默认200M
*
* @return {0} if successful
*/
- (NSInteger)SmartPlayerSetRecorderFileMaxSize:(NSInteger)size;
/*
* 设置录像时音频转AAC编码的开关
*
* aac比较通用,sdk增加其他音频编码(比如speex, pcmu, pcma等)转aac的功能.
*
* @param is_transcode: 设置为1的话,如果音频编码不是aac,则转成aac, 如果是aac,则不做转换. 设置为0的话,则不做任何转换. 默认是0.
*
* 注意: 转码会增加性能消耗
*/
- (NSInteger)SmartPlayerSetRecorderAudioTranscodeAAC:(NSInteger)is_transcode;
/**
* 设置是否录视频,默认的话,如果视频源有视频就录,没有就不录, 但有些场景下可能不想录制视频,只想录音频,所以增加个开关
*
* @param is_record_video 1 表示录制视频, 0 表示不录制视频, 默认是1
*
* @return {0} if successful
*/
- (NSInteger)SmartPlayerSetRecorderVideo:(NSInteger)is_record_video;
/**
* 设置是否录音频,默认的话,如果视频源有音频就录,没有就不录, 但有些场景下可能不想录制音频,只想录视频,所以增加个开关
*
* @param is_record_audio 1 表示录制音频, 0 表示不录制音频, 默认是1
*
* @return {0} if successful
*/
- (NSInteger)SmartPlayerSetRecorderAudio:(NSInteger)is_record_audio;
/**
* 录像相关:
*
* Start recorder(开始录像)
*
* @return {0} if successful
*/
- (NSInteger)SmartPlayerStartRecorder;
/**
* 录像相关:
*
* Stop recorder(停止录像)
*
* @return {0} if successful
*/
- (NSInteger)SmartPlayerStopRecorder;
总结
RTSP、RTMP播放端录像,我们的设计,是播放和录像分离,可以只录像或只播放,如果同时录像和播放,只需要一个拉流实例来完成。