Android平台RTMP推送|轻量级RTSP服务如何实现麦克风|扬声器声音采集切换

news2024/12/26 21:28:04

技术背景

我们在做Android端同屏的时候,开发者希望可以高版本的Android系统上,在设备支持的前提下,可以采集到扬声器输出的audio,并支持和麦克风采集的audio相互切换,实现无纸化|智慧教室同屏不同audio模式的输出。Android系统出于安全和隐私的考虑,默认并不允许应用程序直接访问系统级别的音频输出。

从Android 10(API级别29)开始,Android引入了媒体投影API(MediaProjection),允许应用捕获屏幕内容以及音频。但是,直接捕获扬声器输出的音频并不是通过MediaProjection API直接实现的,而是通常与屏幕录制功能一起提供。

  • 启用屏幕录制权限:应用需要请求RECORD_AUDIOCAPTURE_AUDIO_OUTPUT权限,以及CAPTURE_VIDEO_OUTPUTCAPTURE_SECURE_VIDEO_OUTPUT(如果捕获安全内容)。
  • 使用MediaProjectionManager:创建一个MediaProjection会话,并引导用户通过系统UI授权屏幕录制。
  • 捕获音频:在录制屏幕时,音频也会同时被捕获。但是,这通常只适用于用户当前正在操作的应用的音频输出,而不是整个系统的音频。

技术实现

本文以大牛直播SDK的SmartServicePublisherV2这个demo为例,介绍下相关的技术实现。

如果需要支持音频播放采集和麦克风采集,可以想把这两个选项打开,然后,通过右侧下拉框,推送过程中,实时切换数据源。

采集麦克风实现逻辑:

/*
 * NTStreamMediaProjectionEngineImpl.java
 * Author: daniusdk.com
 * WeChat: xinsheng120
 *
 * Copyright © 2014~2024 DaniuSDK. All rights reserved.
 */
@Override
public boolean start_audio_record(int sample_rate, int channels) {
	if (audio_record_.get() != null) {
		Log.e(TAG, "start_audio_record audio_record already exists");
		return false;
	}

	if (!check_record_audio_permission()) {
		Log.e(TAG, "start_audio_record RECORD_AUDIO permission is missing");
		return false;
	}

	NTAudioRecordV2 audio_record = new NTAudioRecordV2(get_application_context());

	// audio_record.IsMicSource(true);		//如音频采集声音过小,建议开启
	// audio_record.IsRemoteSubmixSource(true); // Anrdoid 10以下, 有些设备可能能用

   if (!audio_record.Start(sample_rate, channels)) {
	   Log.e(TAG, "start_audio_record start failed");
	   return false;
   }

	if (audio_record_callback_ != null)
		audio_record.AddCallback(audio_record_callback_);

	NTAudioRecordV2 old = audio_record_.getAndSet(audio_record);
	if (old != null) {
		if (audio_record_callback_ != null)
			old.RemoveCallback(audio_record_callback_);

		old.Stop();
	}

	Log.i(TAG, "start_audio_record ok, sample_rate:" + sample_rate + ", channels:" + channels);
	return true;
}

@Override
public boolean is_audio_record_running() {
	return audio_record_.get() != null;
}

@Override
public void stop_audio_record() {
	NTAudioRecordV2 old = audio_record_.getAndSet(null);
	if (old != null) {
		if (audio_record_callback_ != null)
			old.RemoveCallback(audio_record_callback_);

		old.Stop();
		Log.i(TAG, "call audio_record_.Stop.");
	}
}

采集音频播放声音(扬声器)实现逻辑:

/*
 * NTStreamMediaProjectionEngineImpl.java
 * Author: daniusdk.com
 * WeChat: xinsheng120
 *
 * Copyright © 2014~2024 DaniuSDK. All rights reserved.
 */
@Override
public boolean start_audio_playback_capture(int sample_rate, int channels) {
	if (Build.VERSION.SDK_INT < 29) {
		Log.e(TAG, "start_audio_playback_capture Device SDK_INT:" + Build.VERSION.SDK_INT +" < 29(Android 10)");
		return false;
	}

	if (audio_playback_capture_.get() != null) {
		Log.e(TAG, "start_audio_playback_capture capture already exists");
		return false;
	}

	if (!check_record_audio_permission()) {
		Log.e(TAG, "start_audio_playback_capture RECORD_AUDIO permission is missing.");
		return false;
	}

	MediaProjection media_projection = get_media_projection();
	if (null == media_projection) {
		Log.e(TAG, "start_audio_playback_capture media_projection is null");
		return false;
	}

	NTAudioPlaybackCapture capture = new NTAudioPlaybackCapture();
	if (!capture.start(get_application_context(), media_projection, sample_rate, channels)) {
		capture.close();
		Log.e(TAG, "start_audio_playback_capture start failed");
		return false;
	}

	if (audio_playback_capture_callback_ != null)
		capture.register_callback(audio_playback_capture_callback_);

	NTAudioPlaybackCapture old = audio_playback_capture_.getAndSet(capture);
	if (old != null)
		old.close();

	Log.i(TAG, "start_audio_playback_capture ok, sample_rate:" + sample_rate + ", channels:" + channels);
	return true;
}

