技术背景
好多开发者在跟我做技术交流的时候,说用大牛直播SDK模块的特点是,想到什么功能,找找头文件和demo几乎都有对应的实现,你们是何收集到这么多技术需求的?
实际上,这还是取决于我们多年的行业口碑和大规模的实实在在的用户积累,才让我们清楚的认识到,一个直播模块,需要有什么,需要舍弃什么。
技术设计
本文以大牛直播SDK的Windows平台RTSP|RTMP直播播放录制功能设计为例,谈谈我们的接口的细粒度设计。
目前,我们录像模块,涵盖了Windows/Linux/android/iOS 推送端(涵盖轻量级RTSP服务模块、RTMP推流模块和GB28181设备接入模块)和RTSP|RTMP播放端,主要实现了如下功能:
- [拉流]支持拉取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上来,网络堵塞、音视频同步均做了非常友好的处理。
设置只录制视频或音频:
/* * 设置是否录视频,默认的话,如果视频源有视频就录,没有就没得录, 但有些场景下可能不想录制视频,只想录音频,所以增加个开关 * * is_record_video: 1 表示录制视频, 0 表示不录制视频, 默认是1 */ [DllImport(@"SmartPlayerSDK.dll")] public static extern UInt32 NT_SP_SetRecorderVideo(IntPtr handle, Int32 is_record_video); /* * 设置是否录音频,默认的话,如果视频源有音频就录,没有就没得录, 但有些场景下可能不想录制音频,只想录视频,所以增加个开关 * * is_record_audio: 1 表示录制音频, 0 表示不录制音频, 默认是1 */ [DllImport(@"SmartPlayerSDK.dll")] public static extern UInt32 NT_SP_SetRecorderAudio(IntPtr handle, Int32 is_record_audio);
录像存储位置设置,设置本地录像目录,需要注意的是,我们已经支持宽字符中文路径设置,开始录像和录像结束,我们会有事件回调上来:
/* * 设置本地录像目录, 支持中文目录, 需要设置宽字符,比如L"D:\\xxx\\gg" */ [DllImport(@"SmartPlayerSDK.dll")] public static extern UInt32 NT_SP_SetRecorderDirectoryW(IntPtr handle, [MarshalAs(UnmanagedType.LPWStr)] String dir);
设置单个录像文件大小,如果一直是录像状态,超过这个大小后,会自动切分录制到下个文件:
/* * 设置单个录像文件最大大小, 当超过这个值的时候,将切割成第二个文件 * size: 单位是KB(1024Byte), 当前范围是 [5MB-800MB], 超出将被设置到范围内 */ [DllImport(@"SmartPlayerSDK.dll")] public static extern UInt32 NT_SP_SetRecorderFileMaxSize(IntPtr handle, UInt32 size);
设置录像名称生成规则:
/* * 设置录像文件名生成规则 */ [DllImport(@"SmartPlayerSDK.dll", EntryPoint = "NT_SP_SetRecorderFileNameRuler", CallingConvention = CallingConvention.StdCall)] public static extern UInt32 NT_SP_SetRecorderFileNameRuler(IntPtr handle, ref NT_SP_RecorderFileNameRuler ruler);
结构体定义如下:
/*如果三项都是0的话,将不能启动录像*/ [StructLayoutAttribute(LayoutKind.Sequential)] public struct NT_SP_RecorderFileNameRuler { public UInt32 type_; // 这个值目前默认是0,将来扩展用 [MarshalAs(UnmanagedType.LPStr)] public String file_name_prefix_; // 设置一个录像文件名前缀, 例如:daniulive public Int32 append_date_; // 如果是1的话,将在文件名上加日期, 例如:daniulive-2017-01-17 public Int32 append_time_; // 如果是1的话,将增加时间,例如:daniulive-2017-01-17-17-10-36 }
开始录像和结束录像,我们有事件回调,设置回调接口如下:
/* * 设置录像回调接口 */ [DllImport(@"SmartPlayerSDK.dll")] public static extern UInt32 NT_SP_SetRecorderCallBack(IntPtr handle, IntPtr call_back_data, SP_SDKRecorderCallBack call_back);
设置录像时音频转AAC编码的开关,考虑到AAC更通用,我们增加其他音频编码(比如speex, pcmu, pcma等)转AAC的功能:
/* * 设置录像时音频转AAC编码的开关, aac比较通用,sdk增加其他音频编码(比如speex, pcmu, pcma等)转aac的功能. * is_transcode: 设置为1的话,如果音频编码不是aac,则转成aac, 如果是aac,则不做转换. 设置为0的话,则不做任何转换. 默认是0. * 注意: 转码会增加性能消耗 */ [DllImport(@"SmartPlayerSDK.dll")] public static extern UInt32 NT_SP_SetRecorderAudioTranscodeAAC(IntPtr handle, Int32 is_transcode);
录像控制接口,开始录像和结束录像:
/* * 启动录像 */ [DllImport(@"SmartPlayerSDK.dll")] public static extern UInt32 NT_SP_StartRecorder(IntPtr handle); /* * 停止录像 */ [DllImport(@"SmartPlayerSDK.dll")] public static extern UInt32 NT_SP_StopRecorder(IntPtr handle);
以C#的demo为例,我们看看录像调用示例代码:
设置录像规则:
private void btn_record_config_Click(object sender, EventArgs e) { RecordConfigForm record_config_dlg = new RecordConfigForm(is_rec_video_, is_rec_audio_, rec_dir_, rec_name_file_prefix_, max_file_size_, is_append_date_, is_append_time_, is_audio_transcode_aac_); record_config_dlg.ShowDialog(); String rec_dir = record_config_dlg.RecDir(); if (!String.IsNullOrEmpty(rec_dir)) { rec_dir_ = rec_dir; } else { MessageBox.Show("未设置录像保存路径,默认保存到rec文件夹下.."); } is_rec_video_ = record_config_dlg.IsRecVideo(); is_rec_audio_ = record_config_dlg.IsRecAudio(); rec_name_file_prefix_ = record_config_dlg.RecNameFilePrefix(); max_file_size_ = record_config_dlg.MaxFileSize(); is_append_date_ = record_config_dlg.IsAppendDate(); is_append_time_ = record_config_dlg.IsAppendTime(); is_audio_transcode_aac_ = record_config_dlg.IsAudioTanscodeAAC(); }
开始录像和结束录像:
private void btn_record_Click(object sender, EventArgs e) { if (player_handle_ == IntPtr.Zero) return; if (btn_record.Text == "录像") { if (!is_rec_video_ && !is_rec_audio_) { MessageBox.Show("音频录制选项和视频录制选项至少需要选择一个!"); return; } if (!is_playing_) { if (!InitCommonSDKParam()) { MessageBox.Show("设置参数错误!"); return; } } NTSmartPlayerSDK.NT_SP_SetRecorderVideo(player_handle_, is_rec_video_ ? 1 : 0); NTSmartPlayerSDK.NT_SP_SetRecorderAudio(player_handle_, is_rec_audio_ ? 1 : 0); UInt32 ret = NTSmartPlayerSDK.NT_SP_SetRecorderDirectoryW(player_handle_, rec_dir_); if (NT.NTBaseCodeDefine.NT_ERC_OK != ret) { MessageBox.Show("设置录像目录失败"); return; } NTSmartPlayerSDK.NT_SP_SetRecorderFileMaxSize(player_handle_, max_file_size_); NT_SP_RecorderFileNameRuler rec_name_ruler = new NT_SP_RecorderFileNameRuler(); rec_name_ruler.type_ = 0; rec_name_ruler.file_name_prefix_ = rec_name_file_prefix_; rec_name_ruler.append_date_ = is_append_date_ ? 1 : 0; rec_name_ruler.append_time_ = is_append_time_ ? 1 : 0; NTSmartPlayerSDK.NT_SP_SetRecorderFileNameRuler(player_handle_, ref rec_name_ruler); record_call_back_ = new SP_SDKRecorderCallBack(SDKRecorderCallBack); NTSmartPlayerSDK.NT_SP_SetRecorderCallBack(player_handle_, IntPtr.Zero, record_call_back_); NTSmartPlayerSDK.NT_SP_SetRecorderAudioTranscodeAAC(player_handle_, is_audio_transcode_aac_ ? 1 : 0); if (NT.NTBaseCodeDefine.NT_ERC_OK != NTSmartPlayerSDK.NT_SP_StartRecorder(player_handle_)) { MessageBox.Show("录像失败!"); return; } btn_record.Text = "停止录像"; is_recording_ = true; } else { StopRecorder(); } }
录像回调处理,由于我们支持宽字符中文路径,回调上来的文件路径,需要做下简单的处理:
private void RecordCallBack(UInt32 status, [MarshalAs(UnmanagedType.LPStr)] String file_name) { byte[] utf8_bytes = Encoding.Default.GetBytes(file_name); byte[] default_bytes = Encoding.Convert(Encoding.UTF8, Encoding.Default, utf8_bytes); String recorder_file_name = Encoding.Default.GetString(default_bytes); StringBuilder sb = new StringBuilder(); sb.Append("录像状态:"); if (status == 1) { sb.Append("new file: "); } else if(status == 2) { sb.Append("finished file: "); } sb.Append(recorder_file_name); MessageBox.Show(sb.ToString()); }
总结
上述是Window平台RTSP|RTMP直播播放录像相关的接口设计探讨,感兴趣的开发者,可以单独和我交流。有人说国内的互联网环境下,做SDK真的很难生存,是的,开源的那么多,干嘛非要用你们的?但是也有人说,目前好多传统行业,对流媒体直播这块,技术要求非常高,市面上找个靠谱的,真的太难了。专注做好一件事,极致做精一件事,口碑做成一件事,比快更快,让RTSP|RTMP直播播放器更适用于延迟要求苛刻的使用场景(如平衡控制、无人机、智能机器人等),是我们一直的追求。