技术背景
上篇文章,我们介绍了Unity平台RTMP、RTSP播放器录像功能,这里,我们详细的介绍下,做个RTSP或RTMP拉流端录像模块有哪些需要考虑的技术点?
在我们常规的考量,RTMP或RTSP流录制,无非就是拉取数据写文件而已,接口设计StartRecorder()/StopRecorder()足矣。
是的,一般场景下,两个接口足够了,但如果是做个更加通用的模块,以下几点是可以酌情考虑的:
- 支持设置单个录像文件大小,比如单个录像文件最大设置到200M,到了200M,可自动切分到下个录像文件;
- 支持设置录像路径;
- 支持设置录像文件前缀:录像文件前缀,是为了更友好的做特定文件的分类;
- 支持文件名增加日期;
- 支持文件名增加时间;
- 支持设置纯音频、纯视频、音视频录制模式;
- 支持音频(PCMU/PCMA,Speex等)转AAC后再录像;
- 支持录像事件回调,从开始录像,到录像结束均有event callback上来。
除了上述的设计,还需要确保和RTSP、RTMP播放在一个实例下,确保播放的过程中可以随时录像,录像的过程中,可以随时播放。
录像模块设计
无图无真相,先看录像设置:

开始录像、停止录像:

Windows平台,我们提供了C++和C#的接口,本文以C++接口设计为例:
先说录像设置:
设置录制纯音频或纯视频:
/*
		* 设置是否录视频,默认的话,如果视频源有视频就录,没有就没得录, 但有些场景下可能不想录制视频,只想录音频,所以增加个开关
		* is_record_video: 1 表示录制视频, 0 表示不录制视频, 默认是1
		*/
		NT_UINT32(NT_API *SetRecorderVideo)(NT_HANDLE handle, NT_INT32 is_record_video);
		/*
		* 设置是否录音频,默认的话,如果视频源有音频就录,没有就没得录, 但有些场景下可能不想录制音频,只想录视频,所以增加个开关
		* is_record_audio: 1 表示录制音频, 0 表示不录制音频, 默认是1
		*/
		NT_UINT32(NT_API *SetRecorderAudio)(NT_HANDLE handle, NT_INT32 is_record_audio);设置录像目录:
/*
		设置本地录像目录, 必须是英文目录,否则会失败
		*/
		NT_UINT32(NT_API *SetRecorderDirectory)(NT_HANDLE handle, NT_PCSTR dir);设置单个录像文件最大大小:
/*
		设置单个录像文件最大大小, 当超过这个值的时候,将切割成第二个文件
		size: 单位是KB(1024Byte), 当前范围是 [5MB-800MB], 超出将被设置到范围内
		*/
		NT_UINT32(NT_API *SetRecorderFileMaxSize)(NT_HANDLE handle, NT_UINT32 size);设置录像文件名生成规则:
/*
		设置录像文件名生成规则
		*/
		NT_UINT32(NT_API *SetRecorderFileNameRuler)(NT_HANDLE handle, NT_SP_RecorderFileNameRuler* ruler);对应的NT_SP_RecorderFileNameRuler设计:
// 如果三项都是0的话,将不能启动录像
typedef struct _NT_SP_RecorderFileNameRuler
{
	NT_UINT32	type_; // 这个值目前默认是0,将来扩展用
	NT_PCSTR	file_name_prefix_; // 设置一个录像文件名前缀, 例如:daniulive
	NT_INT32	append_date_; // 如果是1的话,将在文件名上加日期, 例如:daniulive-2017-01-17
	NT_INT32	append_time_; //  如果是1的话,将增加时间,例如:daniulive-2017-01-17-17-10-36
} NT_SP_RecorderFileNameRuler;设置录像回调接口:
/*
		设置录像回调接口
		*/
		NT_UINT32(NT_API *SetRecorderCallBack)(NT_HANDLE handle,
			NT_PVOID call_back_data, SP_SDKRecorderCallBack call_back);对应录像回调:
/*
录像回调
status: 1:表示开始写一个新录像文件. 2:表示已经写好一个录像文件
file_name: 实际录像文件名
*/
typedef NT_VOID(NT_CALLBACK* SP_SDKRecorderCallBack)(NT_HANDLE handle, NT_PVOID user_data, NT_UINT32 status,
	NT_PCSTR file_name);设置音频转AAC开关:
/*
		设置录像时音频转AAC编码的开关, aac比较通用,sdk增加其他音频编码(比如speex, pcmu, pcma等)转aac的功能.
		is_transcode: 设置为1的话,如果音频编码不是aac,则转成aac, 如果是aac,则不做转换. 设置为0的话,则不做任何转换. 默认是0.
		注意: 转码会增加性能消耗
		*/
		NT_UINT32(NT_API *SetRecorderAudioTranscodeAAC)(NT_HANDLE handle, NT_INT32 is_transcode);启动录像、停止录像:
/*
		启动录像
		*/
		NT_UINT32(NT_API *StartRecorder)(NT_HANDLE handle);
		/*
		停止录像
		*/
		NT_UINT32(NT_API *StopRecorder)(NT_HANDLE handle);接口调用实例
/*
 * Author: https://daniusdk.com
 */
void CSmartPlayerDlg::OnBnClickedButtonRecord()
{
	if ( player_handle_ == NULL )
		return;
	CString btn_record_str;
	btn_record_.GetWindowTextW(btn_record_str);
	if ( btn_record_str == _T("录像") )
	{
		if ( !rec_conf_info_.is_record_video_ && !rec_conf_info_.is_record_audio_ )
		{
			AfxMessageBox(_T("音频录制选项和视频录制选项至少需要选择一个!"));
			return;
		}
		if ( !is_playing_ )
		{
			if ( !InitCommonSDKParam() )
			{
				AfxMessageBox(_T("设置参数错误!"));
				return;
			}
		}
		player_api_.SetRecorderVideo(player_handle_, rec_conf_info_.is_record_video_ ? 1 : 0);
		player_api_.SetRecorderAudio(player_handle_, rec_conf_info_.is_record_audio_ ? 1 : 0);
		auto ret = player_api_.SetRecorderDirectory(player_handle_, rec_conf_info_.dir_.c_str());
		if ( NT_ERC_OK != ret )
		{
			AfxMessageBox(_T("设置录像目录失败,请确保目录存在且是英文目录"));
			return;
		}
		player_api_.SetRecorderFileMaxSize(player_handle_, rec_conf_info_.file_max_size_);
		NT_SP_RecorderFileNameRuler rec_name_ruler = { 0 };
		rec_name_ruler.type_ = 0;
		rec_name_ruler.file_name_prefix_ = rec_conf_info_.file_name_prefix_.c_str();
		rec_name_ruler.append_date_		 = rec_conf_info_.is_append_date_ ? 1 : 0;
		rec_name_ruler.append_time_		 = rec_conf_info_.is_append_time_ ? 1 : 0;
		player_api_.SetRecorderFileNameRuler(player_handle_, &rec_name_ruler);
		player_api_.SetRecorderCallBack(player_handle_, GetSafeHwnd(), &SP_SDKRecorderHandle);
		player_api_.SetRecorderAudioTranscodeAAC(player_handle_, rec_conf_info_.is_audio_transcode_aac_ ? 1 : 0);
		if ( NT_ERC_OK != player_api_.StartRecorder(player_handle_) )
		{
			AfxMessageBox(_T("录像失败!"));
			return;
		}
		btn_record_.SetWindowTextW(_T("停止录像"));
		is_recording_ = true;
	}
	else
	{
		StopRecorder();
	}
}停止录像:
void CSmartPlayerDlg::StopRecorder()
{
	if (player_handle_ == NULL)
		return;
	player_api_.StopRecorder(player_handle_);
	btn_record_.SetWindowTextW(_T("录像"));
	is_recording_ = false;
	if (!is_playing_)
	{
		SetWindowText(base_title_);
		edit_duration_.SetWindowText(_T(""));
		btn_pause_.SetWindowText(_T("暂停"));
	}
	RefreshLogo(true);
}总结
一个小小的录像功能,如果做的更加通用兼容性好的话,需要注意的点还很多,本文抛砖引玉,感兴趣的开发者可酌情参考。
 
 
                     
            
        













 
                    

 
                 
                    