@Override
public boolean is_audio_playback_capture_running() {
	return audio_playback_capture_.get() != null;
}

@Override
public void stop_audio_playback_capture() {
	NTAudioPlaybackCapture old = audio_playback_capture_.getAndSet(null);
	if (old != null) {
		old.close();
		Log.i(TAG, "stop_audio_playback_capture capture.close.");
	}
}

启动RTMP推送或轻量级RTSP服务过程中,切换采集扬声器或者麦克风:

@Override
public boolean set_audio_output_type(int type) {
	if (type < 0 || type > 2) {
		Log.e(TAG, "set_audio_output_type type:" + type + " error");
		return false;
	}

	Runnable r = new Runnable() {
		int type_;

		@Override
		public void run() {
			audio_output_type_ = this.type_;
			Log.i(TAG, "set_audio_output_type value:" + this.type_);

			if (stream_publisher_.is_publishing())
				switch_audio_output_type(audio_output_type_);
		}

		Runnable set(int type) {
			this.type_ = type;
			return this;
		}
	}.set(type);

	post_or_execute(r);
	return true;
}

播放效果如下(Android采集屏幕和麦克风|扬声器audio,然后推送到RTMP服务和轻量级RTSP服务),扬声器audio采集,特别是视频播放模式下,比如无纸化同屏过程中,需要放个宣传片,或者一些视频材料,非常方便:

总结

Android平台扬声器播放声音的采集,在无纸化同屏等场景下,意义很大,早期低版本的Android设备,是没法直接采集扬声器audio的(从Android 10开始支持),所以,如果需要采集扬声器audio,需要先做系统版本判断,添加相应的权限。如果需要实时切换扬声器或麦克风声音,可以参考上述实现逻辑,以上是大概的流程,感兴趣的开发者,可以单独跟我沟通讨论。

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

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

相关文章

Scrapy框架妙用:如何添加代理IP让数据采集更顺畅

什么是Scrapy框架&#xff1f; Scrapy框架是Python编写的一个强大、快速的网络爬虫和网页抓取框架。它能帮助开发者轻松地从网站上提取数据&#xff0c;并进行数据处理和存储。Scrapy的设计灵活且功能强大&#xff0c;适用于各种数据采集任务。 为何需要在Scrapy中添加代理IP…

【fastapi框架:jinja2模板、ORM操作、中间件与CORS】

## 五、jinja2模板要了解jinja2&#xff0c;那么需要先理解模板的概念。模板在Python的web开发中⼴泛使⽤&#xff0c;它能够有效的将业务逻辑和页⾯逻辑分开&#xff0c;使代码可读性增强、并且更加容易理解和维护。 模板简单来说就是⼀个其中包涵占位变量表⽰动态的部分的⽂件…

百元蓝牙耳机哪个品牌性价比最高?四大高质量耳机爆肝推荐

蓝牙耳机的日常使用非常简便&#xff0c;而且充电也方便。但如今市场中的蓝牙耳机有的质量与价格不匹配&#xff0c;耳机的使用体验不佳&#xff0c;那百元蓝牙耳机哪个品牌性价比最高&#xff1f;关于这一点&#xff0c;作为资深的蓝牙耳机测评师&#xff0c;下面就给大家带来…

2024年下半年软考备考建议

备考建议 第一轮(建议5-10天) 1、了解考试的基本情况&#xff0c;确定是自学还是报班&#xff0c;准备好备考工具; 2、过一下官方教材蓝皮书&#xff0c;借助思维导图对考试科目知识体系结构有大致了解。 第二轮(建议60-80天) 1、按照学习打卡表&#xff0c;一步步学习科目的考…

金贝E-KA1M 5.5T卓越性能,引领行业新高度

金贝 E-KA1M 5.5t 主要适用于家庭、书房、办公室等对噪音有一定要求的环境。它在运行时噪音极低&#xff0c;不会打扰您的日常生活&#xff0c;无论是放在家中还是办公场所&#xff0c;都能悄然为您创造财富。 金贝 E-KA1M 5.5t是一款具有较强算力的静音挖kuang机&#xff0c;其…

Awesome-Chinese-LLM:收集和梳理中文LLM相关的开源模型、应用、数据集及教程等资料

自ChatGPT为代表的大语言模型&#xff08;Large Language Model, LLM&#xff09;出现以后&#xff0c;由于其惊人的类通用人工智能&#xff08;AGI&#xff09;的能力&#xff0c;掀起了新一轮自然语言处理领域的研究和应用的浪潮。尤其是以ChatGLM、LLaMA等平民玩家都能跑起来…

《图解设计模式》笔记(三)生成实例

五、Singleton模式&#xff1a;只有一个实例 Singleton 是指只含有一个元素的集合。因为本模式只能生成一个实例&#xff0c;因此以 Singleton命名。 示例程序类图 Singleton.java public class Singleton {private static Singleton singleton new Singleton();private Si…

达林顿管阵列ULN2003的用途就是非门(输入和输出的关系)

