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

本文涉及的产品
视觉智能开放平台,图像资源包5000点
视觉智能开放平台,分割抠图1万点
视觉智能开放平台,视频资源包5000点
简介: 大牛直播SDK为Windows平台提供了细致的RTSP/RTMP直播播放及录像功能。支持多平台(Windows/Linux/Android/iOS)的推送端录像,并具备轻量级RTSP服务及GB28181设备接入能力。其特性包括:拉取RTSP/RTMP流录像、推送端同步录像、录像过程中的实时暂停与恢复、支持中文路径设置、单文件大小限制、纯音频/视频或音视频录制模式、音频转码至AAC、H.265编码支持、URL切换时自动文件分割等功能。此外,还提供丰富的事件回调机制以监测录像状态。通过细粒度的接口设计,满足了多样化的应用场景和技术需求。

 技术背景

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

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

技术设计

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

image.gif

目前,我们录像模块,涵盖了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);

image.gif

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

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

image.gif

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

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

image.gif

设置录像名称生成规则:

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

image.gif

结构体定义如下:

/*如果三项都是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
    }

image.gif

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

/*
         * 设置录像回调接口
         */
        [DllImport(@"SmartPlayerSDK.dll")]
        public static extern UInt32 NT_SP_SetRecorderCallBack(IntPtr handle,
            IntPtr call_back_data, SP_SDKRecorderCallBack call_back);

image.gif

设置录像时音频转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);

image.gif

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

/*
         * 启动录像
         */
        [DllImport(@"SmartPlayerSDK.dll")]
        public static extern UInt32 NT_SP_StartRecorder(IntPtr handle);
        /*
         * 停止录像
         */
        [DllImport(@"SmartPlayerSDK.dll")]
        public static extern UInt32 NT_SP_StopRecorder(IntPtr handle);

image.gif

以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();
        }

image.gif

开始录像和结束录像:

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();
            }
        }

image.gif

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

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());
        }

image.gif

总结

上述是Window平台RTSP|RTMP直播播放录像相关的接口设计探讨,感兴趣的开发者,可以单独和我交流。有人说国内的互联网环境下,做SDK真的很难生存,是的,开源的那么多,干嘛非要用你们的?但是也有人说,目前好多传统行业,对流媒体直播这块,技术要求非常高,市面上找个靠谱的,真的太难了。专注做好一件事,极致做精一件事,口碑做成一件事,比快更快,让RTSP|RTMP直播播放器更适用于延迟要求苛刻的使用场景(如平衡控制、无人机、智能机器人等),是我们一直的追求。

相关文章
|
1月前
|
监控 C# 块存储
Windows平台RTSP|RTMP播放器如何叠加OSD文字
做Windows平台RTSP|RTMP播放器的时候,特别是多路播放场景下,开发者希望可以给每一路RTSP或RTMP流添加个额外的OSD台标,以区分不同的设备信息(比如添加摄像头所在位置),本文主要探讨,如何动态添加OSD台标。
Windows平台RTSP|RTMP播放器如何叠加OSD文字
|
1月前
|
Linux Android开发 iOS开发
Windows平台RTSP|RTMP播放器如何实现实时录像功能
Windows平台RTSP、RTMP播放器实时录像接口设计,实际上,除了Windows平台,我们Linux、Android、iOS平台也是一样的设计,单纯的录像模块,如果做的全面,也不是一两个接口可以搞定的
|
13天前
|
边缘计算 安全 网络安全
|
6天前
|
数据安全/隐私保护 Windows
安装 Windows Server 2019
安装 Windows Server 2019
|
9天前
|
网络协议 Windows
Windows Server 2019 DHCP服务器搭建
Windows Server 2019 DHCP服务器搭建
|
9天前
|
网络协议 定位技术 Windows
Windows Server 2019 DNS服务器搭建
Windows Server 2019 DNS服务器搭建
|
6天前
|
安全 网络协议 数据安全/隐私保护
Windows Server 2019 搭建并加入域
Windows Server 2019 搭建并加入域
|
9天前
|
网络协议 文件存储 Windows
Windows Server 2019 FTP服务器搭建
Windows Server 2019 FTP服务器搭建
|
9天前
|
网络协议 Windows
Windows Server 2019 Web服务器搭建
Windows Server 2019 Web服务器搭建
|
1月前
|
网络安全 虚拟化 Windows
windows 11安装openSSH server 遇到的"kex_exchange_identification: read: Connection reset"问题
windows 11安装openSSH server 遇到的"kex_exchange_identification: read: Connection reset"问题