Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android:控件Spinner实现下拉列表

Android:控件Spinner实现下拉列表

作者头像
圆号本昊
发布于 2021-09-24 04:27:21
发布于 2021-09-24 04:27:21
1.7K080
代码可运行
举报
文章被收录于专栏:github@hornhuanggithub@hornhuang
运行总次数:80
代码可运行

​引言

在本文中,我们将介绍如何基于大牛直播SDK构建一个功能强大的RTSP|RTMP播放器,该播放器利用自定义SDK解码视频、处理RGB帧,并将其推送到RTMP流中进行直播。这个解决方案非常适合需要在实时视频流中集成视觉算法的场景,在处理后将数据推送到RTMP服务器。我们将详细探讨播放器的架构、回调处理以及图像帧的操作过程。

核心组件概述

先看视频演示,左侧是Windows平台轻量级RTSP服务,采集毫秒计数器,然后编码打包,对外提供RTSP拉流的URL,右上角拉取原始的RTSP流,然后回调解码后的RGB|YUV数据,然后点击推送RTMP,实现RGB|YUV数据的二次编码和RTMP推送。可以看到,从原始数据播放回调,到右下角处理后的RTMP流,二次播放,整体延迟在毫秒级,非常低。

  1. RTSP/RTMP播放器架构
    • 该播放器接收RTSP流,将其解码为RGB帧,处理后将这些帧推送到RTMP流进行直播广播。
    • 它利用自定义SDK来处理视频解码、帧处理和流推送。
  2. 关键概念
    • RTSP(实时流协议):该协议用于控制流媒体服务器,广泛应用于实时视频流的传输。
    • RTMP(实时消息协议):RTMP用于将视频数据推送到直播服务器,确保低延迟广播。
    • RGB数据处理:播放器将视频帧解码为RGB格式(32位),然后传递给视觉算法进行处理,最后将处理后的数据推送到RTMP服务器。
代码讲解

进入系统后,先播放RTMP、或RTSP流,然后点RTMP推流,那么会模拟把播放器回调的RGB或YUV数据,投递到RTMP推送模块(右上方播放和转推)、右下方播放RTMP服务器二次处理后的RTMP流。

1. 初始化播放器SDK

CSmartPlayerDlg类负责初始化播放器、设置事件回调并准备视频渲染窗口。SDK初始化通过player_api_对象完成。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/* 
 * SmartPlayDlg.cpp
 * Created by daniusdk.com
 * WeChat: xinsheng120
 */