对于UL2003来说&#xff0c;可以看作是非门。 输入为1&#xff0c;输出为0&#xff1b; 输入为0&#xff0c;输出为高组态[接一个上拉电阻即为1] 下面的可以不用看了&#xff0c;如果你想了解深入一点&#xff0c;可以往下看看。 ULN2003A就是个达林顿管&#xff0c; 一&am…

C++入门——“继承”

一、引入 面相对象的计算机语言有三大特性&#xff1a;“封装”、“继承”、“多态”。今天来讲解一下C的一大重要特性——继承。 通俗理解来讲&#xff0c;继承就和现实生活一样&#xff0c;子辈继承父辈的一些特性&#xff0c;C中的继承也可以这样理解。它允许我们在保持原有…

Windows C++控制台菜单库开发与源码展示

Windows C控制台菜单库 声明&#xff1a;演示视频&#xff1a;一、前言二、具体框架三、源码展示console_screen_set.hframeconsole_screen_frame_base.hconsole_screen_frame_char.hconsole_screen_frame_wchar_t.hconsole_screen_frame.h menuconsole_screen_menu_base.hcons…

html+css 实现hover 凹陷按钮

前言:哈喽,大家好,今天给大家分享html+css 绚丽效果!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、原理解析💡1.这是一个,hover时凹陷的效果。每个按钮是一个button…

【Android 远程数据库操作】

按正常情况下&#xff0c;前端不应该直接进行远程数据库操作&#xff0c;这不是一个明智的方式&#xff0c;应该是后端提供对应接口来处理&#xff0c;奈何公司各方面原因需要前端这样做。 对此&#xff0c;我对远程数据库操作做了总结&#xff0c;便于自己复盘&#xff0c;同…

机器学习第十四章-概率图模型

目录 14.1 隐马尔可夫模型 14.2马尔科夫随机场 14.3条件随机场 14.4学习与推断 14.4.1变量消去 14.4.2信念传播 14.5近似推断 14.5.1 MCMC采样 14.5.2 变分推断 14.6 话题模型 14.1 隐马尔可夫模型 概率围棋型是一类用图来表达变量相关关系的概率模型.它以图为表示工具…

Transformer(课程笔记)

一&#xff1a;Motivation RNN需要顺序的执行&#xff0c;不利于并行计算。 RNN的变体例如GRU、LSTM等需要依靠注意力机制解决信息瓶颈等问题。 抛弃RNN结构&#xff0c;提出了Transformer结构。 Transformer整体架构 二&#xff1a; 输入层&#xff08;BPE&#xff0c;PE&…

《黑神话:悟空》玩家必看!AMD显卡驱动24.8.1版全力支持!

系统之家于8月20日发出最新报道&#xff0c;AMD发布了最新的24.8.1版本驱动&#xff0c;本次更新增加了《黑神话&#xff1a;悟空》《星球大战&#xff1a;亡命之徒》等游戏的支持&#xff0c;且HYPR Tune支持允许HYPR-RX启用游戏内技术。下面跟随小编一起来看看AMD显卡驱动24.…

Centos7 message日志因dockerd、kubelet、warpdrive、containerd等应用迅速增长

问题&#xff1a;公司服务器在部署一套业务后&#xff0c;message日志记录大量的dockerd、kubelet、warpdrive、containerd应用日志&#xff0c;每天增加2G大小的日志 解决方案&#xff1a; 前期吐槽下&#xff1a;发现某个帖子&#xff0c;需要会员或者花钱才能看&#xff0c…

探索网络安全的深度与广度:挑战、策略与未来展望

一、引言 在当今数字化的时代&#xff0c;网络已经成为社会运转的核心基础设施之一。从个人的日常通信、娱乐到企业的业务运营、国家的关键服务&#xff0c;几乎所有领域都依赖于网络。然而&#xff0c;随着网络的普及和应用的深化&#xff0c;网络安全问题也日益凸显&#xf…

松下弧焊机器人维修 控制柜故障 连接线修复

一、Panasonic焊接机器人控制柜与机器人的接线 机器人的控制箱&#xff0c;一定要配对使用。松下焊接机器人控制柜已经记忆了机器人的绝对原点(机器人位置控制原点)。 二、编码器电缆 (圆形连接器) 1. 接口的插头插座要注意&#xff0c;插头要插到插座中。 2. 用一手握住电缆&a…

网络原理TCP/UDP详解

目录 传输属的几种格式 1.xml&#xff1a;通过成对的标签表示键值对信息。 2.json&#xff1a;当前更主流一点的&#xff0c;网络通信的数据格式 3.yml&#xff08;yaml&#xff09;强制要求数据的组织格式 4.google protobuffer 传输层 1.端口号&#xff1a; UDP协议 …

Vue3 组件管理 12 种神仙写法,灵活使用才能提高效率

SFC 单文件组件 顾名思义&#xff0c;就是一个.vue文件只写一个组件 模板写法 如果这个组件想要在别的组件里使用&#xff0c;就需要在另一个.vue中引入和使用、复用 h函数写法 使用 defineComponent h 去进行组件编写 JSX/TSX写法 使用 defineComponent JSX/TSX 去进行…