2017-11-22 19:40:00 weixin_34363171 阅读数 245

1、此问题在cocos2dx 3.13/3.14版本(其它版本没有测试过)在Android7中使用AudioEngine的play2d函数播放音效时出现。

调试时出现如下提示:

 

2、论坛中相关讨论帖地址:http://forum.cocos.com/t/android7/44119

 

3.解决办法:

将cocos2d-x\cocos\audio\android\AudioPlayerProvider.cpp中的getSystemAPILevel() < 17都改为getSystemAPILevel() >= 17。

转载于:https://www.cnblogs.com/chevin/p/7880924.html

2015-07-07 11:16:00 u011416077 阅读数 0
adb logcat | "C:/android-ndk-r9d/ndk-stack" -sym "C:/Users/DaDa_/Desktop/cocos2d-2.1rc0-x-2.1.3/BullFight/proj.android/obj/local/armeabi" 


adb logcat | "ndk路径/ndk-stack" -sym "cocos项目安卓工程路径/obj/local/armeabi"

将上面命令写在一个.bat文件

放到安卓sdk的platform-tools目录下,

例如:

E:\adt-bundle-windows-x86_64-20131030\sdk\platform-tools

运行.bat文件

再运行安卓崩溃的地方

就会在命令行输出堆栈信息

2017-09-27 12:43:18 xi_mi_ 阅读数 488

经过一番折腾,终于找到了一种第三方库来转换android与iOS录音播放格式不兼容的问题。
思路:android/iOS手机录音传给服务器,参数(录音数据+手机端口类型),然后等到服务器广播数据给玩家,判断端口是android,将数据保存为为.amr格式,否则保存为.aac格式。不同手机端口调用相应端口函数。
1、设置端口录音格式:
android–>.amr

    public void record(){
        if (isRecording == true ) return;    

        recondPath = Environment.getExternalStorageDirectory().getAbsolutePath(); 
        recondPath += "/ione1.amr"; //录音格式

        File dirs = new File(recondPath);
        if (dirs.exists()){
           dirs.delete();
        }

        stopRecorder();         
        isRecording = true; 

        mRecorder = new MediaRecorder();          
        //设置音源为Micphone  
        mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 
        //设置封装格式
        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB); 
        //设置编码格式  
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);         
        mRecorder. setAudioEncodingBitRate(8);//设置音频编码录音比特率
        mRecorder.setAudioChannels(1);//设置录制的音频通道数
        mRecorder.setAudioSamplingRate(8000); //设置音频采样率记录                
        mRecorder.setOutputFile(recondPath);    

        try {    
            mRecorder.prepare();    
        } catch (IOException e) {    
            Log.e(TAG, "prepare() failed");
        }
        //录音  
        mRecorder.start();        
    }
    public boolean stopRecorder() {
         if( mRecorder == null ) return false;

        try{
            mRecorder.stop();    
            mRecorder.reset();
            mRecorder.release();    
            mRecorder = null;   
        }catch ( IllegalStateException e){
            e.printStackTrace();
        }

        return false;  
    }  

ios–>.aac

-(void)startAudioRecording
{
    if(!isRecording)
    {
        [self init];

        isRecording = YES;
        NSLog(@"正在录音");

        NSMutableDictionary *dicM=[NSMutableDictionary dictionary];
        //设置录音格式
        [dicM setObject:@(kAudioFormatMPEG4AAC) forKey:AVFormatIDKey];//录音格式
        //设置录音采样率,8000是电话采样率,对于一般录音已经够了
        [dicM setObject:@(1600) forKey:AVSampleRateKey];
        //设置通道,这里采用单声道
        [dicM setObject:@(1) forKey:AVNumberOfChannelsKey];
        //每个采样点位数,分为8、16、24、32
        [dicM setObject:@(8) forKey:AVLinearPCMBitDepthKey];
        //录音的质量
        [dicM setValue:[NSNumber numberWithInt:AVAudioQualityMin] forKey:AVEncoderAudioQualityKey];
        //是否使用浮点数采样
        [dicM setObject:@(YES) forKey:AVLinearPCMIsFloatKey];

        recorder = [[AVAudioRecorder alloc] initWithURL:recordedFile settings:dicM error:nil];

        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
        [[AVAudioSession sharedInstance] setActive:YES error:nil];
        [self setSoundSession];
        [recorder peakPowerForChannel:0];

        [recorder prepareToRecord];
        [recorder record];
    }
}

