首先是需要在as里新建一个项目,然后新建一个library
导入unity-classes.jar文件到libs里,然后引用一下
具体的麦克风打开的步骤可以参考下面的这个帖子,我基本就是照猫画虎
https://www.cnblogs.com/vir56k/archive/2012/11/29/2794226.html
这里需要注意一下
由于安卓的更新,在不同的移动端上,安卓的不同版本在授权上有不同的处理方式
安卓6.0以上的话需要动态的在程序运行的时候去申请权限
这里可以参考其他大佬动态申请权限的帖子,小弟因为隔了一天,突然找不到用的大佬的帖子的网址了
哦我想起来了,这个动态申请权限的方法是看之前使用百度语音唤醒时看的帖子里的,在那个大佬的代码里初始化语音唤醒和识别的时候就做了这个操作,然后我就发现了他在初始化动态申请权限的方法里形参里加了一个Context,对java不了解的我点进去看了一下,发现这个其实就是一个activity,然后就用了上面讲的思路,把unity这个activity传回给了aar包里的这个初始化打胎申请权限的方法
这里贴一下自己看了之后做了一些变动的逻辑
这里是java代码
public String startRecording() {
mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.setOutputFile(newFileName());
try {
mediaRecorder.prepare();
} catch (Exception e) {
Log.e("test", e.getMessage());
}
mediaRecorder.start();
return "finied";
}
public String newFileName() {
//String mFileName = Environment.getExternalStorageDirectory() + File.separator + Environment.DIRECTORY_DCIM + File.separator;
String mFileName = Environment.getExternalStorageDirectory()
.getAbsolutePath();
String s = new SimpleDateFormat("yyyy-MM-dd hhmmss")
.format(new Date());
mFileName += "/rcd_" + s + ".amr";
Log.d("test", mFileName);
return mFileName;
}
/**
* android 6.0 以上需要动态申请权限
*/
public void initPermission(Context context) {
String permissions[] = {Manifest.permission.RECORD_AUDIO,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.INTERNET,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
PackageManager pm = getActivityByContext(context).getPackageManager();
boolean permission_readStorage = (PackageManager.PERMISSION_GRANTED ==
pm.checkPermission("android.permission.RECORD_AUDIO", "com.cientx.tianguo"));
boolean permission_network_state = (PackageManager.PERMISSION_GRANTED ==
pm.checkPermission("android.permission.ACCESS_NETWORK_STATE", "com.cientx.tianguo"));
boolean permission_internet = (PackageManager.PERMISSION_GRANTED ==
pm.checkPermission("android.permission.INTERNET", "com.cientx.tianguo"));
boolean permission_writeStorage = (PackageManager.PERMISSION_GRANTED ==
pm.checkPermission("android.permission.WRITE_EXTERNAL_STORAGE", "com.cientx.tianguo"));
if (!(permission_readStorage && permission_writeStorage && permission_network_state && permission_internet)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getActivityByContext(context).requestPermissions(permissions, 0x01);
}
}
}
不过还需要注意一下,这个大佬的帖子里使用到了activity,但是我们是在unity项目里面的,是不需要安卓里自己再做一个activity的,那应该怎么办,鸡贼的我想到了一个方法,因为之前百度的语音唤醒功能里也是没有独立的activity的,但是他们还是能通过和上面这个大佬一样的方式获取到activity,于是我去翻了一下百度语音唤醒帖子的源码,发现那个大佬的实现方式是通过在unity里调用AndroidJavaClass类,然后传unity的路径,把项目的activity作为activity,然后回传给aar包
此处我只能说nb,大佬还是大佬
把unity作为activity之后回传给aar包里初始化动态申请权限的方法就可以了
有关动态获得权限的方法这一个难点
在麦克风初始化的时候是需要设置文件储存路径的
而有关储存的权限,似乎单单通过上面动态申请权限的方法是会出现问题的
就比如我在使用了上面的一顿操作之后,发现一直无法正确的调用麦克风,debug发现是因为
/storage/emulated/0/rcd_2021-09-11 094229.amr: open failed: EPERM (Operation
这个异常导致的,在百度和思考了良久后,我发现是因为获取读写文件权限的方式在不同的安卓版本也有不同的处理方式(可真是离谱给离谱他妈开门-离谱到家了)
除了上述动态申请权限的方法外还需要在AndroidManifest.xml文件里添加下面的这个标签
<application android:requestLegacyExternalStorage="true" />
详细的可以看这个帖子和评论(主要是评论)
https://blog.csdn.net/qq_34884729/article/details/53284274
打包有两种方式,一个是aar,一个是jar
注意这里只能打aar包(从小弟目前的能力来说)
这是因为打出jar包的话他是不会打出AndroidManifest.xml这些文件的,也就是说里面的获得权限的标签就都没了,所以这个方法是不可取的
jar包的话比较适合用在一些不需要权限的功能比较好(个人感觉)
将aar里的unity-classes.jar删掉然后导入unity(老生常谈的问题了)
unity里调用相应的方法即可
贴一下完整的代码吧~
这是一个单例(是之前百度语音唤醒的那个大佬的帖子里的)
他把所有的功能都封装在了这个单例里,然后都是直接通过这个单例去调用需要的方法的
import android.content.Context;
public class CientBaiDuVoiceMainActivity {
public static CientBaiDuVoiceMainActivity _instance;
public static CientBaiDuVoiceMainActivity getInstance() {
if (_instance == null) {
_instance = new CientBaiDuVoiceMainActivity();
}
return _instance;
}
MroTest mroTest;
public void InitMroTest(Context context){
try{
mroTest = new MroTest();
}catch (Exception e){
System.out.println(e.getMessage());
}
mroTest.initPermission(context);
}
}
这是我自己在整理和百度语音唤醒大佬的帖子和网上其他帖子之后自己整理的麦克风的管理类
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import java.text.SimpleDateFormat;
import java.util.Date;
import static com.pure.testmcro.GetActivity.getActivityByContext;
public class MroTest {
public MediaRecorder mediaRecorder;
public String startRecording() {
mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.setOutputFile(newFileName());
try {
mediaRecorder.prepare();
} catch (Exception e) {
Log.e("test", e.getMessage());
}
mediaRecorder.start();
return "finied";
}
public String newFileName() {
//String mFileName = Environment.getExternalStorageDirectory() + File.separator + Environment.DIRECTORY_DCIM + File.separator;
String mFileName = Environment.getExternalStorageDirectory()
.getAbsolutePath();
String s = new SimpleDateFormat("yyyy-MM-dd hhmmss")
.format(new Date());
mFileName += "/rcd_" + s + ".amr";
Log.d("test", mFileName);
return mFileName;
}
public void stopRecording() {
mediaRecorder.stop();
mediaRecorder.release();
mediaRecorder = null;
}
public void test() {
Log.d("pure", "test: 测试");
}
/**
* android 6.0 以上需要动态申请权限
*/
public void initPermission(Context context) {
String permissions[] = {Manifest.permission.RECORD_AUDIO,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.INTERNET,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
PackageManager pm = getActivityByContext(context).getPackageManager();
boolean permission_readStorage = (PackageManager.PERMISSION_GRANTED ==
pm.checkPermission("android.permission.RECORD_AUDIO", "com.cientx.tianguo"));
boolean permission_network_state = (PackageManager.PERMISSION_GRANTED ==
pm.checkPermission("android.permission.ACCESS_NETWORK_STATE", "com.cientx.tianguo"));
boolean permission_internet = (PackageManager.PERMISSION_GRANTED ==
pm.checkPermission("android.permission.INTERNET", "com.cientx.tianguo"));
boolean permission_writeStorage = (PackageManager.PERMISSION_GRANTED ==
pm.checkPermission("android.permission.WRITE_EXTERNAL_STORAGE", "com.cientx.tianguo"));
if (!(permission_readStorage && permission_writeStorage && permission_network_state && permission_internet)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getActivityByContext(context).requestPermissions(permissions, 0x01);
}
}
}
}
还有一个工具类
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
public class GetActivity {
public static Activity getActivityByContext(Context context){
while(context instanceof ContextWrapper){
if(context instanceof Activity){
return (Activity) context;
}
context = ((ContextWrapper) context).getBaseContext();
}
return null;
}
}
然后是Androidmanifest文件(这里也是用的百度语音唤醒大佬的,所以有的其实是不需要用到的,看着删减吧)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.pure.testmcro">
<!-- 通用权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 语音识别权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 语音合成权限 -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<application android:requestLegacyExternalStorage="true" />
</manifest>
打包aar和jar的代码我也贴上来吧(百度一搜其实也有),注意打包的代码是需要放在build,gradle里的
task CopyPlugin(type: Copy) {
dependsOn assemble
from('build/outputs/aar')
into('../../Assets/Plugins/Android')
include(project.name + '-release.aar')
}
//上面是打包成aar的
///--------------分割线---------------------
//下面是打包成jar的
task deleteOldJar(type: Delete) {
delete 'release/AndroidPlugin.jar'
}
//task to export contents as jar
task exportJar(type: Copy) {
from('build/intermediates/packaged-classes/release/') //从这个目录下取出默认jar包
into('release/')
include('classes.jar')
///Rename the jar
rename('classes.jar', 'AndroidPlugin.jar')
}
exportJar.dependsOn(deleteOldJar, build)
然后是unity里的代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TextMcro : MonoBehaviour
{
AndroidJavaObject m_AndroidPluginObj;
AndroidJavaClass _androidJC;
AndroidJavaObject m_Android;
AndroidJavaClass jc;
AndroidJavaObject test1;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void StartRecoding()
{
test1 = new AndroidJavaObject("com.pure.testmcro.MroTest");
string result = test1.Call<string>("startRecording");
Debug.Log(result);
}
public void EndRecoding()
{
test1 = new AndroidJavaObject("com.pure.testmcro.MroTest");
test1.Call("stopRecording");
}
public void test()
{
test1 = new AndroidJavaObject("com.pure.testmcro.MroTest");
test1.Call("test");
}
private void Awake()
{
_androidJC = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
if (_androidJC == null)
{
Debug.Log("JNI initialization failure.");
return;
}
m_AndroidPluginObj = _androidJC.GetStatic<AndroidJavaObject>("currentActivity");
jc = new AndroidJavaClass("com.pure.testmcro.CientBaiDuVoiceMainActivity");
m_Android = jc.CallStatic<AndroidJavaObject>("getInstance");
Debug.Log("和安卓交互成功!");
if (m_Android == null)
{
Debug.Log("BaiduWake class ini failure");
}
InitREe();
}
public void InitREe()
{
if (m_Android != null)
{
m_Android.Call("InitMroTest", m_AndroidPluginObj);
Debug.Log("唤醒初始化成功!");
}
else
Debug.Log("AndroidPlugin is Null");
}
}
把startrecording和stoprecording挂载到场景里的按钮然后打包就可以了
目前是可以完美调用startrecoding方法的,也能在内存中找到对应的文件,就是在stoprecording方法的时候会调用异常
这个问题目前还没解决,主要是怕前面的这些都给忘了,所以就先记录一下
更新一下,这个问题已解决
这个异常很显然是报了空指针的异常
所以仔细研究一下代码就知道了我unity里的生成了两个mrotest类,开始的方法一个,结束的方法一个,在as里就生成了两个一样的类,但是我们的麦克风是需要调用的一个类里的内容的,所以这个时候前面创建的单例就很有用了,我们只需要在单例里面再写个开启麦克风和关闭麦克风的方法就好了,这样就能保证用的是一个类里的同一个麦克风了.
在前面的CientBaiDuVoiceMainActivity类里直接添加下面的这两个方法就可以了
然后重新打包导入到unity
public void startRecording(){
try{
mroTest.startRecording();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
public void stopRecording(){
try{
mroTest.stopRecording();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
在unity里也需要修改一下代码
用awake里已经创建好的单例的m_Android这个对象去调用刚才写好的两个方法就可以了
public void StartRecoding()
{
//test1 = new AndroidJavaObject("com.pure.testmcro.MroTest");
//string result = test1.Call("startRecording");
m_Android.Call("startRecording");
}
public void EndRecoding()
{
//test1 = new AndroidJavaObject("com.pure.testmcro.MroTest");
//test1.Call("stopRecording");
m_Android.Call("stopRecording");
}
完美解决
然后就是看怎么把这个文件上传到图灵的接口里了~
加油呀