【Android 高性能音频】Oboe 音频流打开后 耳机 / 音箱 插拔事件处理 ( 动态注册广播接收者监听耳机插拔事件 | 重新打开 Oboe 音频流 )

简介: 【Android 高性能音频】Oboe 音频流打开后 耳机 / 音箱 插拔事件处理 ( 动态注册广播接收者监听耳机插拔事件 | 重新打开 Oboe 音频流 )

文章目录

一、动态注册广播接收者监听耳机插拔事件

二、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 = 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



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 地址 : https://github.com/han1202012/OboeDemo


CSDN 源码快照 :


目录
相关文章
|
存储 缓存 Android开发
安卓Jetpack Compose+Kotlin, 使用ExoPlayer播放多个【远程url】音频,搭配Okhttp库进行下载和缓存,播放完随机播放下一首
这是一个Kotlin项目,使用Jetpack Compose和ExoPlayer框架开发Android应用,功能是播放远程URL音频列表。应用会检查本地缓存,如果文件存在且大小与远程文件一致则使用缓存,否则下载文件并播放。播放完成后或遇到异常,会随机播放下一首音频,并在播放前随机设置播放速度(0.9到1.2倍速)。代码包括ViewModel,负责音频管理和播放逻辑,以及UI层,包含播放和停止按钮。
|
XML Java 数据库
安卓项目:app注册/登录界面设计
本文介绍了如何设计一个Android应用的注册/登录界面,包括布局文件的创建、登录和注册逻辑的实现,以及运行效果的展示。
866 1
安卓项目:app注册/登录界面设计
|
11月前
|
缓存 前端开发 Android开发
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
532 12
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
|
Android开发
Android面试高频知识点(1) 图解Android事件分发机制
Android面试高频知识点(1) 图解Android事件分发机制
|
Android开发
Android面试高频知识点(1) 图解 Android 事件分发机制
Android面试高频知识点(1) 图解 Android 事件分发机制
177 1
|
Android开发
Android 事件分发机制详细解读
Android 事件分发机制详细解读
221 5
|
XML 前端开发 Android开发
Android面试高频知识点(1) 图解Android事件分发机制
Android面试高频知识点(1) 图解Android事件分发机制
Android面试高频知识点(1) 图解Android事件分发机制
|
Android开发
Android面试高频知识点(1) 图解 Android 事件分发机制
在Android开发中,事件分发机制是一块Android比较重要的知识体系,了解并熟悉整套的分发机制有助于更好的分析各种点击滑动失效问题,更好去扩展控件的事件功能和开发自定义控件,同时事件分发机制也是Android面试必问考点之一,如果你能把下面的一些事件分发图当场画出来肯定加分不少。废话不多说,总结一句:事件分发机制很重要。
470 9
|
图形学 Android开发
小功能⭐️Unity调用Android常用事件
小功能⭐️Unity调用Android常用事件
|
开发工具 Android开发
Android项目架构设计问题之组件A通知组件B某个事件的发生如何解决
Android项目架构设计问题之组件A通知组件B某个事件的发生如何解决
138 0

热门文章

最新文章