如何实现RTMP或RTSP播放端回调YUV/RGB数据?

今天某乎收到个问题推荐,如何实现RTSP回调YUV数据,用于二次处理?

正好前些年我们做RTSP和RTMP直播播放的时候,实现过相关的需求,本文就以Android为例,大概说说具体实现吧。

先说回调yuv或rgb这块意义吧,不管是RTSP还是RTMP直播播放模块,解码后的yuv/rgb数据,可以实现比如快照(编码保存png或jpeg)、回调给第三方用于比如视频分析、亦或比如回调给Unity,实现Unity平台下的绘制。

为了图文并茂,让大家有个基本的认识,先上张图,demo展示的是本地播放的同时,可把yuv或rgb回上来,供上层做二次处理:

我们把协议栈这块处理,放到JNI下,播放之前,设置回调:

libPlayer.SmartPlayerSetExternalRender(playerHandle, new I420ExternalRender());

I420ExternalRender()具体实现:

/** SmartPlayer.java* SmartPlayer* * Github: https://github.com/daniulive/SmarterStreaming* * Created by DaniuLive on 2015/09/26.*/class I420ExternalRender implements NTExternalRender {// public static final int NT_FRAME_FORMAT_RGBA = 1;// public static final int NT_FRAME_FORMAT_ABGR = 2;// public static final int NT_FRAME_FORMAT_I420 = 3;private int width_ = 0;private int height_ = 0;private int y_row_bytes_ = 0;private int u_row_bytes_ = 0;private int v_row_bytes_ = 0;private ByteBuffer y_buffer_ = null;private ByteBuffer u_buffer_ = null;private ByteBuffer v_buffer_ = null;@Overridepublic int getNTFrameFormat() {Log.i(TAG, "I420ExternalRender::getNTFrameFormat return "+ NT_FRAME_FORMAT_I420);return NT_FRAME_FORMAT_I420;}@Overridepublic void onNTFrameSizeChanged(int width, int height) {width_ = width;height_ = height;y_row_bytes_ = (width_ + 15) & (~15);u_row_bytes_ = ((width_ + 1) / 2 + 15) & (~15);v_row_bytes_ = ((width_ + 1) / 2 + 15) & (~15);y_buffer_ = ByteBuffer.allocateDirect(y_row_bytes_ * height_);u_buffer_ = ByteBuffer.allocateDirect(u_row_bytes_* ((height_ + 1) / 2));v_buffer_ = ByteBuffer.allocateDirect(v_row_bytes_* ((height_ + 1) / 2));Log.i(TAG, "I420ExternalRender::onNTFrameSizeChanged width_="+ width_ + " height_=" + height_ + " y_row_bytes_="+ y_row_bytes_ + " u_row_bytes_=" + u_row_bytes_+ " v_row_bytes_=" + v_row_bytes_);}@Overridepublic ByteBuffer getNTPlaneByteBuffer(int index) {if (index == 0) {return y_buffer_;} else if (index == 1) {return u_buffer_;} else if (index == 2) {return v_buffer_;} else {Log.e(TAG, "I420ExternalRender::getNTPlaneByteBuffer index error:" + index);return null;}}@Overridepublic int getNTPlanePerRowBytes(int index) {if (index == 0) {return y_row_bytes_;} else if (index == 1) {return u_row_bytes_;} else if (index == 2) {return v_row_bytes_;} else {Log.e(TAG, "I420ExternalRender::getNTPlanePerRowBytes index error:" + index);return 0;}}public void onNTRenderFrame(int width, int height, long timestamp){if ( y_buffer_ == null )return;if ( u_buffer_ == null )return;if ( v_buffer_ == null )return;y_buffer_.rewind();u_buffer_.rewind();v_buffer_.rewind();/*if ( !is_saved_image ){is_saved_image = true;int y_len = y_row_bytes_*height_;int u_len = u_row_bytes_*((height_+1)/2);int v_len = v_row_bytes_*((height_+1)/2);int data_len = y_len + (y_row_bytes_*((height_+1)/2));byte[] nv21_data = new byte[data_len];byte[] u_data = new byte[u_len];byte[] v_data = new byte[v_len];y_buffer_.get(nv21_data, 0, y_len);u_buffer_.get(u_data, 0, u_len);v_buffer_.get(v_data, 0, v_len);int[] strides = new int[2];strides[0] = y_row_bytes_;strides[1] = y_row_bytes_;int loop_row_c = ((height_+1)/2);int loop_c = ((width_+1)/2);int dst_row = y_len;int src_v_row = 0;int src_u_row = 0;for ( int i = 0; i < loop_row_c; ++i){int dst_pos = dst_row;for ( int j = 0; j <loop_c; ++j ){nv21_data[dst_pos++] = v_data[src_v_row + j];                   nv21_data[dst_pos++] = u_data[src_u_row + j];}dst_row   += y_row_bytes_;src_v_row += v_row_bytes_;src_u_row += u_row_bytes_;}String imagePath = "/sdcard" + "/" + "testonv21" + ".jpeg";Log.e(TAG, "I420ExternalRender::begin test save iamge++ image_path:" + imagePath);try{File file = new File(imagePath);FileOutputStream image_os = new FileOutputStream(file);   YuvImage image = new YuvImage(nv21_data, ImageFormat.NV21, width_, height_, strides);  image.compressToJpeg(new android.graphics.Rect(0, 0, width_, height_), 50, image_os);  image_os.flush();  image_os.close();}catch(IOException e){e.printStackTrace();}Log.e(TAG, "I420ExternalRender::begin test save iamge--");}*/Log.i(TAG, "I420ExternalRender::onNTRenderFrame w=" + width + " h=" + height + " timestamp=" + timestamp);// copy buffer// test// byte[] test_buffer = new byte[16];// y_buffer_.get(test_buffer);// Log.i(TAG, "I420ExternalRender::onNTRenderFrame y data:" + bytesToHexString(test_buffer));// u_buffer_.get(test_buffer);// Log.i(TAG, "I420ExternalRender::onNTRenderFrame u data:" + bytesToHexString(test_buffer));// v_buffer_.get(test_buffer);// Log.i(TAG, "I420ExternalRender::onNTRenderFrame v data:" + bytesToHexString(test_buffer));}}

为了验证回上来的数据是否正常,我们加了保存jpeg文件的代码。

当然,回调yuv或rgb,可以做的更精细,比如我们windows的RTMP或RTSP播放器,回调数据,可以指定分辨率(比如缩放)和frame类型:

/*设置视频回调, 吐视频数据出来, 可以指定吐出来的视频宽高*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*/NT_UINT32(NT_API *SetVideoFrameCallBackV2)(NT_HANDLE handle,NT_INT32 scale_width, NT_INT32 scale_height,NT_INT32 scale_filter_mode, NT_INT32 frame_format,NT_PVOID call_back_data, SP_SDKVideoFrameCallBack call_back);

相关视频帧图像格式和帧结构:

//定义视频帧图像格式
typedef enum _NT_SP_E_VIDEO_FRAME_FORMAT
{NT_SP_E_VIDEO_FRAME_FORMAT_RGB32 = 1, // 32位的rgb格式, r, g, b各占8, 另外一个字节保留, 内存字节格式为: bb gg rr xx, 主要是和windows位图匹配, 在小端模式下,按DWORD类型操作,最高位是xx, 依次是rr, gg, bbNT_SP_E_VIDEO_FRAME_FORMAT_ARGB  = 2, // 32位的argb格式,内存字节格式是: bb gg rr aa 这种类型,和windows位图匹配NT_SP_E_VIDEO_FRAME_FROMAT_I420  = 3, // YUV420格式, 三个分量保存在三个面上
} NT_SP_E_VIDEO_FRAME_FORMAT;// 定义视频帧结构.
typedef struct _NT_SP_VideoFrame
{NT_INT32  format_;  // 图像格式, 请参考NT_SP_E_VIDEO_FRAME_FORMATNT_INT32  width_;   // 图像宽NT_INT32  height_;  // 图像高NT_UINT64 timestamp_; // 时间戳, 一般是0,不使用, 以ms为单位的// 具体的图像数据, argb和rgb32只用第一个, I420用前三个NT_UINT8* plane0_;NT_UINT8* plane1_;NT_UINT8* plane2_;NT_UINT8* plane3_;// 每一个平面的每一行的字节数,对于argb和rgb32,为了保持和windows位图兼容,必须是width_*4// 对于I420, stride0_ 是y的步长, stride1_ 是u的步长, stride2_ 是v的步长,NT_INT32  stride0_;NT_INT32  stride1_;NT_INT32  stride2_;NT_INT32  stride3_;} NT_SP_VideoFrame;

感兴趣的开发者可以酌情参考,实现自己的业务逻辑。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/552971.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GB28181控制、传输流程和协议接口之注册|注销和技术实现

注册和注销基本要求 SIP客户端、网关、SIP设备、联网系统等 SIP代理(SIP UA)使用IETFRFC3261中定义的方法 15 GB/T28181—2016Register进行注册和注销。 注册和注销时应进行认证&#xff0c;认证方式应支持数字摘要认证方式&#xff0c;高安全级别的宜支持数字证书的认证方式&…

GB28181状态信息报送解读及Android端国标设备接入技术实现

今天主要聊聊GB/T28181状态信息报送这块&#xff0c;先回顾下协议规范相关细节&#xff0c;然后再针对代码实现&#xff0c;做个简单的说明。 状态消息报送基本要求 当源设备(包括网关、SIP设备、SIP客户端或联网系统)发现工作异常时,应立即向本 SIP监控域 的SIP服务器发送状…

GB28181设备接入端如何实现校时?

在探讨这个问题之前&#xff0c;我们先看看GB/T28181-2016官方文档怎么说的&#xff0c;9.10.1章节校时基本要求提到&#xff1a; 联网内设备支持基于SIP方式或 NTP方式的网络校时功能&#xff0c;标准时间为北京时间。 SIP方式校时见本节具体描述&#xff1b;NTP(见IETFRFC2…

如何在Unity下采集音视频实现轻量级RTSP服务(类似于IPC)

好多开发者在做虚拟仿真、VR教育等场景的时候&#xff0c;遇到个问题&#xff0c;想把头显里面的画面在内网环境下低延迟的同步出来&#xff0c;又不想单独部署流媒体服务器。为此&#xff0c;我们在Unity下&#xff0c;添加了轻量级RTSP服务模块&#xff0c;通过头显端启动个轻…

【Datawhale 大模型基础】第十一章 环境影响

第十一章 环境影响 This blog is based on datawhale files and a paper. The initial consideration revolves around the potential positive or negative direct impact on the environment. Other transformative technological advancements, like the metaverse, are li…

Android GB28181接入端实时位置订阅和上报之-如何获取当前经纬度

我们在做Android平台GB28181的时候&#xff0c;其中实时位置(MobilePosition)订阅和上报这块&#xff0c;涉及到实时经纬度的获取&#xff0c;特别是执法记录、车载系统的那个等场景&#xff0c;几乎就是标配。 今天主要是分享一段实时获取位置的代码&#xff1a; /** Camera…

如何实现Android平台GB28181设备对接Camera2数据

技术背景 在写如何实现Android平台GB28181设备对接Camera2数据说明之前&#xff0c;我在前两年的blog就有针对camera2的RTMP直播推送模块做过技术分享&#xff1a; 在Google 推出Android 5.0的时候, Android Camera API 版本升级到了API2(android.hardware.camera2), 之前使用…

Android平台GB28181设备接入端本地SIP端口被占用或屏蔽怎么办?

好多开发者或厂商&#xff0c;对Android平台GB28181接入模块的定位&#xff0c;大多是IPC国标流程打通模拟&#xff0c;基于这个目的&#xff0c;很难按照标准SPEC规范实现Android平台GB28181设备接入&#xff0c;我们在跟第三方国标平台厂商对接时发现&#xff0c;部分公司&am…

Android平台GB28181设备接入端如何实现本地录像?

实现Android平台GB28181设备接入的时候&#xff0c;有个功能点不可避免&#xff0c;那就是本地录像&#xff0c;实际上&#xff0c;在实现GB28181设备接入模块之前&#xff0c;我们前些年做RTMP推送和轻量级RTSP服务的时候&#xff0c;早已经实现了本地录像功能。 本地录像功能…

国网B接口注册(REGISTER)接口描述和消息示例

技术背景 电网视频监控系统是智能电网的一个重要组成部分&#xff0c;广泛应用于电网的建设、生产、运行、经营等方面。由于视频监控系统在不同的建设时期选用了不同的技术和不同厂家的产品&#xff0c;导致了标准不统一、技术路线不一致。目前国家电网公司智能电网建设&#…

国网B接口资源上报(Push_Resourse)接口描述和消息示例

上篇blog&#xff0c;梳理了国网B接口的REGISTER接口描述和消息示例&#xff0c;前端系统加电启动并初次注册成功后&#xff0c;向平台上报前端系统的设备资源信息&#xff08;包括&#xff1a;视频服务器、DVR/DVS、摄像机、告警设备、环境量采集设备等模拟或数字信号采集设备…

Android平台GB28181设备接入端语音广播如何实现实时音量调节

Android平台GB28181设备接入&#xff0c;语音广播功能非常重要&#xff0c;本文要介绍的&#xff0c;不是语音广播的流程&#xff0c;语音广播流程&#xff0c;之前的blog也有非常详细的分享&#xff0c;感兴趣的可以参考官方规范书的交互流程&#xff1a; 语音广播这块&#x…

GB28181基于TCP协议的视音频媒体传输探究及实现

我们先看看官方规范针对TCP协议的视音频传输描述&#xff1a; 实时视频点播、历史视频回放与下载的 TCP媒体传输应支持基于RTP封装的视音频PS流&#xff0c;封装格式参照IETFRFC4571。 流媒体服务器宜同时支持作为TCP媒体流传输服务端和客户端。默认情况下&#xff0c;前端设…

Android平台GB28181接入端如何对接UVC摄像头?

我们在对接Android平台GB28181接入的时候&#xff0c;有公司提出这样的需求&#xff0c;除了采集执法记录仪摄像头自带的数据外&#xff0c;还想通过执法记录仪采集外接UVC摄像头。 实际上&#xff0c;这块对我们来说有点炒冷饭了&#xff0c;不算新的诉求。​大牛直播SDK​在2…

Android平台实现系统内录(捕获播放的音频)并推送RTMP服务技术方案探究

几年来&#xff0c;我们在做无纸化同屏或在线教育相关场景的时候&#xff0c;总是被一件事情困扰&#xff1a;如何实现Android平台的系统内录&#xff0c;并推送到其他播放端&#xff0c;常用的场景比如做无纸化会议或教育的时候&#xff0c;主讲人或老师需要放一个视频&#x…

Android平台GB28181设备接入端PTZ对接详解

PTZCmd实现背景 上一篇blog“Android平台GB28181设备接入模块之球机/云台控制探究”谈到&#xff0c;Android平台做国标GB28181设备接入端的时候&#xff0c;PTZ控制要不要处理&#xff1f;如果处理&#xff0c;难度大不大&#xff1f; 首先说要不要处理&#xff1a;如果只是…

Android平台GB28181设备接入模块摄像头采集方向不对怎么办?

背景 我们在做Android平台GB28181设备接入模块的时候&#xff0c;有开发者提到这样的诉求&#xff1a;他们的智能头盔、执法记录仪等设备&#xff0c;采集到的图像&#xff0c;是旋转了90、180甚至270的&#xff0c;设备本身无法针对图像做翻转或者旋转操作&#xff0c;问我们…

Android平台GB28181设备接入模块分辨率发生变化怎么办?

技术背景 我们在做Android平台gb28181设备接入模块的时候&#xff0c;遇到这样的情况&#xff0c;比如横竖屏分辨率不锁定&#xff0c;采集摄像头的时候&#xff0c;可以实现&#xff0c;横屏状态采集横屏&#xff0c;竖屏状态采集竖屏&#xff0c;简单来说&#xff0c;横屏状…

RTSP、RTMP播放器拉到的视频图像角度不对怎么办?

我们在做RTSP、RTMP播放器的时候&#xff0c;遇到这样的诉求&#xff1a;特别是RTSP&#xff0c;有些摄像头安装可能倒置或者旋转了90亦或270&#xff0c;拉取到图像&#xff0c;势必需要对视频图像做一定的处理&#xff0c;确保显示正常。 为此&#xff0c;我们提供了以下接口…

Android平台GB28181设备接入端如何调节实时音量?

我们在对接Android平台GB28181设备接入端的时候&#xff0c;有开发者提出这样的疑惑&#xff0c;如何调整设备接入端的实时音量&#xff1f; 实际上&#xff0c;这块我们前几年在做RTMP直播推送模块的时候&#xff0c;已经发布了相关的接口&#xff0c;这里再回顾下&#xff1…