void CSmartPlayerDlg::OnBnClickedButtonPlay()
{
	if ( player_handle_ == NULL )
		return;

	CString btn_play_str;

	btn_play_.GetWindowTextW(btn_play_str);

	if ( btn_play_str == _T("播放") )
	{
		if ( !is_recording_ )
		{
			if ( !InitCommonSDKParam() )
			{
				AfxMessageBox(_T("设置参数错误!"));
				return;
			}
		}
	
		player_api_.SetVideoSizeCallBack(player_handle_, GetSafeHwnd(), SP_SDKVideoSizeHandle);

		bool is_support_d3d_render = false;
		NT_INT32 in_support_d3d_render = 0;

		if ( NT_ERC_OK == player_api_.IsSupportD3DRender(player_handle_,
			wrapper_render_wnd_.RenderWnd(), &in_support_d3d_render))
		{
			if ( 1 == in_support_d3d_render )
			{
				is_support_d3d_render = true;
			}
		}

		player_api_.SetVideoFrameCallBack(player_handle_, NT_SP_E_VIDEO_FRAME_FORMAT_RGB32,
			this, SM_SDKVideoFrameHandleV2);

		if ( is_support_d3d_render )
		{
			is_gdi_render_ = false;

			// 支持d3d绘制的话,就用D3D绘制
			player_api_.SetRenderWindow(player_handle_, wrapper_render_wnd_.RenderWnd());

			player_api_.SetRenderScaleMode(player_handle_, btn_check_render_scale_mode_.GetCheck() == BST_CHECKED ? 1 : 0);
		}
		else
		{
			is_gdi_render_ = true;

			// 不支持D3D就让播放器吐出数据来,用GDI绘制

			wrapper_render_wnd_.SetRenderScaleMode(btn_check_render_scale_mode_.GetCheck() == BST_CHECKED ? 1 : 0);

			player_api_.SetVideoFrameCallBack(player_handle_, NT_SP_E_VIDEO_FRAME_FORMAT_RGB32,
				GetSafeHwnd(), SM_SDKVideoFrameHandle);
		}

		if ( BST_CHECKED == btn_check_hardware_decoder_.GetCheck() )
		{
			player_api_.SetH264HardwareDecoder(player_handle_, is_support_h264_hardware_decoder_?1:0, 0);
			player_api_.SetH265HardwareDecoder(player_handle_, is_support_h265_hardware_decoder_?1:0, 0);
		}
		else
		{
			player_api_.SetH264HardwareDecoder(player_handle_, 0, 0);
			player_api_.SetH265HardwareDecoder(player_handle_, 0, 0);
		}

		player_api_.SetOnlyDecodeVideoKeyFrame(player_handle_, BST_CHECKED == btn_check_only_decode_video_key_frame_.GetCheck() ? 1 : 0);

		player_api_.SetLowLatencyMode(player_handle_, BST_CHECKED == btn_check_low_latency_.GetCheck() ? 1 : 0);

		player_api_.SetFlipVertical(player_handle_, BST_CHECKED == btn_check_flip_vertical_.GetCheck() ? 1 :0 );

		player_api_.SetFlipHorizontal(player_handle_, BST_CHECKED == btn_check_flip_horizontal_.GetCheck() ? 1 : 0);

		player_api_.SetRotation(player_handle_, rotate_degrees_);

		player_api_.SetAudioVolume(player_handle_, slider_audio_volume_.GetPos());

		player_api_.SetUserDataCallBack(player_handle_, GetSafeHwnd(), NT_SP_SDKUserDataHandle);


		if (NT_ERC_OK != player_api_.StartPlay(player_handle_))
		{
			AfxMessageBox(_T("播放器失败!"));
			return;
		}

		btn_play_.SetWindowTextW(_T("停止"));
		is_playing_ = true;
	}
	else
	{
		StopPlayback();
	}
}


void CSmartPlayerDlg::StopPlayback()
{
	if ( player_handle_ == NULL )
		return;

	is_gdi_render_ = false;

	btn_full_screen_.EnableWindow(FALSE);
	wrapper_render_wnd_.ClearVideoSize();
	wrapper_render_wnd_.SetPlayerHandle(NULL);

	width_ = 0; 
	height_ = 0;

	player_api_.StopPlay(player_handle_);

	wrapper_render_wnd_.CleanRender();

	btn_play_.SetWindowTextW(_T("播放"));
	is_playing_ = false;

	if (!is_recording_)
	{
		SetWindowText(base_title_);
		edit_duration_.SetWindowText(_T(""));
		edit_playback_pos_.SetWindowText(_T(""));
		btn_pause_.SetWindowText(_T("暂停"));

		edit_player_msg_.SetWindowText(_T(""));
	}
}

上述代码片段初始化了SmartPlayer SDK,这是解码RTSP流、处理视频和音频播放所必需的。

2. 设置事件回调

播放器SDK提供了多个事件回调函数,这些回调函数会在特定事件发生时触发,比如接收到新的视频帧或遇到缓冲事件。这些回调函数在初始化时进行设置。

例如,SetVideoFrameCallBack函数用于定义在接收到新的视频帧时应该执行什么操作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
player_api_.SetVideoFrameCallBack(handle, NT_SP_E_VIDEO_FRAME_FORMAT_RGB32, this, &CSmartPlayerDlg::OnVideoFrameHandle);

OnVideoFrameHandle函数会处理每个RGB帧,然后将其推送到RTMP流。

3. 处理视频帧回调