2、接收服务器传来的需要播放的录音数据,根据不同端口录的音保存为数据相同录音格式:
android–>.amr
ios–>.aac

//服务器传来录音数据,保存在表voiceTab中
//voiceTab[index] = event.data
function MainScene:startPlayVoice()
    if self.isStartRecond  == false then -- 没有在录音
        if device.platform == "android" or device.platform == "ios" then --播放录音        
            local len = #voiceTab
            if len >= 1 then
                voicePlay( data )
            else
                print(" 录音播放完了 ")
            end
        end
    end
end
function voicePlay(data)
    if device.platform == "android" then
        local function callback(result)
            -- print("录音播放完成 " )  
            ReturnRecordingPlayChange(result)
        end

        local path = writefileCheckRecond( data.voicebin, data.content )
        print("开始播放了  voicePlay ")
        local args = { 1,  path, callback }
        local sigs = "(ILjava/lang/String;I)I"
        local luaj = require "cocos.cocos2d.luaj"
        local className = "com/cocos2dx/sample/LuaJavaBridge"
        local ok,ret  = luaj.callStaticMethod(className,"sendLuaToJavaAudioRecorPlay",args,sigs)
        if not ok then
            print("luaj error:", ret)
        else
            print("The ret is:", ret)
        end
    elseif device.platform == "ios" then
        local path = writefileCheckRecond( data.voicebin, data.content )
        print("开始播放了  voicePlay ",path)
        local i = iosAudioStartPlay( path ) 
    end
end
function writefileCheckRecond( data,platform )
    local path = device.writablePath.."netSprite/" --获取本地存储目录
    if not io.exists(path) then
        lfs.mkdir(path) --目录不存在,创建此目录
    end

    if platform == "android" then
        path = path.."recond.amr"
    elseif platform == "ios" then
        path = path.."recond.aac"
    else
        return;
    end

    print("写入完成:"..path)

    return path;
end

3、分析两端口录音互相播放的情况(A端口录音–>B端口播放)
android–>android:android玩家录音为.amr传给服务器,android玩家接收录音二进制数据保存的也是.amr格式,播放OK。

    public void startPlay(String filePath,int luaFunc){
        if( isPlay == true)
        {
            Log.d("tag", "正在播放中");
            return;
        }

        try {
            stopPlay();  

            isPlay = true;
            playLuaFun = luaFunc; 

            mPlayer = MediaPlayer.create(Cocos2dxActivity.getInstance(), Uri.parse(filePath));
            mPlayer.start();// 开始播放                     

            mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener(){
                @Override 
                public void onCompletion(MediaPlayer m) { 
                    Log.d("tag", "播放完毕");                       
                    LuaJavaBridge.callbackLuaFun("1","ReturnRecordingPlayChange",index++);   
                    buySuccessJava(1);
                    Log.d(TAG, "playLuaFun = " + playLuaFun);

                    isPlay = false;
                    stopPlay();
                }
            });

        }catch(Exception e){  
            Log.e(TAG, "prepare() failed");
        }
    }

android–>iOS:android玩家录音为.amr传给服务器,iOS玩家接收录音二进制数据保存为.amr格式,调用第三方库将格式.amr转.mav格式就播放OK。

