好多开发者使用场景,除了实现基础的低延迟RTSP、RTMP播放外,还需要实现RTSP、RTMP流数据的本地录像功能。本文以大牛直播SDK的Windows平台播放模块为例,介绍下如何实现RTSP、RTMP流录像。
功能设计
- [拉流]支持拉取RTSP流录像;
- [拉流]支持拉取RTMP流录像;
- [逻辑分离]和播放、转发功能完全分离,支持随时录像;
- [参数设置]支持设置单个录像文件大小、录像路径等,并支持纯音频、纯视频、音视频录制模式;
- [音频转码]支持音频(PCMU/PCMA,Speex等)转AAC后再录像;
- [265支持]支持RTSP/RTMP H.265录制到MP4文件;
- [事件回调]从开始录像,到录像结束均有event callback上来。
引入相关头文件和库
C#头文件:
- [base code定义]nt_base_code_define.cs
- [player接口]smart_player_define.cs
- [player参数定义]smart_player_sdk.cs
相关Lib:
- SmartLog.dll
- SmartLog.lib
- SmartPlayerSDK.dll
- SmartPlayerSDK.lib
- avcodec-56.dll
- avdevice-56.dll
- avfilter-5.dll
- avformat-56.dll
- avutil-54.dll
- postproc-53.dll
- swresample-1.dll
- swscale-3.dll
设置日志存放路径
需要在player_api_.Init之前添加下面的代码:
// 设置日志路径(请确保目录存在)
String log_path = "D:\\playerlog";
NTSmartLog.NT_SL_SetPath(log_path);
如目录存在,并具备文件写入权限,关闭应用程序后,相关文件夹下会有smart_sdk.log生成。
初始化SDK
NT_SP_Init:SDK初始化,多实例播放,此接口仅需调用一次即可。
创建播放实例
NT_SP_Open:每调用一次Open接口,对应一个播放实例,如需播放多实例,对应多个player handler。
if (player_handle_ == IntPtr.Zero)
{
player_handle_ = new IntPtr();
UInt32 ret_open = NTSmartPlayerSDK.NT_SP_Open(out player_handle_, IntPtr.Zero, 0, IntPtr.Zero);
if (ret_open != 0)
{
player_handle_ = IntPtr.Zero;
MessageBox.Show("调用NT_SP_Open失败..");
return;
}
}
设置回调事件
NT_SP_SetEventCallBack:用于回调网络链接状态、buffer状态(开始、buffer比例、结束)、实时带宽等,对应EventID如下:
/*事件ID*/
public enum NT_SP_E_EVENT_ID : uint
{
NT_SP_E_EVENT_ID_BASE = NTBaseCodeDefine.NT_EVENT_ID_SMART_PLAYER_SDK,
NT_SP_E_EVENT_ID_CONNECTING = NT_SP_E_EVENT_ID_BASE | 0x2, /*连接中*/
NT_SP_E_EVENT_ID_CONNECTION_FAILED = NT_SP_E_EVENT_ID_BASE | 0x3, /*连接失败*/
NT_SP_E_EVENT_ID_CONNECTED = NT_SP_E_EVENT_ID_BASE | 0x4, /*已连接*/
NT_SP_E_EVENT_ID_DISCONNECTED = NT_SP_E_EVENT_ID_BASE | 0x5, /*断开连接*/
NT_SP_E_EVENT_ID_NO_MEDIADATA_RECEIVED = NT_SP_E_EVENT_ID_BASE | 0x8, /*收不到RTMP数据*/
NT_SP_E_EVENT_ID_RTSP_STATUS_CODE = NT_SP_E_EVENT_ID_BASE | 0xB, /*rtsp status code上报, 目前只上报401, param1表示status code*/
/* 接下来请从0x81开始*/
NT_SP_E_EVENT_ID_START_BUFFERING = NT_SP_E_EVENT_ID_BASE | 0x81, /*开始缓冲*/
NT_SP_E_EVENT_ID_BUFFERING = NT_SP_E_EVENT_ID_BASE | 0x82, /*缓冲中, param1 表示百分比进度*/
NT_SP_E_EVENT_ID_STOP_BUFFERING = NT_SP_E_EVENT_ID_BASE | 0x83, /*停止缓冲*/
NT_SP_E_EVENT_ID_DOWNLOAD_SPEED = NT_SP_E_EVENT_ID_BASE | 0x91, /*下载速度, param1表示下载速度,单位是(Byte/s)*/
NT_SP_E_EVENT_ID_PLAYBACK_REACH_EOS = NT_SP_E_EVENT_ID_BASE | 0xa1, /*播放结束, 直播流没有这个事件,点播流才有*/
NT_SP_E_EVENT_ID_RECORDER_REACH_EOS = NT_SP_E_EVENT_ID_BASE | 0xa2, /*录像结束, 直播流没有这个事件, 点播流才有*/
NT_SP_E_EVENT_ID_PULLSTREAM_REACH_EOS = NT_SP_E_EVENT_ID_BASE | 0xa3, /*拉流结束, 直播流没有这个事件,点播流才有*/
NT_SP_E_EVENT_ID_DURATION = NT_SP_E_EVENT_ID_BASE | 0xa8, /*视频时长,如果是直播,则不上报,如果是点播的话, 若能从视频源获取视频时长的话,则上报, param1表示视频时长,单位是毫秒(ms)*/
}
设置拉流的URL
NT_SP_SetURL:支持rtsp/rtmp/本地FLV文件(全路径)。
设置录像规则
- NT_SP_SetRecorderDirectory:设置录像目录
- NT_SP_SetRecorderFileMaxSize:设置单个文件最大大小
- NT_SP_SetRecorderFileNameRuler:设置录像文件名生成规则
- NT_SP_SetRecorderCallBack:设置录像回调接口
- NT_SP_SetRecorderAudioTranscodeAAC:设置录像时音频转AAC编码的开关, aac比较通用,sdk增加其他音频编码(比如speex, pcmu, pcma等)转aac的功能
- NT_SP_SetRecorderVideo:设置是否录视频,默认的话,如果视频源有视频就录,没有就没得录, 但有些场景下可能不想录制视频,只想录音频,所以增加个开关
- NT_SP_SetRecorderAudio:设置是否录音频,默认的话,如果视频源有音频就录,没有就没得录, 但有些场景下可能不想录制音频,只想录视频,所以增加个开关
实现录像逻辑
- NT_SP_StartRecorder:启动录像
- NT_SP_StopRecorder:停止录像
代码调用示例:
/*
* SmartPlayerForm.cs
* Author: daniusdk.com
* WeChat: xinsheng120
*/
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();
}
}
录像事件回调
/*
* 录像回调
* status: 1:表示开始写一个新录像文件. 2:表示已经写好一个录像文件
* file_name: 实际录像文件名
*/
[UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)]
public delegate void SP_SDKRecorderCallBack(IntPtr handle, IntPtr user_data, UInt32 status, [MarshalAs(UnmanagedType.LPStr)] String file_name);
调用示例:
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());
}
销毁播放实例
NT_SP_Close
调用Close接口后,player handler置空。
if ( player_handle_ != IntPtr.Zero)
{
NTSmartPlayerSDK.NT_SP_Close(player_handle_);
player_handle_ = IntPtr.Zero;
}
释放资源
NT_SP_UnInit
UnInit() 是SDK最后一个调用的接口,多实例环境下,只需要调用一次即可。