OnVideoFrameHandle函数中,我们通过首先检查帧的格式,然后将其数据复制到nt_rgb32_image结构体中来处理RGB帧:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void CSmartPlayerDlg::OnVideoFrameHandle(NT_HANDLE handle, NT_UINT32 status,
	const NT_SP_VideoFrame* frame)
{
	if (nullptr == frame)
		return;

	std::unique_lock<std::recursive_mutex> lock(push_handle_mutex_);

	if (!is_pushing_)
		return;

	if (GetPushHandle() == nullptr)
		return;

	//NT_UINT64 ts = frame->timestamp_;
	//std::ostringstream ss;
	//ss << "OnVideoFrameHandle, ts: " << ts << "\r\n";
	//OutputDebugStringA(ss.str().c_str());

	NT_PB_Image image;
	memset(&image, 0, sizeof(image));

	image.width_ = frame->width_;
	image.height_ = frame->height_;

	// timestamp_ 目前不使用
	//image.timestamp_ = frame->timestamp_;

	if (NT_SP_E_VIDEO_FRAME_FORMAT_RGB32 == frame->format_)
	{
		image.format_ = NT_PB_E_IMAGE_FORMAT_RGB32;

		image.plane_[0] = frame->plane0_;
		image.stride_[0] = frame->stride0_;
		image.plane_size_[0] = frame->stride0_ * frame->height_;

	}
	else if (NT_SP_E_VIDEO_FRAME_FROMAT_I420 == frame->format_)
	{
		image.format_ = NT_PB_E_IMAGE_FORMAT_I420;

		image.plane_[0] = frame->plane0_;
		image.stride_[0] = frame->stride0_;
		image.plane_size_[0] = frame->stride0_ * frame->height_;

		image.plane_[1] = frame->plane1_;
		image.stride_[1] = frame->stride1_;
		image.plane_size_[1] = frame->stride1_ * ((frame->height_ + 1) / 2);

		image.plane_[2] = frame->plane2_;
		image.stride_[2] = frame->stride2_;
		image.plane_size_[2] = frame->stride2_ * ((frame->height_ + 1) / 2);
	}
	else
	{
		return;
	}

	int index_ = 0;

	push_api_.PostLayerImage(push_handle_, 0, index_, &image, 0, NULL);
}

该函数将帧数据转换为图像对象,可以进行后续处理或传递给可视算法,通过PostLayerImage()接口投递到RTMP推送模块。

4. 推送到RTMP

一旦RGB帧处理完成,我们需要将视频数据推送到RTMP服务器。通过使用Smart Publisher SDK的推送功能,我们实现了这一点:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/* SmartPlayerDlg.cpp
 * Created by daniusdk.com
 * WeChat: xinsheng120
 */
void CSmartPlayerDlg::OnBnClickedButtonPush()
{
	// TODO: Add your control notification handler code here

	CString btn_push_str;

	btn_push_.GetWindowTextW(btn_push_str);

	if (btn_push_str == _T("推送RTMP"))
	{
		StartPush("rtmp://192.168.1.7:1935/hls/stream666");
	}
	else
	{
		StopPush();
	}
}

bool CSmartPlayerDlg::StartPush(const std::string& url)
{
	if (is_pushing_)
		return false;

	if (url.empty())
		return false;

	if (!OpenPushHandle())
		return false;

	auto push_handle = GetPushHandle();
	ASSERT(push_handle != nullptr);

	if (publisher_handle_count_ < 1)
	{
		SetCommonOptionToPublisherSDK();
	}

	if (NT_ERC_OK != push_api_.SetURL(push_handle, url.c_str(), NULL))
	{
		if (0 == publisher_handle_count_)
		{
			push_api_.Close(push_handle);
			SetPushHandle(nullptr);
		}

		return false;
	}

	if (NT_ERC_OK != push_api_.StartPublisher(push_handle, NULL))
	{
		if (0 == publisher_handle_count_)
		{
			push_api_.Close(push_handle);
			SetPushHandle(nullptr);
		}

		return false;
	}

	publisher_handle_count_++;

	btn_push_.SetWindowTextW(_T("停止推送"));
	is_pushing_ = true;

	return true;
}

void CSmartPlayerDlg::StopPush()
{
	if (!is_pushing_)
		return;

	is_pushing_ = false;

	std::unique_lock<std::recursive_mutex> lock(push_handle_mutex_);

	if (nullptr == push_handle_)
		return;

	publisher_handle_count_--;
	push_api_.StopPublisher(push_handle_);

	if (0 == publisher_handle_count_)
	{
		push_api_.Close(push_handle_);
		push_handle_ = nullptr;
	}

	btn_push_.SetWindowTextW(_T("推送RTMP"));
}

PushVideoFrame方法将处理后的视频数据实时推送到RTMP服务器。