-(void)playAudio
{
    if(!isPlay)
    {
        if([player isPlaying])
        {
            NSLog(@"停止录音播放");
            [player pause];
        }
        else
        {
            NSString *documentsDirectory= [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
            audioRecoderSavePath=[NSString stringWithFormat:@"%@/%@", documentsDirectory,@"netSprite/"];
            recoderName= [NSString stringWithFormat:@"%@",@"recond.amr"];
            tempRecoderPath=[NSString stringWithFormat:@"%@%@",audioRecoderSavePath,recoderName];

            fm = [NSFileManager defaultManager]; //创建文件管理对象
            if([fm fileExistsAtPath:tempRecoderPath] == NO)//判断文件是否存在
            {
                NSLog(@"文件不存在");

                recoderName= [NSString stringWithFormat:@"%@",@"recond.aac"];
                tempRecoderPath=[NSString stringWithFormat:@"%@%@",audioRecoderSavePath,recoderName];
                isIos = true;
                [self startPlayAudio];
                return;
            }
            else
            {
                isIos = false;
                NSLog(@"文件存在");
            }

            NSString *recoderWavName= [NSString stringWithFormat:@"%@",@"recond.wav"];
            tempWavRecoderPath =[NSString stringWithFormat:@"%@%@",audioRecoderSavePath,recoderWavName];
            //第三方库AudioConverter
            [AudioConverter convertAmrToWavAtPath:tempRecoderPath wavSavePath:tempWavRecoderPath asynchronize:YES completion:^(BOOL success, NSString * _Nullable resultPath) {
                if (success) {
                    NSLog(@"amr转wav成功!");

                    _wavFilePath = resultPath;

                    [self startPlayAudio];
                } else {
                   NSLog(@"amr转wav失败!");
                }
            }];
        }
    }
}

iOS –>android:ios玩家录音为.aac格式传给服务器,android玩家接收录音二进制数据保存为.aac格式,
android玩家播iOS录的.aac格式OK。

iOS–>iOS:ios玩家录音为.aac格式传给服务器,ios玩家接收录音二进制数据保存为.aac格式,ios玩家播iOS录的.aac格式OK。

参考:
http://blog.csdn.net/qq_36946260/article/details/69224150
http://blog.csdn.net/adalu1986/article/details/50502387
http://www.jianshu.com/p/7dc01b48f8fc

2018-03-08 15:31:00 weixin_30471561 阅读数 0

首先贴一张官方对于ios与android上音频格式的推荐:

 这里只给出了推荐格式,一般我们在实际运用中会使用如下方式:

     一、IOS与安卓各一套:音乐:都使用MP3    音效:ios用caf Android用ogg

    二、使用通用的MP3格式
  
需要注意的是在IOS上MP3的比特率过大或者过小都可能播不出来,官方没有给出具体原因;
  一般使用128kpbs与65kbps

转载于:https://www.cnblogs.com/kefeiGame/p/8528772.html

2016-03-12 11:44:00 weixin_34128237 阅读数 28

本文主要实现两个功能:
(1)通过Android sdk的API得到应用程序的包名(PackageName),然后传递给c++层函数。
(2)通过c++函数调用Android的java层函数,显示一个对话框,点击按钮退出程序。
1. 首先来简单学习一下JNI的相关知识,我这篇文章中简单实现了怎么在Android Java层调用c++函数。要想使用JNI,必须得包含头文件,android是使用ndk编译c/c++的,这里jni.h文件位于:\android-ndk-r8b\platforms\android-14\arch-arm\usr\include\jni.h,该文件定义了所有和JNI相关的数据类型和接口。下面是相关代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# include <inttypes.h>      /* C99 */
typedefuint8_t         jboolean;       /* unsigned 8 bits */
typedefint8_t          jbyte;          /* signed 8 bits */
typedefuint16_t        jchar;          /* unsigned 16 bits */
typedefint16_t         jshort;         /* signed 16 bits */
typedefint32_t         jint;           /* signed 32 bits */
typedefint64_t         jlong;          /* signed 64 bits */
typedeffloat          jfloat;         /* 32-bit IEEE 754 */
typedefdouble         jdouble;        /* 64-bit IEEE 754 */
#else
typedefunsigned char  jboolean;       /* unsigned 8 bits */
typedefsignedchar    jbyte;          /* signed 8 bits */
typedefunsigned short jchar;          /* unsigned 16 bits */
typedefshort          jshort;         /* signed 16 bits */
typedefint            jint;           /* signed 32 bits */
typedeflonglong      jlong;          /* signed 64 bits */
typedeffloat          jfloat;         /* 32-bit IEEE 754 */
typedefdouble         jdouble;        /* 64-bit IEEE 754 */
#endif
 
/* "cardinal indices and sizes" */
typedefjint            jsize;
 
#ifdef __cplusplus
/*
 * Reference types, in C++
 */
class_jobject {};
class_jclass : public_jobject {};
class_jstring : public_jobject {};
class_jarray : public_jobject {};
class_jobjectArray : public_jarray {};
class_jbooleanArray : public_jarray {};
class_jbyteArray : public_jarray {};
class_jcharArray : public_jarray {};
class_jshortArray : public_jarray {};
class_jintArray : public_jarray {};
class_jlongArray : public_jarray {};
class_jfloatArray : public_jarray {};
class_jdoubleArray : public_jarray {};
class_jthrowable : public_jobject {};
 
typedef_jobject*       jobject;
typedef_jclass*        jclass;
typedef_jstring*       jstring;
typedef_jarray*        jarray;
typedef_jobjectArray*  jobjectArray;
typedef_jbooleanArray* jbooleanArray;
typedef_jbyteArray*    jbyteArray;
typedef_jcharArray*    jcharArray;
typedef_jshortArray*   jshortArray;
typedef_jintArray*     jintArray;
typedef_jlongArray*    jlongArray;
typedef_jfloatArray*   jfloatArray;
typedef_jdoubleArray*  jdoubleArray;
typedef_jthrowable*    jthrowable;
typedef_jobject*       jweak;
 
#else /* not __cplusplus */
 
/*
 * Reference types, in C.
 */
typedefvoid*           jobject;
typedefjobject         jclass;
typedefjobject         jstring;
typedefjobject         jarray;
typedefjarray          jobjectArray;
typedefjarray          jbooleanArray;
typedefjarray          jbyteArray;
typedefjarray          jcharArray;
typedefjarray          jshortArray;
typedefjarray          jintArray;
typedefjarray          jlongArray;
typedefjarray          jfloatArray;
typedefjarray          jdoubleArray;
typedefjobject         jthrowable;
typedefjobject         jweak;
 
#endif /* not __cplusplus */

我们经常用到的是JNIEnv*,它是一个c结构体,封装了许多常用的函数,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct_JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    conststructJNINativeInterface* functions;
 
#if defined(__cplusplus)
 
    jint GetVersion()
    { returnfunctions->GetVersion(this); }
 
    jclass DefineClass(constchar*name, jobject loader, constjbyte* buf,
        jsize bufLen)
    { returnfunctions->DefineClass(this, name, loader, buf, bufLen); }
 
    jclass FindClass(constchar* name)
    { returnfunctions->FindClass(this, name); }
// 这里省略其他函数...
 
}

