技术背景
好多开发者在跟我做技术交流的时候,说用大牛直播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直播播放器更适用于延迟要求苛刻的使用场景(如平衡控制、无人机、智能机器人等),是我们一直的追求。