5. 处理错误与缓冲

SDK还提供了事件回调来处理错误和缓冲事件。例如,当播放器开始缓冲时,将触发以下回调:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
extern "C" NT_VOID NT_CALLBACK NT_Push_SDKEventHandle(NT_HANDLE handle, NT_PVOID user_data,
	NT_UINT32 event_id,
	NT_INT64  param1,
	NT_INT64  param2,
	NT_UINT64 param3,
	NT_UINT64 param4,
	NT_PCSTR  param5,
	NT_PCSTR  param6,
	NT_PVOID  param7
	)
{
	if (user_data == NULL)
		return;

	HWND hwnd = (HWND)user_data;

	if (NT_PB_E_EVENT_ID_CONNECTING == event_id
		|| NT_PB_E_EVENT_ID_CONNECTION_FAILED == event_id
		|| NT_PB_E_EVENT_ID_CONNECTED == event_id
		|| NT_PB_E_EVENT_ID_DISCONNECTED == event_id)
	{
		if (hwnd != nullptr && ::IsWindow(hwnd))
		{
			auto event_info = new ConnectionEventInfo(handle, event_id, param5);

			::PostMessage(hwnd, WM_USER_PB_SDK_CONNECTION_INFO, (WPARAM)event_info, 0);
		}
	}

	if (NT_PB_E_EVENT_ID_RTSP_URL == event_id)
	{
		if (hwnd != nullptr && ::IsWindow(hwnd))
		{
			if (param5 != nullptr)
			{
				auto url_event_data = new NT_PushRtspURLEventData(handle, param5);

				::PostMessage(hwnd, WM_USER_SDK_PUSH_RTSP_URL_EVENT, (WPARAM)url_event_data, 0);
			}
		}
	}
}

该事件确保应用程序能在网络或流中断时做出相应处理。

结论

