【Android 高性能音频】Oboe 音频流打开后 耳机 / 音箱 插拔事件处理 ( 动态注册广播接收者监听耳机插拔事件 | 重新打开 Oboe 音频流 )
2023-06-13 09:17:50 时间
文章目录
基于 【Android 高性能音频】Oboe 开发流程 ( Oboe 完整代码示例 ) 博客中的示例 , 为该示例添加耳机插拔监听 , 监测到耳机插拔后 , 重新打开 Oboe 音频流 ;
一、动态注册广播接收者监听耳机插拔事件
耳机插拔监听 , 需要监听 android.intent.action.HEADSET_PLUG
广播事件 ;
注意不能使用静态注册的广播接收者监听该事件 , 只能使用代码中动态注册的广播接收者进行监听 ;
还有一点特别注意 , 在 Resume 时 , 也会激活一次耳机插拔事件 , 相当于初始化事件 , 这里屏蔽 Resume 后的第一次耳机插拔事件 , 需要设置标志位 ;
广播接收者代码示例 :
/**
* 广播接收者
* 监听耳机插拔事件
*/
val mHeadsetPlugReceiver: BroadcastReceiver = object : BroadcastReceiver(){
override fun onReceive(context: Context, intent: Intent) {
if (intent.hasExtra("state")) {
// resume 第一次忽略耳机插拔事件
if(isResumeIgnore){
isResumeIgnore = false
return
}
if (intent.getIntExtra("state", 0) == 0) {
stringFromJNI()
Toast.makeText(context,
"耳机拔出", Toast.LENGTH_SHORT).show()
} else if (intent.getIntExtra("state", 0) == 1) {
stringFromJNI()
Toast.makeText(context,
"耳机插入", Toast.LENGTH_SHORT).show()
}
}
}
}
注册广播接收者 :
val filter = IntentFilter()
filter.addAction("android.intent.action.HEADSET_PLUG")
registerReceiver(mHeadsetPlugReceiver, filter)
完整代码示例 :
package kim.hsl.oboedemo
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
/**
* 每次 Resume 第一次忽略
*/
var isResumeIgnore = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 创建 Oboe 音频流并发音
sample_text.text = stringFromJNI()
val filter = IntentFilter()
filter.addAction("android.intent.action.HEADSET_PLUG")
registerReceiver(mHeadsetPlugReceiver, filter)
}
override fun onResume() {
super.onResume()
isResumeIgnore = true
}
override fun onPause() {
super.onPause()
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(mHeadsetPlugReceiver)
}
/**
* 广播接收者
* 监听耳机插拔事件
*/
val mHeadsetPlugReceiver: BroadcastReceiver = object : BroadcastReceiver(){
override fun onReceive(context: Context, intent: Intent) {
if (intent.hasExtra("state")) {
// resume 第一次忽略耳机插拔事件
if(isResumeIgnore){
isResumeIgnore = false
return
}
if (intent.getIntExtra("state", 0) == 0) {
stringFromJNI()
Toast.makeText(context,
"耳机拔出", Toast.LENGTH_SHORT).show()
} else if (intent.getIntExtra("state", 0) == 1) {
stringFromJNI()
Toast.makeText(context,
"耳机插入", Toast.LENGTH_SHORT).show()
}
}
}
}
/**
* 重新打开 Oboe 音频流
*/
external fun stringFromJNI(): String
companion object {
init {
System.loadLibrary("native-lib")
}
}
}
二、jni 层的 Oboe 播放器代码 ( 重新打开 Oboe 音频流 )
JNI 层代码没有进行修改 ;
Oboe 音频流变量声明为全局变量 , 如果插入耳机 , 再次调用 Java_kim_hsl_oboedemo_MainActivity_stringFromJNI 方法 , 即可重新打开 Oboe 音频流 , 打开时的设备是默认的设备 , 即当前插入的耳机/音箱 ;
// 声明 Oboe 音频流
oboe::ManagedStream managedStream = oboe::ManagedStream();
如果拔出耳机 , 再次调用 Java_kim_hsl_oboedemo_MainActivity_stringFromJNI 方法 , 即可重新打开 Oboe 音频流 , 打开时的设备是默认的设备 , 即手机本身自带的扬声器 ;
完整 C++ 代码示例 :
#include <jni.h>
#include <string>
#include <oboe/Oboe.h>
#include "logging_macros.h"
// 这部分变量是采样相关的 , 与 Oboe 操作无关
// 声道个数 , 2 代表立体声
static int constexpr kChannelCount = 2;
static int constexpr kSampleRate = 48000;
// Wave params, these could be instance variables in order to modify at runtime
static float constexpr kAmplitude = 0.5f;
// 频率
static float constexpr kFrequency = 440;
// PI 圆周率
static float constexpr kPI = M_PI;
// 2 PI 两倍圆周率
static float constexpr kTwoPi = kPI * 2;
// 每次累加的采样值
static double constexpr mPhaseIncrement = kFrequency * kTwoPi / (double) kSampleRate;
// 追踪当前波形位置
float mPhase = 0.0;
// Oboe 音频流回调类
class MyCallback : public oboe::AudioStreamCallback {
public:
oboe::DataCallbackResult
onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) {
// 需要生成 AudioFormat::Float 类型数据 , 该缓冲区类型也是该类型
// 生产者需要检查该格式
// oboe::AudioStream *audioStream 已经转换为适当的类型
// 获取音频数据缓冲区
auto *floatData = static_cast<float *>(audioData);
// 生成正弦波数据
for (int i = 0; i < numFrames; ++i) {
float sampleValue = kAmplitude * sinf(mPhase);
for (int j = 0; j < kChannelCount; j++) {
floatData[i * kChannelCount + j] = sampleValue;
}
mPhase += mPhaseIncrement;
if (mPhase >= kTwoPi) mPhase -= kTwoPi;
}
LOGI("回调 onAudioReady");
return oboe::DataCallbackResult::Continue;
}
};
// 创建 MyCallback 对象
MyCallback myCallback = MyCallback();
// 声明 Oboe 音频流
oboe::ManagedStream managedStream = oboe::ManagedStream();
extern "C" JNIEXPORT jstring JNICALL
Java_kim_hsl_oboedemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
// 1. 音频流构建器
oboe::AudioStreamBuilder builder = oboe::AudioStreamBuilder();
// 设置音频流方向
builder.setDirection(oboe::Direction::Output);
// 设置性能优先级
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
// 设置共享模式 , 独占
builder.setSharingMode(oboe::SharingMode::Exclusive);
// 设置音频采样格式
builder.setFormat(oboe::AudioFormat::Float);
// 设置声道数 , 单声道/立体声
builder.setChannelCount(oboe::ChannelCount::Stereo);
// 设置采样率
builder.setSampleRate(48000);
// 设置回调对象 , 注意要设置 AudioStreamCallback * 指针类型
builder.setCallback(&myCallback);
// 2. 通过 AudioStreamBuilder 打开 Oboe 音频流
oboe::Result result = builder.openManagedStream(managedStream);
LOGI("openManagedStream result : %s", oboe::convertToText(result));
// 3. 开始播放
result = managedStream->requestStart();
LOGI("requestStart result : %s", oboe::convertToText(result));
// 返回数据到
std::string hello = "Oboe Test " + std::to_string(static_cast<int>(oboe::PerformanceMode::LowLatency)) + " Result : " + oboe::convertToText(result);
return env->NewStringUTF(hello.c_str());
}
三、相关资料
Oboe GitHub 主页 : GitHub/Oboe
- ① 简单使用 : Getting Started
- ② Oboe 全指南 : Full Guide To Oboe
- ③ Oboe API 参考 : API reference
- ④ Android 音频框架发展 : Android audio history
代码示例 :
- GitHub 地址 : https://github.com/han1202012/OboeDemo
相关文章
- android 置灰不可点击,Android Studio 运行按钮灰色的完美解决方法
- android开机动画多长时间_Android开机动画及黑屏[通俗易懂]
- android中适配器的作用,适配器模式 在Android中的简单理解「建议收藏」
- Android Studio中Intel HAXM的那些坑「建议收藏」
- mac 电脑android环境变量设置,mac上Android环境变量配置[通俗易懂]
- Android开发中,怎样调用摄像机拍照以及怎样从本地图库中选取照片
- android vlc 中文字幕,解决Android版vlc中文乱码问题
- android 获取收到短信验证码,Android自动获取短信验证码
- Android设置TabLayout熟悉及下划线宽度
- Android触摸事件_wpf触摸屏点击事件
- ubuntu android studio_android自启动
- Android preference_android studio preview
- Android触摸事件_简述兴奋在突触的传递过程
- 浅谈android端的字符串加密
- Android OpenGL ES 基础原理
- Android JetPack~ LiveData (一) 介绍与使用
- 【Android RTMP】RTMP 直播推流阶段总结 ( 服务器端搭建 | Android 手机端编码推流 | 电脑端观看直播 | 服务器状态查看 )
- 【错误记录】Flutter / Android 报错 ( AAPT: error: attribute android:requestLegacyExternalStorage not found )
- 【Android 安全】DEX 加密 ( Application 替换 | 分析 ContentProvider 组件中调用 getApplication() 获取的 Application )
- 【Android 事件分发】ItemTouchHelper 实现拖动排序
- 【Android 逆向】Android 权限 ( Android 逆向中使用的 android.permission 权限 | Android 系统中的 Linux 用户权限 )
- 【Android Gradle 插件】DexOptions 配置 ② ( additionalParameters 属性配置 | dx 工具 | dx 附加参数 )
- 【Android Gradle 插件】将自定义 Gradle 插件上传到自建 Maven 仓库 ① ( Maven 仓库上传源码上传源码设置 | 自定义源码打包任务 | 自定义文档打包任务 )
- Android开发中遇到的问题(三)——eclipse创建android项目无法正常预览布局文件详解手机开发
- [android] 看博客学习Android常见的几种RuntimeException详解手机开发
- Android中使用Gson解析JSON数据的两种方法
- android创建手势识别示例代码
- Android实现判断手机未接来电及处理方法
- Android中Parcelable的作用实例解析
- Android按钮单击事件的四种常用写法总结