Windows平台RTSP|RTMP播放器如何实现细粒度录像控制

​技术背景

好多开发者在跟我做技术交流的时候,说用大牛直播SDK模块的特点是,想到什么功能,找找头文件和demo几乎都有对应的实现,你们是何收集到这么多技术需求的?

实际上,这还是取决于我们多年的行业口碑和大规模的实实在在的用户积累,才让我们清楚的认识到,一个直播模块,需要有什么,需要舍弃什么。

技术设计

本文以大牛直播SDK的Windows平台RTSP|RTMP直播播放录制功能设计为例,谈谈我们的接口的细粒度设计。

目前,我们录像模块,涵盖了Windows/Linux/android/iOS 推送端(涵盖轻量级RTSP服务模块、RTMP推流模块和GB28181设备接入模块)和RTSP|RTMP播放端,主要实现了如下功能:

  • 拉流\]支持拉取RTSP流录像;

  • 推流端录像\]支持RTMP\|RTSP推送端同步录像;

  • 推流端录像实时暂停/恢复\]支持推送端录像过程中实时暂停录像、恢复录像;

  • url切换\]在录像过程中,支持切换不同URL,如两个URL配置一致,则可以录制到同一个MP4文件,如不一致,可自动分割到下一个文件;

  • 音频转码\]支持音频(PCMU/PCMA,Speex等)转AAC后再录像;

  • 推送端265录像\]推送端SDK支持H265录像;

  • 事件回调\]从开始录像,到录像结束均有event callback上来,网络堵塞、音视频同步均做了非常友好的处理。

csharp 复制代码
        /*
		 * 设置是否录视频,默认的话,如果视频源有视频就录,没有就没得录, 但有些场景下可能不想录制视频,只想录音频,所以增加个开关
         * 
		 * 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);

录像存储位置设置,设置本地录像目录,需要注意的是,我们已经支持宽字符中文路径设置,开始录像和录像结束,我们会有事件回调上来:

arduino 复制代码
        /*
		 * 设置本地录像目录, 支持中文目录, 需要设置宽字符,比如L"D:\\xxx\\gg"
		 */
        [DllImport(@"SmartPlayerSDK.dll")]
        public static extern UInt32 NT_SP_SetRecorderDirectoryW(IntPtr handle, [MarshalAs(UnmanagedType.LPWStr)] String dir);

设置单个录像文件大小,如果一直是录像状态,超过这个大小后,会自动切分录制到下个文件:

arduino 复制代码
		/*
		 * 设置单个录像文件最大大小, 当超过这个值的时候,将切割成第二个文件
		 * size: 单位是KB(1024Byte), 当前范围是 [5MB-800MB], 超出将被设置到范围内
		 */
        [DllImport(@"SmartPlayerSDK.dll")]
        public static extern UInt32 NT_SP_SetRecorderFileMaxSize(IntPtr handle, UInt32 size);

设置录像名称生成规则:

csharp 复制代码
		/*
		 * 设置录像文件名生成规则
		 */
        [DllImport(@"SmartPlayerSDK.dll", EntryPoint = "NT_SP_SetRecorderFileNameRuler", CallingConvention = CallingConvention.StdCall)]
        public static extern UInt32 NT_SP_SetRecorderFileNameRuler(IntPtr handle, ref NT_SP_RecorderFileNameRuler ruler);

结构体定义如下:

csharp 复制代码
    /*如果三项都是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
    }

开始录像和结束录像,我们有事件回调,设置回调接口如下:

csharp 复制代码
		/*
		 * 设置录像回调接口
		 */
        [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的功能:

csharp 复制代码
        /*
         * 设置录像时音频转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);

录像控制接口,开始录像和结束录像:

csharp 复制代码
		/*
		 * 启动录像
		 */
        [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为例,我们看看录像调用示例代码:

设置录像规则:

ini 复制代码
        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();
        }

开始录像和结束录像:

ini 复制代码
        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();
            }
        }

录像回调处理,由于我们支持宽字符中文路径,回调上来的文件路径,需要做下简单的处理:

ini 复制代码
        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直播播放器更适用于延迟要求苛刻的使用场景(如平衡控制、无人机、智能机器人等),是我们一直的追求。

相关推荐
音视频牛哥9 天前
nginx-rtmp-module之ngx_rtmp.c代码详解
音视频开发·视频编码·直播
音视频牛哥9 天前
ngx_rtmp_flv_module.c — FLV文件处理模块设计与分析
音视频开发·视频编码·直播
音视频牛哥9 天前
音视频新人如何快速上手nginx-rtmp-module
音视频开发·视频编码·直播
是阿鸽呀9 天前
【音视频开发】10. 使用 FFmpeg API 编码 ADTS 音频流
音视频开发
音视频牛哥12 天前
Android平台毫秒级低延迟HTTP-FLV直播播放器技术探究与实现
音视频开发·视频编码·直播
路漫漫心远13 天前
音视频学习笔记十五——渲染与滤镜之GPUImage滤镜链
音视频开发
是阿鸽呀16 天前
【音视频开发】8. 使用 FFmpeg 解码 AAC 音频流
音视频开发
AJi16 天前
Android音视频框架探索(一):多媒体系统服务MediaServer
android·ffmpeg·音视频开发
音视频牛哥21 天前
RTSP协议规范与SmartMediaKit播放器技术解析
音视频开发·视频编码·直播
一只小灿灿22 天前
视频编码中视频帧的类型解析
计算机视觉·音视频·视频编码