精华内容
下载资源
问答
  • rtmp播放器
    2020-10-15 16:46:02

    好多开发者提到,RTMP播放器,不知道有哪些对标和考察指标,以下大概聊聊我们的一点经验,感兴趣的,可以关注 github

    1. 低延迟:大多数RTMP的播放都面向直播场景,如果延迟过大,严重影响体验,所以,低延迟是衡量一个好的RTMP播放器非常重要的指标,目前大牛直播SDK的RTMP直播播放延迟比开源播放器更优异(大牛直播SDK延迟在1秒左右,开源播放器如VLC,延迟在5-7秒),而且长时间运行下,大牛直播SDK播放端不会造成延迟累积,开源或第三方播放器,长时间运行,容易产生延迟累积;

    部分服务器会缓存GOP,确保快速实现首屏播放,又不影响延迟,对此,我们设计了快速启动接口,快速render第一帧的同时,追到最新的播放数据:

    		/*
             * 设置秒开, 1为秒开, 0为不秒开
    		 */
    		[DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetFastStartup(IntPtr handle, Int32 isFastStartup);

    2. 音视频同步处理大多播放器为了追求低延迟,甚至不做音视频同步,拿到audio video直接播放,导致a/v不同步,还有就是时间戳乱跳等各种问题,大牛直播SDK提供的播放器,具备好的时间戳同步和异常时间戳矫正机制;

    备注:如果是超低延迟模式下,可以0 buffer,不做音视频同步:

            /*
             * 设置低延时播放模式,默认是正常播放模式
             * mode: 1为低延时模式, 0为正常模式,其他只无效
             * 接口调用成功返回NT_ERC_OK
             */
            [DllImport(@"SmartPlayerSDK.dll")]
    		public static extern UInt32 NT_SP_SetLowLatencyMode(IntPtr handle, Int32 mode);

    3. 支持多实例:大牛直播SDK提供的RTMP直播播放SDK支持在设备性能允许的情况下,支持多实例播放RTMP流数据,大多开源播放器对多实例支持不太友好;

    除了常规的多实例外,比如大屏监控场景下,尽管我们CPU占用已经是行业内非常低的了,但是好多厂家下,不是每路都需要全帧播放,针对此种情况,我们做了实时只播放关键帧和全帧播放的接口设计,比如8个实例,其中不太重要的几路数据,可以设置只播放关键帧,需要重点关注时,点击全帧率播放即可,这样既节省了系统开销,又实现了多路播放的目的:

            /*
             * 设置只解码视频关键帧
             * is_only_dec_key_frame: 1:表示只解码关键帧, 0:表示都解码, 默认是0
             * 成功返回NT_ERC_OK
    		 */
            [DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetOnlyDecodeVideoKeyFrame(IntPtr handle, Int32 is_only_dec_key_frame);

    4. 支持buffer time设置:在一些有网络抖动的场景,播放器需要支持buffer time设置,一般来说,以毫秒计,开源播放器对此支持不够友好;

    5. 实时静音:比如,多窗口播放RTMP流,如果每个audio都播放出来,体验非常不好,所以实时静音功能非常必要,开源播放器不具备实时静音功能;

    6. 视频view旋转:好多摄像头由于安装限制,导致图像倒置,所以一个好的RTMP播放器应该支持如视频view实时旋转(0° 90° 180° 270°)、水平反转、垂直反转,开源或第三方播放器不具备此功能;

            /*
    		 *上下反转(垂直反转)
    		 *is_flip: 1:表示反转, 0:表示不反转
    		 */
    		[DllImport(@"SmartPlayerSDK.dll")]
    		public static extern UInt32 NT_SP_SetFlipVertical(IntPtr handle, Int32 is_flip);
    
    		/*
    		 *水平反转
    		 *is_flip: 1:表示反转, 0:表示不反转
    		 */
    		[DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetFlipHorizontal(IntPtr handle, Int32 is_flip);
    
    		/*
             * 设置旋转,顺时针旋转
             * degress: 设置0, 90, 180, 270度有效,其他值无效
             * 注意:除了0度,其他角度播放会耗费更多CPU
             * 接口调用成功返回NT_ERC_OK
    		 */
            [DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetRotation(IntPtr handle, Int32 degress);

    7. 支持解码后audio/video数据输出:大牛直播SDK接触到好多开发者,希望能在播放的同时,获取到YUV或RGB数据,进行人脸匹配等算法分析,开源播放器不具备此功能;

            public void SDKVideoFrameCallBack(UInt32 status, NT_SP_VideoFrame frame)
            {
                if (cur_video_frame_.plane0_ != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(cur_video_frame_.plane0_);
                    cur_video_frame_.plane0_ = IntPtr.Zero;
                }
    
                cur_video_frame_ = frame;
    
                this.Invalidate();
            }
    
            public void SDKAudioPCMFrameCallBack(UInt32 status, IntPtr data, UInt32 size,
            Int32 sample_rate, Int32 channel, Int32 per_channel_sample_number)
            {
                //这里拿到回调的PCM frame,进行相关操作(如自己播放)
                //label_debug.Text = per_channel_sample_number.ToString();
    
                //release
                Marshal.FreeHGlobal(data);
            }
    
            public void SetVideoFrameCallBack(IntPtr handle, IntPtr userData, UInt32 status, IntPtr frame)
            {
                if (frame == IntPtr.Zero)
                {
                    return;
                }
    
                //如需直接处理RGB数据,请参考以下流程
                NT_SP_VideoFrame video_frame = (NT_SP_VideoFrame)Marshal.PtrToStructure(frame, typeof(NT_SP_VideoFrame));
    
                NT_SP_VideoFrame pVideoFrame = new NT_SP_VideoFrame();
    
                pVideoFrame.format_ = video_frame.format_;
                pVideoFrame.width_ = video_frame.width_;
                pVideoFrame.height_ = video_frame.height_;
    
                pVideoFrame.timestamp_ = video_frame.timestamp_;
                pVideoFrame.stride0_ = video_frame.stride0_;
                pVideoFrame.stride1_ = video_frame.stride1_;
                pVideoFrame.stride2_ = video_frame.stride2_;
                pVideoFrame.stride3_ = video_frame.stride3_;
    
                Int32 argb_size = video_frame.stride0_ * video_frame.height_;
    
                pVideoFrame.plane0_ = Marshal.AllocHGlobal(argb_size);
                CopyMemory(pVideoFrame.plane0_, video_frame.plane0_, (UInt32)argb_size);
    
    
                if (playWnd.InvokeRequired)
                {
                    BeginInvoke(set_video_frame_call_back_, status, pVideoFrame);
                }
                else
                {
                    set_video_frame_call_back_(status, pVideoFrame);
                }
            }
    
            public void SetAudioPCMFrameCallBack(IntPtr handle, IntPtr user_data,
                 UInt32 status, IntPtr data, UInt32 size,
                 Int32 sample_rate, Int32 channel, Int32 per_channel_sample_number)
            {
                if (data == IntPtr.Zero || size == 0)
                {
                    return;
                }
    
                IntPtr pcmData = Marshal.AllocHGlobal((Int32)size);
                CopyMemory(pcmData, data, (UInt32)size);
    
                if (playWnd.InvokeRequired)
                {
                    BeginInvoke(set_audio_pcm_frame_call_back_, status, pcmData, size, sample_rate, channel, per_channel_sample_number);
                }
                else
                {
                    set_audio_pcm_frame_call_back_(status, pcmData, size, sample_rate, channel, per_channel_sample_number);
                }
            }

    8. 实时快照:感兴趣或重要的画面,实时截取下来非常必要,一般播放器不具备快照能力,开源播放器不具备此功能;

            /*
    		 * 捕获图片
    		 * file_name_utf8: 文件名称,utf8编码
    		 * call_back_data: 回调时用户自定义数据
    		 * call_back: 回调函数,用来通知用户截图已经完成或者失败
    		 * 成功返回 NT_ERC_OK
    		 * 只有在播放时调用才可能成功,其他情况下调用,返回错误.
    		 * 因为生成PNG文件比较耗时,一般需要几百毫秒,为防止CPU过高,SDK会限制截图请求数量,当超过一定数量时,
    		 * 调用这个接口会返回NT_ERC_SP_TOO_MANY_CAPTURE_IMAGE_REQUESTS. 这种情况下, 请延时一段时间,等SDK处理掉一些请求后,再尝试.
    		 */
            [DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_CaptureImage(IntPtr handle, IntPtr file_name_utf8,
                IntPtr call_back_data, SP_SDKCaptureImageCallBack call_back);
    
    
            public void SDKCaptureImageCallBack(IntPtr handle, IntPtr userData, UInt32 result, IntPtr file_name)
            {
                if (file_name == IntPtr.Zero)
                    return;
    
                int index = 0;
    
                while (true)
                {
                    if (0 == Marshal.ReadByte(file_name, index))
                        break;
    
                    index++;
                }
    
                byte[] file_name_buffer = new byte[index];
    
                Marshal.Copy(file_name, file_name_buffer, 0, index);
    
                byte[] dst_buffer = Encoding.Convert(Encoding.UTF8, Encoding.Default, file_name_buffer, 0, file_name_buffer.Length);
                String image_name = Encoding.Default.GetString(dst_buffer, 0, dst_buffer.Length);
    
                if (playWnd.InvokeRequired)
                {
                    BeginInvoke(set_capture_image_call_back_, result, image_name);
                }
                else
                {
                    set_capture_image_call_back_(result, image_name);
                }
            }

    9. 网络抖动处理(如断网重连):稳定的网络处理机制、支持如断网重连等,开源播放器对网络异常处理支持较差;

            /*事件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)*/
            }

    10. 长期运行稳定性:大牛直播SDK提供的RTMP直播播放SDK适用于长时间运行,开源播放器对长时间运行稳定性支持较差;

    11. 实时下载速度反馈:大牛直播SDK提供音视频流实时下载回调,并可设置回调时间间隔,确保实时下载速度反馈,以此来监听网络状态,开源播放器不具备此能力;

            /*
             * 设置下载速度上报, 默认不上报下载速度
             * is_report: 上报开关, 1: 表上报. 0: 表示不上报. 其他值无效.
             * report_interval: 上报时间间隔(上报频率),单位是秒,最小值是1秒1次. 如果小于1且设置了上报,将调用失败
             * 注意:如果设置上报的话,请设置SetEventCallBack, 然后在回调函数里面处理这个事件.
             * 上报事件是:NT_SP_E_EVENT_ID_DOWNLOAD_SPEED
             * 这个接口必须在StartXXX之前调用
             * 成功返回NT_ERC_OK
    		 */
            [DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetReportDownloadSpeed(IntPtr handle, Int32 is_report, Int32 report_interval);
    
    		/*
             * 主动获取下载速度
             * speed: 返回下载速度,单位是Byte/s
             * (注意:这个接口必须在startXXX之后调用,否则会失败)
             * 成功返回NT_ERC_OK
    		 */
            [DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_GetDownloadSpeed(IntPtr handle, ref Int32 speed);

    12. 异常状态处理Event状态回调如播放的过程中断网,大牛直播SDK提供的播放器可实时回调相关状态,确保上层模块感知处理,开源播放器对此支持不好;

    		/*
             * 设置事件回调,如果想监听事件的话,建议调用Open成功后,就调用这个接口
             */
            [DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetEventCallBack(IntPtr handle, IntPtr call_back_data, SP_SDKEventCallBack call_back);
    
    		/*
             * 设置视频大小回调接口
    		 */
    		[DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetVideoSizeCallBack(IntPtr handle, IntPtr callbackdata, SP_SDKVideoSizeCallBack call_back);
    		
    		/*
             * 设置视频回调, 吐视频数据出来
             * frame_format: 只能是NT_SP_E_VIDEO_FRAME_FORMAT_RGB32, NT_SP_E_VIDEO_FRAME_FROMAT_I420
    		 */
            [DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetVideoFrameCallBack(IntPtr handle, Int32 frame_format,
                IntPtr callbackdata, SP_SDKVideoFrameCallBack call_back);
    
            /*
             * 设置视频回调, 吐视频数据出来, 可以指定吐出来的视频宽高
             * handle: 播放句柄
             * scale_width:缩放宽度(必须是偶数,建议是 16 的倍数)
             * scale_height:缩放高度(必须是偶数)
             * scale_filter_mode: 缩放质量, 0 的话 SDK 将使用默认值, 目前可设置范围为[1, 3], 值越大 缩放质量越好,但越耗性能
             * frame_format: 只能是NT_SP_E_VIDEO_FRAME_FORMAT_RGB32, NT_SP_E_VIDEO_FRAME_FROMAT_I420
             * 成功返回NT_ERC_OK
    		 */
            [DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetVideoFrameCallBackV2(IntPtr handle,
                Int32 scale_width, Int32 scale_height,
                Int32 scale_filter_mode, Int32 frame_format,
                IntPtr call_back_data, SP_SDKVideoFrameCallBack call_back);
    
           /*
    		* 设置绘制视频帧时,视频帧时间戳回调
    		* 注意如果当前播放流是纯音频,那么将不会回调,这个仅在有视频的情况下才有效
    		*/
            [DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetRenderVideoFrameTimestampCallBack(IntPtr handle,
                IntPtr callbackdata, SP_SDKRenderVideoFrameTimestampCallBack call_back);
    
            /*
             * 设置音频PCM帧回调, 吐PCM数据出来,目前每帧大小是10ms.
    		 */
            [DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetAudioPCMFrameCallBack(IntPtr handle,
                IntPtr call_back_data, SP_SDKAudioPCMFrameCallBack call_back);
    
            /*
    		 * 设置用户数据回调
    		 */
    		[DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetUserDataCallBack(IntPtr handle,
    			IntPtr call_back_data, SP_SDKUserDataCallBack call_back);
            
    		/*
    		 * 设置视频sei数据回调
    		 */
    		[DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetSEIDataCallBack(IntPtr handle,
                IntPtr call_back_data, SP_SDKSEIDataCallBack call_back);

    13. 设置视频填充模式(等比例显示):好多情况下,有些场景需要全view铺满播放,有些为了防止视频拉伸,可以设置成等比例缩放显示;

            /*
             * 设置视频画面的填充模式,如填充整个绘制窗口、等比例填充绘制窗口,如不设置,默认填充整个绘制窗口
             * handle: 播放句柄
             * mode: 0: 填充整个绘制窗口; 1: 等比例填充绘制窗口, 默认值是0
             * 成功返回NT_ERC_OK
    		 */
            [DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_SetRenderScaleMode(IntPtr handle, Int32 mode);

    14. D3D检测:一般来说市面上的大多Windows都支持D3D,有些小众化的,只支持GDI模式绘制,所以为了更好的兼容性,这个接口非常必要。

    		/*
             * handle: 播放句柄
             * hwnd: 这个要传入真正用来绘制的窗口句柄
             * is_support: 如果支持的话 *is_support 为1, 不支持的话为0
             * 接口调用成功返回NT_ERC_OK
    		 */
            [DllImport(@"SmartPlayerSDK.dll")]
            public static extern UInt32 NT_SP_IsSupportD3DRender(IntPtr handle, IntPtr hwnd, ref Int32 is_support);

     

    更多相关内容
  • player)是一款简单到毫无特色的纯rtmp播放器, 不依赖ffmpeg,仅依赖srs-librtmp第三方库,体积小,可调整性强. 功能介绍 未依赖ffmpeg框架,基于srs-librtmp的rtmp拉流,编译打包更简单; 支持Android API level 16及以上...
  • RTMP播放器

    2017-07-13 13:18:10
    rtmp flash player. 支持设置缓存和缓冲大小
  • SGPlayer 是一款基于 AVPlayer、FFmpeg 的媒体资源播放器框架。支持360°全景视频,VR视频,RTMP、RTSP 等直播流;同时支持 iOS、macOS、tvOS 三个平台。 因为包大小限制,删除了项目中的 demo 视频。完整教程请...
  • RTMP播放器测试程序

    2016-08-31 16:26:13
    RTMP播放器测试程序
  • rtmp播放器

    热门讨论 2013-02-06 14:24:16
    这个里面有两个rtmp播放器,应该都可以嵌入到网页中用的。
  • rtmp播放器Demo

    2018-05-25 11:27:20
    基于C#开发的一款用于网络直播的工具,可以播放网络流视频数据,延迟小
  • htmp rtmp播放器

    2020-09-22 14:43:50
    htmp rtmp播放器 htmp rtmp播放器 htmp rtmp播放器 htmp rtmp播放器 htmp rtmp播放器 htmp rtmp播放器
  • mediaPlayer.play(Uri.parse("rtmp://192.168.0.101/live/livestream")); } @Override protected void onStop(){ super.onStop(); mediaPlayer.detachViews(); mediaPlayer.stop(); } @Override ...
  • 从零开发一款Android RTMP播放器

    千次阅读 2021-10-12 14:12:29
    15年移动端直播应用火起来的时候,主要的直播协议是RTMP,多媒体服务以Adobe的AMS、wowza、Red5、crtmpserver、nginx rtmp module等,后面过长RTMP服务SRS开始流行。Android端播放器主要以开始以EXOPlayer播放HLS,...

    1. 背景介绍

    15年移动端直播应用火起来的时候,主要的直播协议是RTMP,多媒体服务以Adobe的AMS、wowza、Red5、crtmpserver、nginx rtmp module等,后面过长RTMP服务SRS开始流行。Android端播放器主要以开始以EXOPlayer播放HLS,但是HLS有延迟高的确定,随后大家主要使用开源的ijkplyer,ijkplayer通过ffmpeg进行拉流及解码,支持多种音视频编码,还有跨平台,API与系统播放器保持一致等特征,后续各大厂提供的直播SDK均有ijkplayer身影。

    当时在做一款游戏SDK,SDK主要提供了游戏画面声音采集、音视频编解码、直播推流、直播拉流播放等,SDK为游戏提供直播功能,播放也是采用了现成的ijkplayer播放器。但是SDK推广的时候遇到了问题,游戏厂家嫌弃SDK体积大(其实总共也就3Mb左右),我们需要一款体积小,性能高的播放器,由于开发成本的原因一直没有时间做,后面换工作期间,花了一个月时间把这款播放器开发出来,并开源了出来。oarplayer 是基于MediaCodec与srs-librtmp,完全不依赖ffmpeg,纯C语言实现的播放器。本文主要介绍这款播放器的实现思路。

    2. 整体架构设计

    播放器整体播放流程如下:

    通过srs-librtmp拉取直播流,通过package type分离音视频流,将package数据缓存到package队列,解码线程不断从package队列读取package交由解码器解码,解码器将解码后的frame存储到frame队列,opensles播放线程与opengles渲染线程从frame队列读取frame播放与渲染,这里还涉及到音视频同步。

    播放器主要涉及了以下线程:

    1. rtmp拉流线程;
    2. 音频解码线程;
    3. 视频解码线程;
    4. 音频播放线程;
    5. 视频渲染线程;
    6. JNI回调线程。

    3. API接口设计

    通过以下几步即可完成rtmp播放:

    1. 实例化OARPlayer:OARPlayer player = new OARPlayer();
    2. 设置视频源:player.setDataSource(rtmp_url);
    3. 设置surface:player.setSurface(surfaceView.getHolder());
    4. 开始播放:player.start();
    5. 停止播放:player.stop();
    6. 释放资源:player.release();

    Java层方法封装了JNI层方法,JNI层封装调用了对应的具体功能。

    4. rtmp拉流线程

    oarplayer使用的是srs-librtmp,srs-librtmp是从SRS服务器导出的一个客户端库,作者提供srs-librtmp初衷是:

    1. 觉得rtmpdump/librtmp的代码太难读了,而SRS的代码可读性很好;
    2. 压测工具srs-bench是个客户端,需要一个客户端库;
    3. 觉得服务器能搞好,客户端也不在话下

    目前srs-librtmp作者已经停止维护,主要原因如作者所说:

    决定开源项目正义的绝对不是技术多好,而是能跑多久。技术很牛,性能很强,代码风格很好,固然是个好事,但是这些都顶不上一个“不维护”的大罪过,代码放出来不维护,怎么跟进业内技术的不断发展呢。而决定能跑多久的,首先是技术热情,然后是维护者的领域背景。SRS的维护者都是服务器背景,大家的工作都是在服务器,客户端经验太少了,无法长久维护客户端的库。因此,SRS决定果断放弃srs-librtmp,不再维护客户端库,聚焦于服务器的快速迭代。客户端并非不重要,而是要交给专业的客户端的开源项目和朋友维护,比如FFmpeg也自己实现了librtmp。

    oarplayer当初使用srs-librtmp是基于srs-librtmp代码的可读性考虑。oarplayer有相当高的模块化特性,可以很方便的替换各个rtmp lib实现。这里介绍srs-librtmp接口:

    1. 创建srs_rtmp_t对象:srs_rtmp_create(url);
    2. 设置读写超时时间:srs_rtmp_set_timeout;
    3. 开始握手:srs_rtmp_handshake;
    4. 开始连接:srs_rtmp_connect_app;
    5. 设置播放模式:srs_rtmp_play_stream;
    6. 循环读取音视频包:srs_rtmp_read_packet(rtmp, &type, &timestamp, &data, &size);
    7. 解析音频包:
      1. 获取编码类型:srs_utils_flv_audio_sound_format;
      2. 获取音频采样率:srs_utils_flv_audio_sound_rate;
      3. 获取采样位深:srs_utils_flv_audio_sound_size;
      4. 获取声道数:srs_utils_flv_audio_sound_type;
      5. 获取音频包类型:srs_utils_flv_audio_aac_packet_type;
    8. 解析视频包:
      1. 获取编码类型:srs_utils_flv_video_codec_id;
      2. 是否关键帧:srs_utils_flv_video_frame_type;
      3. 获取视频包类型:srs_utils_flv_video_avc_packet_type;
    9. 解析metadata类型;
    10. 销毁srs_rtmp_t对象:srs_rtmp_destroy;

    这里有个小技巧,我们在拉流线程中,循环调用srs_rtmp_read_packet方法,可以通过srs_rtmp_set_timeout设置超时时间,但是如果超时时间设置的太短,会导致频繁的唤起线程,如果设置超时时间太长,我们在停止时,必须等待超时结束才会能真正结束。这里我们可以使用poll模型,将rtmp的tcp socket放入poll中,再放入一个管道fd,在需要停止时向管道写入一个指令,唤醒poll,直接停止rtmp拉流线程。

    5. 主要数据结构

    5.1 package结构:

    typedef struct OARPacket {
        int size;//包大小
        PktType_e type;//包类型
        int64_t dts;//解码时间戳
        int64_t pts;//显示时间戳
        int isKeyframe;//是否关键帧
        struct OARPacket *next;//下一个包地址
        uint8_t data[0];//包数据内容
    }OARPacket;
    

    5.2 package队列:

    typedef struct oar_packet_queue {
        PktType_e media_type;//类型
        pthread_mutex_t *mutex;//线程锁
        pthread_cond_t *cond;//条件变量
        OARPacket *cachedPackets;//队列首地址
        OARPacket *lastPacket;//队列最后一个元素
    
        int count;//数量
        int total_bytes;//总字节数
        uint64_t max_duration;//最大时长
    
        void (*full_cb)(void *);//队列满回调
    
        void (*empty_cb)(void *);//队列为空回调
    
        void *cb_data;
    } oar_packet_queue;
    

    5.3 Frame类型

    typedef struct OARFrame {
        int size;//帧大小
        PktType_e type;//帧类型
        int64_t dts;//解码时间戳
        int64_t pts;//显示时间戳
        int format;//格式(用于视频)
        int width;//宽(用于视频)
        int height;//高(用于视频)
        int64_t pkt_pos;
        int sample_rate;//采样率(用于音频)
        struct OARFrame *next;
        uint8_t data[0];
    }OARFrame;
    

    5.4 Frame队列

    typedef struct oar_frame_queue {
        pthread_mutex_t *mutex;
        pthread_cond_t *cond;
        OARFrame *cachedFrames;
        OARFrame *lastFrame;
        int count;//帧数量
        unsigned int size;
    } oar_frame_queue;
    

    6. 解码线程

    我们的rtmp流拉取、解码、渲染、音频输出都在C层实现。在C层,Android 21之后系统提供了AMediaCodec接口,我们直接find_library(media-ndk mediandk),并引入<media/NdkMediaCodec.h>头文件即可。对于Android 21之前版本,可以在C层调用Java层的MediaCodec。下面分别介绍两种实现:

    6.1 Java层代理解码

    Java层MediaCodec解码使用步骤:

    1. 创建解码器:codec = MediaCodec.createDecoderByType(codecName);
    2. 配置解码器格式:codec.configure(format, null, null, 0);
    3. 启动解码器:codec.start()
    4. 获取解码输入缓存ID:dequeueInputBuffer
    5. 获取解码输入缓存:getInputBuffer
    6. 获取解码输出缓存:dequeueOutputBufferIndex
    7. 释放输出缓存:releaseOutPutBuffer
    8. 停止解码器:codec.stop();

    Jni层封装对应的调用接口即可。

    6.2 C层解码器使用

    C层接口介绍:

    1. 创建Format:AMediaFormat_new;
    2. 创建解码器:AMediaCodec_createDecoderByType;
    3. 配置解码参数:AMediaCodec_configure;
    4. 启动解码器:AMediaCodec_start;
    5. 输入音视频包:
      1. 获取输入buffer序列:AMediaCodec_dequeueInputBuffer
      2. 获取输入buffer:AMediaCodec_getInputBuffer
      3. 拷贝数据:memcpy
      4. 输入buffer放入解码器:AMediaCodec_queueInputBuffer
    6. 获取解码后帧:
      1. 获取输出buffer序列:AMediaCodec_dequeueOutputBuffer
      2. 获取输出buffer:AMediaCodec_getOutputBuffer

    我们发现不管是Java层还是C层的接口都是提供了类似的思路,其实他们最终调用的还是系统的解码框架。

    这里我们可以根据系统版本来觉得使用Java层接口和C层接口,我们的oarplayer,主要的代码都是在C层实现,所以我们也有限使用C层接口。

    7. 音频输出线程

    音频输出我们使用opensl实现,之前文章介绍过Android音频架构,其实也可以使用AAudio或者Oboe。这里再简单介绍下opensl es的使用。

    1. 创建引擎:slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    2. 实现引擎:(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    3. 获取接口:(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
    4. 创建输出混流器:(*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);;
    5. 实现混流器:(*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    6. 配置音频源:SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
    7. 配置Format:SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, channel, SL_SAMPLINGRATE_44_1,SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN};
    8. 创建播放器:(*engineEngine)->CreateAudioPlayer(engineEngine,&bqPlayerObject, &audioSrc, &audioSnk,2, ids, req);
    9. 实现播放器:(*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
    10. 获取播放接口:(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
    11. 获取缓冲区接口:(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,&bqPlayerBufferQueue);
    12. 注册缓存回调:(*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, oar);
    13. 获取音量调节器:(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
    14. 缓存回调中不断的从音频帧队列读取数据,并写入缓存队列:(*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, ctx->buffer,(SLuint32)ctx->frame_size);

    上面就是音频播放的opensl es接口使用介绍。

    8. 渲染线程

    相比较于音频播放,视频渲染可能更复杂一些,除了opengl引擎创建,opengl线程创建,oarplayer使用的是基于音频的同步方式,所以在视频渲染时还需要考虑音视频同步问题。

    8.1 OpenGL引擎创建

    1. 生成buffer:glGenBuffers
    2. 绑定buffer:glBindBuffer(GL_ARRAY_BUFFER, model->vbos[0])
    3. 设置清屏色:glClearColor
    4. 创建纹理对象:texture2D
    5. 创建着色器对象:glCreateShader
    6. 设置着色器源码:glShaderSource
    7. 编译着色器源码:glCompileShader
    8. 附着着色器:glAttachShader
    9. 连接着色器:glLinkProgram

    opengl与硬件交互还需要EGL环境,下面展示EGL初始化流程代码:

    static void init_egl(oarplayer * oar){
        oar_video_render_context *ctx = oar->video_render_ctx;
        const EGLint attribs[] = {EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE,
                                  EGL_OPENGL_ES2_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE,
                                  8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 0, EGL_STENCIL_SIZE, 0,
                                  EGL_NONE};
        EGLint numConfigs;
        ctx->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
        EGLint majorVersion, minorVersion;
        eglInitialize(ctx->display, &majorVersion, &minorVersion);
        eglChooseConfig(ctx->display, attribs, &ctx->config, 1, &numConfigs);
        ctx->surface = eglCreateWindowSurface(ctx->display, ctx->config, ctx->window, NULL);
        EGLint attrs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
        ctx->context = eglCreateContext(ctx->display, ctx->config, NULL, attrs);
        EGLint err = eglGetError();
        if (err != EGL_SUCCESS) {
            LOGE("egl error");
        }
        if (eglMakeCurrent(ctx->display, ctx->surface, ctx->surface, ctx->context) == EGL_FALSE) {
            LOGE("------EGL-FALSE");
        }
        eglQuerySurface(ctx->display, ctx->surface, EGL_WIDTH, &ctx->width);
        eglQuerySurface(ctx->display, ctx->surface, EGL_HEIGHT, &ctx->height);
        initTexture(oar);
    
        oar_java_class * jc = oar->jc;
        JNIEnv * jniEnv = oar->video_render_ctx->jniEnv;
        jobject surface_texture = (*jniEnv)->CallStaticObjectMethod(jniEnv, jc->SurfaceTextureBridge, jc->texture_getSurface, ctx->texture[3]);
        ctx->texture_window = ANativeWindow_fromSurface(jniEnv, surface_texture);
    
    }
    

    8.2 音视频同步

    常见的音视频同步有三种:

    1. 基于视频同步;
    2. 基于音频同步;
    3. 基于第三方时间戳同步。

    这里我们使用基于音频帧同步的方法,渲染画面时,判断音频时间戳diff与视频画面渲染周期,如果大于周期,则等待,如果大于0小于周期,如果小于0则立马绘制。

    下面展示渲染代码:

    /**
     *
     * @param oar
     * @param frame
     * @return  0   draw
     *         -1   sleep 33ms  continue
     *         -2   break
     */
    static inline int draw_video_frame(oarplayer *oar) {
        // 上一次可能没有画, 这种情况就不需要取新的了
        if (oar->video_frame == NULL) {
            oar->video_frame = oar_frame_queue_get(oar->video_frame_queue);
        }
        // buffer empty  ==> sleep 10ms , return 0
        // eos           ==> return -2
        if (oar->video_frame == NULL) {
            _LOGD("video_frame is null...");
            usleep(BUFFER_EMPTY_SLEEP_US);
            return 0;
    
        }
        int64_t time_stamp = oar->video_frame->pts;
    
        int64_t diff = 0;
        if(oar->metadata->has_audio){
            diff = time_stamp - (oar->audio_clock->pts + oar->audio_player_ctx->get_delta_time(oar->audio_player_ctx));
        }else{
            diff = time_stamp - oar_clock_get(oar->video_clock);
        }
        _LOGD("time_stamp:%lld, clock:%lld, diff:%lld",time_stamp , oar_clock_get(oar->video_clock), diff);
        oar_model *model = oar->video_render_ctx->model;
    
        // diff >= 33ms if draw_mode == wait_frame return -1
        //              if draw_mode == fixed_frequency draw previous frame ,return 0
        // diff > 0 && diff < 33ms  sleep(diff) draw return 0
        // diff <= 0  draw return 0
        if (diff >= WAIT_FRAME_SLEEP_US) {
            if (oar->video_render_ctx->draw_mode == wait_frame) {
                return -1;
            } else {
                draw_now(oar->video_render_ctx);
                return 0;
            }
        } else {
            // if diff > WAIT_FRAME_SLEEP_US   then  use previous frame
            // else  use current frame   and  release frame
    //        LOGI("start draw...");
            pthread_mutex_lock(oar->video_render_ctx->lock);
            model->update_frame(model, oar->video_frame);
            pthread_mutex_unlock(oar->video_render_ctx->lock);
            oar_player_release_video_frame(oar, oar->video_frame);
    
            JNIEnv * jniEnv = oar->video_render_ctx->jniEnv;
            (*jniEnv)->CallStaticVoidMethod(jniEnv, oar->jc->SurfaceTextureBridge, oar->jc->texture_updateTexImage);
            jfloatArray texture_matrix_array = (*jniEnv)->CallStaticObjectMethod(jniEnv, oar->jc->SurfaceTextureBridge, oar->jc->texture_getTransformMatrix);
            (*jniEnv)->GetFloatArrayRegion(jniEnv, texture_matrix_array, 0, 16, model->texture_matrix);
            (*jniEnv)->DeleteLocalRef(jniEnv, texture_matrix_array);
    
            if (diff > 0) usleep((useconds_t) diff);
            draw_now(oar->video_render_ctx);
            oar_clock_set(oar->video_clock, time_stamp);
            return 0;
        }
    }
    

    9. 总结

    本文基于Android端的RTMP播放器实现过程,介绍了RTMP推拉流库、Android MediaCodec Java层与C层接口、OpenSL ES接口、OpenGL ES接口、EGL接口、以及音视频相关知识

    推荐阅读:

    Android 音视频开发核心知识点笔记整合

    展开全文
  • RTMP播放器(支持点播与直播)

    热门讨论 2013-08-14 14:54:34
    3. 这个播放器是支持rtmp点播和rtmp直播的; 4. 当然,它也支持纯http协议文件播放,写法如下: --服务器地址必须删去或注释掉------------------------------ //so.addVariable("JcScpServer","rtmp://www.你的...
  • 低延时极简RTMP播放器

    千次阅读 热门讨论 2020-03-16 23:25:08
    近期将项目上RTMP播放相关功能进行打包整理,实现了一款低延时的极简接口RTMP播放器(Windows版和Android版)。市面上的RTMP播放器较多,有开源的ijkplayer及其衍生品,也有收费的功能繁多的播放器,适合自己的才是...

    RtmpPlaySdk简介

    近期将项目上RTMP播放相关功能进行打包整理,实现了一款低延时的极简接口RTMP播放器(Windows版和Android版)。市面上的RTMP播放器较多,有开源的ijkplayer及其衍生品,也有收费的功能繁多的播放器,适合自己的才是最好的,其中Windows版播放器的特性如下:

    1. 支持Rtmp掉线自动重连。
    2. 支持非阻塞Rtmp连接,外层可随时中断。
    3. 支持多实例
    4. 支持任意AAC采样率、声道数,内部自动resample,支持音量调节。
    5. 支持H264+AAC组合Rtmp流,支持同步TS\MP4文件录制。
    6. 支持外层可设置的Jitter Buff延时,设置为0时为极速模式,配合低延时推送端最小延时仅500ms。
    7. 仅六个接口,调用简洁,用户只需传入播放器窗口句柄即可。
    8. 整个系统仅由一个DLL组成,占用空间小。性能强劲,单路720P 30fps在i5 CPU上占用率5%以内。稳定性高经过了长时间项目考验。
    9. C++开发,支持C、C++、C#

     

    RtmpPlaySdk  C API

    /***
    * 环境初始化,系统只需调用一次
    * @param: outputPath:日志文件输出的目录,若目录不存在将自动创建
    * @param: outputLevel:日志输出的级别,只有等于或者高于该级别的日志输出到文件
    * @return: 
    */

    void  RtmpPlayer_Enviroment_Init(const char * outputPath,  LOG_OUTPUT_LEVEL outputLevel);

    /***
    * 环境反初始化,系统只需调用一次
    * @return: 
    */

    void  RtmpPlayer_Enviroment_Free();

    /***
    * 创建RtmpPlayer
    * @return: 返回模块指针,为NULL则失败
    */

    void*  RtmpPlayer_Create();

    /***
    * 销毁RtmpPlayer,注意:【涉及到资源销毁,使用者应该做好本接口与其他接口的互斥保护】
    * @param pRtmpPlayer: 模块指针
    * @return: 
    */

    void  RtmpPlayer_Delete(void* pRtmpPlayer);

    /***
    * 开始拉流Rtmp并播放
    * @param pRtmpPlayer: 模块指针
    * @param strRtmpPlayUrl: Rtmp地址
    * @param unJitterBuffDelay: 内部缓存时间,缓存时间越大延时越大、流畅性越好。反之延时越小,流畅性越差。范围[0, 2000],单位毫秒
    * @param pDisplayHandle: 渲染输出的窗口句柄
    * @return: TURE成功,FALSE失败
    */

    BOOL  RtmpPlayer_Start(void* pRtmpPlayer, char *strRtmpPlayUrl, UINT unJitterBuffDelay, void* pDisplayHandle);


    /***
    * 停止拉流Rtmp播放
    * @param pRtmpPlayer: 模块指针
    * @return: 
    */

    void  RtmpPlayer_Stop(void* pRtmpPlayer);


    /***
    * 获取RTMP连接状态
    * @param pRtmpPlayer: 模块指针
    * @return: RTMP连接状态
    */

    RtmpPlay_Status  RtmpPlayer_GetRtmpStatus(void* pRtmpPlayer);

     

    演示DEMO

    下载地址:

    https://github.com/waterfoxfox/RtmpPlayer

    展开全文
  • c#写的一个flash播放器页面,目前支持rtmp格式的文件进行播放。
  • 一款低延时的极简接口RTMP播放器(Windows版和Android版)。其中Windows版播放器的特性如下: 1、支持Rtmp掉线自动重连。 2、支持非阻塞Rtmp连接,外层可随时中断。 3、支持多实例 4、支持任意AAC采样率、声道数,...
  • ffmpeg播放器C#库封装,RTMP直播播放器播放器支持gpu硬解
  • Android Rtmp播放器,基于MediaCodec与srs-librtmp,不依赖ffmpeg oarplayer(only android rtmp player)是一款简单到毫无特色的纯rtmp播放器, 不依赖ffmpeg,仅依赖srs-librtmp第三方库,体积小,可调整性强. 功能介绍 未...
  • ubuntu下 利用qt做的rtsp /rtmp播放器 支持rtmp h265 由于集成了ffmpeg静态库 所以 体积比较大
  • rtmp-player:rtmp播放器

    2021-05-17 08:20:29
    rtmp播放器 rtmp播放器
  • RTMP 视频流播放器

    2020-07-08 15:33:18
    此VLC播放器组件可以在不安装VLC播放器程序的前提下在JFrame 中内嵌VLC播放器,实现RTMP数据的拉取解析播功能。
  • 好多开发者在做Windows平台...Windows平台RTMP播放器、RTSP播放器C++ demo Windows平台C++的demo,以录像过程为例,动态在左上角显示个闪动的图标+当前时间,具体效果如下: 核心代码 std::shared_ptr<nt_arg

    好多开发者在做Windows平台特别是单屏多画面显示时,希望像监控摄像机一样,可以在播放画面添加OSD台标,以实现字符叠加效果,大多开发者可很轻松的实现以上效果,针对此,本文以大牛直播SDK (Github)的Windows平台demo为例,简单介绍下具体实现:

    Windows平台RTMP播放器、RTSP播放器C++ demo

    Windows平台C++的demo,以录像过程为例,动态在左上角显示个闪动的图标+当前时间,具体效果如下:

    核心代码

    std::shared_ptr<nt_argb_image_logo> CSmartPlayerDlg::MakeLogo()
    {
    	std::shared_ptr<nt_argb_image_logo> logo_image;
    
    	if (!is_init_gdi_plus_ok_)
    		return logo_image;
    
    	if (!recoder_image_)
    	{
    		static bool is_load_image_failed = false;
    
    		if (!is_load_image_failed)
    		{
    			recoder_image_.reset(Gdiplus::Image::FromFile(_T("red_circle.png")));
    
    			if (recoder_image_ && Gdiplus::Ok != recoder_image_->GetLastStatus())
    			{
    				is_load_image_failed = true;
    				recoder_image_.reset();
    			}
    		}
    	}
    
    	is_has_recoder_image_ = !is_has_recoder_image_;
    
    	if (!recoder_image_)
    	{
    		is_has_recoder_image_ = false;
    	}
    
    	if (m_hWnd == nullptr || !::IsWindow(m_hWnd))
    		return logo_image;
    
    	if (cur_logo_font_name_.empty())
    	{
    		cur_logo_font_name_ = FindLogoFontName();
    	}
    
    	if (cur_logo_font_name_.empty())
    	{
    		return logo_image;
    	}
    
    	Gdiplus::FontFamily font_family(cur_logo_font_name_.c_str());
    	if (!font_family.IsAvailable())
    	{
    		return logo_image;
    	}
    
    	Gdiplus::Font font(&font_family, 10, Gdiplus::FontStyleBold, Gdiplus::Unit::UnitPoint);
    	if (!font.IsAvailable())
    	{
    		return logo_image;
    	}
    
    	// 白色
    	Gdiplus::SolidBrush solid_brush(Gdiplus::Color(255, 255, 255));
    	Gdiplus::Graphics  graphics(m_hWnd);
    
    	if (Gdiplus::Ok != graphics.GetLastStatus())
    	{
    		return logo_image;
    	}
    
    	int recoder_image_w = 18;
    	int recoder_image_h = 18;
    
    	if (recoder_image_)
    	{
    		recoder_image_w = recoder_image_->GetWidth();
    		recoder_image_h = recoder_image_->GetHeight();
    	}
    
    	auto image_w = recoder_image_w + 2 + 5;
    	auto image_h = recoder_image_h + 5 + 5;
    
    	graphics.SetTextRenderingHint(Gdiplus::TextRenderingHint::TextRenderingHintClearTypeGridFit);
    
    	auto cur_time_str = MakeCurTimerStr();
    
    	Gdiplus::RectF bounding_box(0, 0, 0, 0);
    	graphics.MeasureString(cur_time_str.c_str(), -1, &font, Gdiplus::PointF(0, 0), &bounding_box);
    
    	Gdiplus::SizeF text_size(0, 0);
    	bounding_box.GetSize(&text_size);
    
    	image_w += (int)text_size.Width;
    	image_h = image_h > ((int)text_size.Height) ? image_h : ((int)text_size.Height);
    
    	image_w += 2;
    	image_h += 2;
    
    	image_w = ByteAlign(image_w, 4);
    	image_h = ByteAlign(image_h, 4);
    
    	Gdiplus::Bitmap   bitmap(image_w, image_h, PixelFormat32bppARGB);
    
    	if (Gdiplus::Ok != bitmap.GetLastStatus())
    	{
    		return logo_image;
    	}
    
    	Gdiplus::Graphics g(&bitmap);
    
    	if (Gdiplus::Ok != g.GetLastStatus())
    	{
    		return logo_image;
    	}
    
    	int r_left = 2;
    	int r_top = (image_h / 2) - (recoder_image_h / 2);
    	r_top -= 1;
    
    	if (is_has_recoder_image_)
    	{
    		g.DrawImage(recoder_image_.get(), r_left, r_top);
    	}
    
    	r_left += recoder_image_w;
    	r_left += 5;
    
    	r_top = (image_h / 2) - (text_size.Height / 2);
    
    	g.DrawString(cur_time_str.c_str(), -1, &font, Gdiplus::PointF(r_left, r_top), &solid_brush);
    
    	Gdiplus::BitmapData locked_bitmapData;
    
    	if (Gdiplus::Ok == bitmap.LockBits(nullptr, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &locked_bitmapData))
    	{
    		auto buffer_size = locked_bitmapData.Stride * locked_bitmapData.Height;
    
    		std::unique_ptr<NT_BYTE[]> buffer(new NT_BYTE[buffer_size]);
    
    		if (buffer)
    		{
    			logo_image = std::make_shared<nt_argb_image_logo>(locked_bitmapData.Width, locked_bitmapData.Height);
    			logo_image->stride_ = locked_bitmapData.Stride;
    
    			memcpy(buffer.get(), locked_bitmapData.Scan0, buffer_size);
    
    			logo_image->data_.swap(buffer);
    		}
    
    		bitmap.UnlockBits(&locked_bitmapData);
    	}
    
    	return logo_image;
    }

    Windows平台RTMP播放器、RTSP播放器C# demo

    Windows平台C#的demo,添加了“设置台标”选择框,在player窗口左上角显示“叠加字符展示”,具体内容、坐标可自定义,具体效果如下:

    核心代码

            //设置OSD文本
            private void DrawOSD(string draw_text)
            {
    
                // gdi 绘制的话,文本请自己绘制
                if (is_gdi_render_)
                    return;
    
                if (player_handle_ == IntPtr.Zero)
                    return;
    
                if (draw_text == null || draw_text.Length < 1)
                {
                    NTSmartPlayerSDK.NT_SP_SetRenderARGBLogo(player_handle_, IntPtr.Zero, 0, 0, 0, 0, 0, 0, 0);
                    return;
                }
    
                Graphics graphics = this.CreateGraphics();
    
                SolidBrush solid_brush = new SolidBrush(Color.FromArgb(255, 255, 255));
    
                graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
    
                SizeF text_size = new SizeF();
                text_size = graphics.MeasureString(draw_text, this.Font);
    
                int image_w = (int)text_size.Width + 4;
                int image_h = (int)text_size.Height + 4;
    
                image_w = (int)ByteAlign((UInt32)image_w, 4);
                image_h = (int)ByteAlign((UInt32)image_h, 4);
    
                Bitmap bmp = new Bitmap(image_w, image_h, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
    
                Graphics g = Graphics.FromImage(bmp);
    
                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
    
                float left = image_w / 2 - text_size.Width / 2;
                float top = image_h / 2 - text_size.Height / 2;
    
                g.DrawString(draw_text, this.Font, solid_brush, left, top);
    
                Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    
                System.Drawing.Imaging.BitmapData bmp_data = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
    
                IntPtr ptr = bmp_data.Scan0;
                int strdie = Math.Abs(bmp_data.Stride);
    
                NTSmartPlayerSDK.NT_SP_SetRenderARGBLogo(player_handle_, ptr, strdie, bmp_data.Width,
                    bmp_data.Height, 6, 6, bmp_data.Width, bmp_data.Height);
    
                // Unlock the bits.
                bmp.UnlockBits(bmp_data);
            }
        }

    注意,如果GDI模式下,我们数据回调到上层绘制的,这样加起来更简单:

                if (btn_check_add_osd.Checked)
                {
                    string draw_text = "叠加字符展示";
    
                    Graphics graphics = this.CreateGraphics();
    
                    SolidBrush solid_brush = new SolidBrush(Color.FromArgb(255, 255, 255));
    
                    graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
    
                    g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
    
                    float left = playWnd.Left + 4;
                    float top = playWnd.Top + 4;
    
                    g.DrawString(draw_text, this.Font, solid_brush, left, top);
                }

    感兴趣的开发者可自行尝试。

    展开全文
  • videojsplayer网页m3u8_flv_mp4_rtmp播放器,自动判断还是手机 ,兼容很强
  • 工作流程功能特点超低延迟的RTMP播放器;超强的设备兼容性和可定制性;完美支持多窗口多实例播放;支持播放端Buffer实时设置,成熟的低延迟追帧技术;秒开播放功能;支持自定义播放布局;编解码,显示,播放原始码全...
  • 我们在做Android平台RTSP或者RTMP播放器开发的时候,需要注意的点非常多,以下,以大牛直播SDK(官方)的接口为例,大概介绍下相关接口设计: 接口设计 1. Open() 接口 Open接口的目的,主要是创建实例,正常返回...
  • 支持http协议下的flv,f4v,mp4,支持rtmp视频流和rtmp视频回放,支持m3u8格式,是你做视频直播,视频点播的理想播放器
  • 引入头文件及初始化#import " RDRtspToRtmp.h"/*** 初始化RDRtspToRtmp,此方法在使用RDRtspToRtmp时在主线程中调
  • 视频共享播放器 rtmp播放器安卓百度地图
  • 支持rtmp播放器

    2018-05-08 08:23:13
    支持rtmp 地址的播放器,无广告很好用的播放器,短小精悍 菜单——打开——打开连接 粘贴rtmp视频源地址就可以播放了

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,905
精华内容 4,762
关键字:

rtmp播放器