文章目录
一、动态注册广播接收者监听耳机插拔事件二、jni 层的 Oboe 播放器代码 ( 重新打开 Oboe 音频流 )三、相关资料基于 【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 = falsereturn}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.oboedemoimport android.content.BroadcastReceiverimport android.content.Contextimport android.content.Intentimport android.content.IntentFilterimport android.os.Bundleimport android.widget.Toastimport androidx.appcompat.app.AppCompatActivityimport kotlinx.android.synthetic.main.activity_main.*class MainActivity : AppCompatActivity() {/*** 每次 Resume 第一次忽略*/var isResumeIgnore = falseoverride 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 = falsereturn}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(): Stringcompanion 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 runtimestatic 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::DataCallbackResultonAudioReady(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 JNICALLJava_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
Oboe API 参考 :
API 索引 :https://google.github.io/oboe/reference/namespaceoboe.html
Oboe 音频流创建器 :https://google.github.io/oboe/reference/classoboe_1_1_audio_stream_builder.html
Oboe 音频流 :https://google.github.io/oboe/reference/classoboe_1_1_audio_stream.html
Oboe 音频流回调接口 :https://google.github.io/oboe/reference/classoboe_1_1_audio_stream_callback.html
代码示例 :
GitHub 地址 :/han120/OboeDemo
CSDN 源码快照 :
【Android 高性能音频】Oboe 音频流打开后 耳机 / 音箱 插拔事件处理 ( 动态注册广播接收者监听耳机插拔事件 | 重新打开 Oboe 音频流 )