cocos2d-x引擎对jni的操作进行了封装,提供了一个非常好用的类:JniHelper,定义了一些常用的接口,该文件位于cocos2dx/platform/android/jni目录下。下面看看JniHelper.h源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedefstructJniMethodInfo_
{
    JNIEnv *    env;
    jclass      classID;
    jmethodID   methodID;
} JniMethodInfo;
 
classCC_DLL JniHelper
{
public:
    staticJavaVM* getJavaVM();
    staticvoidsetJavaVM(JavaVM *javaVM);
    staticconstchar* getExternalAssetPath();
    staticvoidsetExternalAssetPath(constchar* externalAssetPath);
    staticjclass getClassID(constchar*className, JNIEnv *env=0);
    staticboolgetStaticMethodInfo(JniMethodInfo &methodinfo, constchar*className, constchar*methodName, constchar*paramCode);
    staticboolgetMethodInfo(JniMethodInfo &methodinfo, constchar*className, constchar*methodName, constchar*paramCode);
    staticstd::string jstring2string(jstring str);
 
private:
    staticJavaVM *m_psJavaVM;
    staticstd::string m_externalAssetPath;
};

下面来解释JniHelper的两个常用函数:
(1)getStaticMethodInfo
用来判断Java的类静态函数是否存在,并初始化结构体JniMethodInfo,该结构体封装了JNIEnv*和java.lang.Class对象、函数ID。这样就可以使用JNIEnv*调用 CallStaticXXXMethod(jclass clazz, jmethodID methodID, …)和 CallXXXMethod(jobject obj, jmethodID methodID, …)等常用函数(XXX替换为函数返回值类型,如:Void,Int等)。
第一个参数为JniMethodInfo,第二个参数是类的绝对路径,第三个参数是函数名,第四个参数是函数签名(参数和返回类型),示例代码如下:

1
2
3
4
if(JniHelper::getStaticMethodInfo(t, CLASS_NAME, "showTipDialog", "(Ljava/lang/String;Ljava/lang/String;)V"))
{
//...
}

关于类型签名,请对照下图:

(2)getMethodInfo
该函数与getStaticMethodInfo类似,用于Java类的非静态函数。

2. 下面开始实现文章开头所述的两个功能,本文是在cocos2d-x 2.0版本 自适应屏幕分辨率demo的基础上添加的。
(1)利用cocos2d-x创建一个Android工程,名为JniTest,包名为com.alexzhou.jni,此时该包下会自动生成一个JniTest.java文件。
(2)首先来实现把应用程序的包名传递给c++函数,在包下创建JniTestHelper.java,该类封装了给c++调用的函数,添加如下代码:

1
2
3
4
5
6
7
8
privatestaticHandler mHandler;
 
publicstaticvoidinit(Handler handler)
{
    JniTestHelper.mHandler = handler;
}
 
publicstaticnativevoidsetPackageName(String packageName);

(3)打开JniTest.java,在onCreate函数中添加下面的代码:

1
2
3
4
5
protectedvoidonCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        JniTestHelper.init(mHandler);
        JniTestHelper.setPackageName(this.getPackageName());
    }

(4)java层的代码已经完成了,下面来编写jni层代码,在/jni/hellocpp/下创建test.h和test.cpp文件,test.h文件暂时不添加任何函数,代码如下:
test.h

1
2
3
4
#ifndef TEST_H
#define TEST_H
 
#endif

test.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "cocos2d.h"
#include <jni.h>
#include "platform/android/jni/JniHelper.h"
#include "test.h"
#include "JniTest.h"
 
#define CLASS_NAME "com/alexzhou/jni/JniTestHelper"
 
usingnamespacecocos2d;
 
extern"C"
{
 
voidJava_com_alexzhou_jni_JniTestHelper_setPackageName(JNIEnv *env, jobject thiz, jstring packageName)
{
    constchar*pkgName = env->GetStringUTFChars(packageName, NULL);
    setPackageName(pkgName);
    env->ReleaseStringUTFChars(packageName, pkgName);
}
 
}

必须加上extern “C”,声明以c语言的方式进行编译,因为c++和c在编译时生成的函数签名不一样,可以在网上查找相关资料,不然运行的时候会出现链接错误。
(5)现在编写c++函数,在Classes目录下创建JniTest.h,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef JNI_TEST_H
#define JNI_TEST_H
 
#include "cocos2d.h"
 
usingnamespacecocos2d;
 
voidsetPackageName(constchar*packageName)
{
    CCLog("packageName: %s", packageName); 
}
 
#endif

(6)修改jni/Android.mk文件的LOCAL_SRC_FILES值 ,内容如下:

1
2
LOCAL_SRC_FILES := hellocpp/main.cpp \
                   hellocpp/test.cpp

(7)编译运行,因为我是使用cygwin编译的,而且Android项目不在cocos2d-x的根目录下,所以需要修改build_native.sh,修改COCOS2DX_ROOT和NDK_MODULE_PATH的值,把当前cocos2d-x项目的路径添加到NDK_MODULE_PATH,修改后的值:

