Windows平台如何实现RTSP|RTMP流录像?

2024-10-01 16:07:06 浏览数 (1)

​好多开发者使用场景,除了实现基础的低延迟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之前添加下面的代码:

代码语言:csharp复制
// 设置日志路径(请确保目录存在)
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。

代码语言:csharp复制
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如下:

代码语言:csharp复制
/*事件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文件(全路径)。

设置录像规则

  1. NT_SP_SetRecorderDirectory:设置录像目录
  2. NT_SP_SetRecorderFileMaxSize:设置单个文件最大大小
  3. NT_SP_SetRecorderFileNameRuler:设置录像文件名生成规则
  4. NT_SP_SetRecorderCallBack:设置录像回调接口
  5. NT_SP_SetRecorderAudioTranscodeAAC:设置录像时音频转AAC编码的开关, aac比较通用,sdk增加其他音频编码(比如speex, pcmu, pcma等)转aac的功能
  6. NT_SP_SetRecorderVideo:设置是否录视频,默认的话,如果视频源有视频就录,没有就没得录, 但有些场景下可能不想录制视频,只想录音频,所以增加个开关
  7. NT_SP_SetRecorderAudio:设置是否录音频,默认的话,如果视频源有音频就录,没有就没得录, 但有些场景下可能不想录制音频,只想录视频,所以增加个开关

实现录像逻辑

  1. NT_SP_StartRecorder:启动录像
  2. NT_SP_StopRecorder:停止录像

代码调用示例:

代码语言:csharp复制
/*
 * 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();
	}
}

录像事件回调

代码语言:csharp复制
/*
 * 录像回调
 * 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);

调用示例:

代码语言:csharp复制
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置空。

代码语言:csharp复制
if ( player_handle_ != IntPtr.Zero)
{
     NTSmartPlayerSDK.NT_SP_Close(player_handle_);
     player_handle_ = IntPtr.Zero;
}

释放资源

NT_SP_UnInit

UnInit() 是SDK最后一个调用的接口,多实例环境下,只需要调用一次即可。

0 人点赞