构建一个RTSP|RTMP播放器并进行RGB帧处理是实时媒体应用中的一项基本技能。通过使用SmartPlayer SDK,我们能够轻松地集成视频解码、帧处理和流推送在一个平台中。这种解决方案允许我们在视频流中进行自定义的视觉处理,并在处理后将其推送到RTMP服务器。大牛直播SDK提供了必要的构建块,包括帧处理、事件驱动的回调和视频渲染,使得它成为开发专业直播应用程序的强大工具。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018/11/21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Android官方下拉选择控件Spinner
Demo: https://github.com/bige-ye/SpinnerDemo
yechaoa
2022/06/10
1.2K0
Android官方下拉选择控件Spinner
Android 控件:使用下拉列表框--Spinner
---恢复内容开始--- 一、前段代码 <Spinner android:id="@+id/spin" android:paddingTop="10px" android:layout_width="fill_parent" android:layout_height="50sp"/> <Button android:id="@+id/addList" android:layout_width="wrap_content" android:layou
hbbliyong
2018/03/06
2.8K0
android学习笔记----ListView和各种适配器简介
将数据库显示到ListView的小Demo源码地址:https://github.com/liuchenyang0515/ListView_DataBase
砖业洋__
2023/05/06
2.4K0
android学习笔记----ListView和各种适配器简介
Android在PopWindow中使用Spinner的心路历程
最近在开发的项目程序中用到了PopWindow,结果在里面需要加一个点击选择的列表,于是就准备使用Spinner放在PopWindow,期间经历了几个问题,最后都一一解决了,这篇文章就介绍一下Spinner怎么在PopWindow中使用。
Vaccae
2019/07/25
1.8K1
一步步自定义下拉组件spinner
spinner就是下拉选择组件,系统自带的spinner使用起来非常方便,首先定义一个array(strings.xml),如下:
BennuCTech
2021/12/29
1.2K0
一步步自定义下拉组件spinner
android之ArrayAdapter的重写
昨天介绍了ArrayAdapter的使用,今天介绍一下更加实用的一点,对它进行重写,满足自己的个性化设计需要.
全栈程序员站长
2022/07/20
7090
android之ArrayAdapter的重写
跟我学Android之十一 列表和适配器
视频课:https://edu.csdn.net/course/play/7621
张哥编程
2024/12/18
1630
跟我学Android之十一 列表和适配器
跟我学Android之十一 列表和适配器
本章目标 理解 MVC 模式的设计思想 。 了解 AdapterView 的继承关系图 。 掌握掌握使用各类适配器显示列表数据。 掌握列表视图 ListView 的用法。 掌握下拉视图 Spinner 的用法。
张哥编程
2024/12/17
1830
跟我学Android之十一     列表和适配器
Android之控件与布局,结构知识点,基础完结
在Android中我们常常用到很多UI控件,如TextView,EditText,ImageView,Button,ImageButton,ToggleButton,CheckBox,RadioButton等等这些可以自己多用就会了。
达达前端
2019/07/03
1.2K0
Android之控件与布局,结构知识点,基础完结
2014-11-6Android学习------Spinner下拉选择框控件学习
我学习Android都是结合源代码去学习,这样比较直观,非常清楚的看清效果,觉得很好,今天的学习源码是网上找的源码 百度搜就知道很多下载的地方 网上源码的名字叫:activity切换特效.zip我的博客写的比较乱,如果本篇文章没有看懂,
wust小吴
2022/03/07
4720
2014-11-6Android学习------Spinner下拉选择框控件学习
Android开发笔记(三十八)列表类视图
AdapterView顾名思义是适配器视图,Spinner、ListView和GridView都间接继承自AdapterView,这三个视图都存在多个元素并排展示的情况,所以需要引入适配器模式。 适配器视图的特点有: 1、定义了适配器的设置方法setAdapter,以及获取方法getAdapter。适配器用于传入视图展示需要的相关数据。 2、定义了一个数据观察者AdapterDataSetObserver,用于在列表数据发生变化时,可以通过notifyDataSetChanged方法来更新视图。 3、定义了单个元素的点击、长按、选中事件。其中点击方法为setOnItemClickListener,点击监听器为OnItemClickListener;长按方法为setOnItemLongClickListener,长按监听器为OnItemLongClickListener;选中方法为setOnItemSelectedListener,选中监听器为OnItemSelectedListener。
aqi00
2019/01/18
2.6K0
Carson带你学Android:全面解析列表ListView与AdapterView
2. 在MainActivity上定义一个链表,将所要展示的数据以存放在里面 3. 构造ArrayAdapter对象,设置适配器 4. 将LsitView绑定到ArrayAdapter上 如下图:
Carson.Ho
2022/03/24
1.2K0
Carson带你学Android:全面解析列表ListView与AdapterView
Android开发:ListView、AdapterView、RecyclerView全面解析
AdapterView本身是一个抽象类,AdapterView及其子类的继承关系如下图:
Carson.Ho
2019/02/22
3.7K0
Adapter与ListView的简单应用(下)Android应用界面开发
1.继续分析Adapter的常用类 上一篇文章使用了ArrayAdapte制作了一个只由简单的文字组成的ListView,那ArrayAdapter是不是只有简单显示一行文字的功能呢?答案是否定的。
爱因斯坦福
2018/09/10
6790
从0系统学Android--3.5 最常用和最难用的控件---ListView
ListView 是我们在开发中最常使用的控件之一。由于手机屏幕空间比较有限,能够一次性在屏幕上显示的内容不多,ListView 允许用户可以通过手指上下滑动,可以呈现更多的数据。
开发者
2019/12/26
6480
从0系统学Android--3.5 最常用和最难用的控件---ListView
使用ListView自定义布局
ListView这个控件实际上是很难用的,就是因为它很多细节可以优化,效率就是比较重要的一点.下面我们来优化下它的效率:
Dream城堡
2018/12/14
9260
使用ListView自定义布局
自定义ArrayAdapter
ListView用起来还是比较简单的,也是Android应用程序中最重要的一个组件,但其他ListView可以随你所愿,能够完成很多想要的精美列表,而这正是我们接下来要学习的内容。 一、自定义ArrayAdapter 从上期自定义列表项示例知道,每个列表项的图标都一样,如果需要每个列表项的图标根据内容动态表示,Android系统的ArrayAdapter就无能为力了,就只能使用自定义ArrayAdapter来实现啦。 做法就是创建一个ArrayAdapter的子类,重写其getVie
分享达人秀
2018/02/02
1.6K0
自定义ArrayAdapter
相关推荐
Android官方下拉选择控件Spinner
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验