1
2
3
COCOS2DX_ROOT="/cygdrive/e/cocos2d-x/cocos2d-2.0-x-2.0.4"
 
"NDK_MODULE_PATH=${COCOS2DX_ROOT}:${COCOS2DX_ROOT}/cocos2dx/platform/third_party/android/prebuilt:${APP_ROOT}"

运行结果:

(8)现在来实现通过c++函数调用java层函数,显示一个对话框。在JniTestHelper.java添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
publicstaticnativevoidexitApp();
 
privatestaticvoidshowTipDialog(finalString title, finalString text)
{
    Message msg = mHandler.obtainMessage();
    msg.what = JniTest.SHOW_DIALOG;
    DialogMessage dm = newDialogMessage();
    dm.title = title;
    dm.msg = text;
    msg.obj = dm;
    msg.sendToTarget();
}

(9)创建一个DialogMessage.java,封装dialog要显示的数据。

1
2
3
4
5
6
7
8
9
10
11
/**
author:alexzhou
email :zhoujiangbohai@163.com
date  :2012-12-14
 **/
 
publicclassDialogMessage {
 
    publicString title;
    publicString msg;
}

(10) 修改JniTest.java,添加显示对话框的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
publicstaticfinalintSHOW_DIALOG = 0x0001;
 
   privateHandler mHandler = newHandler()
    {
        @Override
        publicvoidhandleMessage(Message msg) {
            switch(msg.what)
            {
            caseSHOW_DIALOG:
                DialogMessage dm = (DialogMessage)msg.obj;
                newAlertDialog.Builder(JniTest.this)
                .setTitle(dm.title)
                .setMessage(dm.msg).setNegativeButton("cancle", newDialogInterface.OnClickListener() {
 
                    @Override
                    publicvoidonClick(DialogInterface dialog, intwhich) {
                        dialog.dismiss();
                    }
                })
                .setPositiveButton("Ok",
                        newDialogInterface.OnClickListener() {
 
                    @Override
                    publicvoidonClick(DialogInterface dialog, intwhich) {
                        dialog.dismiss();
                        JniTestHelper.exitApp();
                    }
                })
                .create().show();
                break;
            }
        }
    };

(11)在test.h和test.cpp中添加显示对话框的接口:
test.h

1
2
3
4
extern"C"
{
voidshowTipDialog(constchar*title, constchar*msg);
}

test.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
extern"C"
{
voidshowTipDialog(constchar*title, constchar*msg)
{
    JniMethodInfo t;
    if(JniHelper::getStaticMethodInfo(t, CLASS_NAME, "showTipDialog", "(Ljava/lang/String;Ljava/lang/String;)V"))
    {
        jstring jTitle = t.env->NewStringUTF(title);
        jstring jMsg = t.env->NewStringUTF(msg);
        t.env->CallStaticVoidMethod(t.classID, t.methodID, jTitle, jMsg);
        t.env->DeleteLocalRef(jTitle);
        t.env->DeleteLocalRef(jMsg);
    }
}
 
voidJava_com_alexzhou_jni_JniTestHelper_setPackageName(JNIEnv *env, jobject thiz, jstring packageName)
{
    constchar*pkgName = env->GetStringUTFChars(packageName, NULL);
    setPackageName(pkgName);
    env->ReleaseStringUTFChars(packageName, pkgName);
}
 
voidJava_com_alexzhou_jni_JniTestHelper_exitApp(JNIEnv *env, jobject thiz)
{
    exitApp();
}
 
}

(12) 修改Classes目录下的JniTest.h,添加代码:

1
2
3
4
voidexitApp()
{
    CCDirector::sharedDirector()->end();
}

(13)到此为止,所有代码都已经完成了,代码比较简单就不详细解释了,现在编译运行,效果如下:

源码下载地址:http://download.csdn.net/detail/u013388439/9459321


转载于:https://my.oschina.net/u/2276921/blog/636217

# Android录音异常

阅读数 5115