Home | 简体中文 | 繁体中文 | 杂文 | Github | 知乎专栏 | Facebook | Linkedin | Youtube | 打赏(Donations) | About
知乎专栏

第 29 章 讯飞云

目录

29.1. AIUI
29.1.1. AIUIPlayer
29.1.2. 酷我音乐
29.1.3. 控制技能
29.1.4. 唤醒词
29.1.5. 汉字转拼音
29.2. 讯飞 TTS
29.2.1. 设置日志输出级别
29.2.2. 流式语音合成
29.3. 语音唤醒
29.3.1. 范例

29.1. AIUI

		
// 写入文本
//                byte[] content= "你好".getBytes();
//                String params = "data_type=text";
//                AIUIMessage msg = new AIUIMessage(AIUIConstant.CMD_WRITE, 0, 0, "tag=write_data_1", content);
//                mAIUIAgent.sendMessage(msg);

                AIUIMessage aiuiMessage = new AIUIMessage(0, 0, 0, "", null);
                aiuiMessage.msgType = AIUIConstant.CMD_WRITE;
                aiuiMessage.arg1 = 0;
                aiuiMessage.arg2 = 0;
                // 在输入参数中设置tag,则对应结果中也将携带该tag,可用于关联输入输出
                aiuiMessage.params = "data_type=text,tag=text-tag";
                aiuiMessage.data = "天气".getBytes(StandardCharsets.UTF_8);
                mAIUIAgent.sendMessage(aiuiMessage);		
		
	

29.1.1. AIUIPlayer

		
package cn.netkiller.aiui;

import android.content.Context;
import android.util.Log;

import androidx.annotation.NonNull;

import com.iflytek.aiui.player.common.data.MetaItem;
import com.iflytek.aiui.player.core.AIUIPlayer;
import com.iflytek.aiui.player.core.PlayState;
import com.iflytek.aiui.player.core.PlayerListener;
import com.iflytek.aiui.player.players.KuwoMusicRemote;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import kotlin.Unit;
import kotlin.jvm.functions.Function0;
import kotlin.jvm.functions.Function2;

public class MusicSkillComponent {
    private static final String TAG = MusicSkillComponent.class.getSimpleName();
    private static MusicSkillComponent musicSkillComponent = null;
    private static String kuwoParams = null;
    private static int playListIndex = 0; //当前播放歌曲序号
    private static JSONArray mPlayList = null; //播放列表
    private static AIUIPlayer mAIUIPlayer = null;
    private static KuwoMusicRemote kuwoRemote = null;
    private final Context context;
    private final PlayerListener mPlayListener = new PlayerListener() {
        @Override
        public void onMediaUrl(@NonNull JSONObject jsonObject) {
            Log.d(TAG, "onMediaUrl: " + jsonObject);
        }

        @Override
        public void onPlayerReady() {
            Log.i(TAG, "onPlayerReady()");
        }

        @Override
        public void onStateChange(@NonNull PlayState state) {
            Log.i(TAG, "onStateChange: PlayState is" + state);
            switch (state) {
                case READY:
                    Log.d(TAG, "AIUIPlayer 播放准备就绪");
                    break;
                case PLAYING:
                    Log.d(TAG, "播放");
                    break;
                case PAUSED:
                    Log.d(TAG, "停止");
                    break;
                case LOADING:
                    Log.d(TAG, "音乐加载中……");
                    break;
                case COMPLETE:
                    Log.d(TAG, "继续");
                    startPlayMusic();
                    break;
                case IDLE:
                    long currentPosition = mAIUIPlayer.getCurrentPosition();
                    Log.d(TAG, String.format("CurrentPosition: %l", currentPosition));
                    break;
                case ERROR:
                    Log.d(TAG, "播放出错");
                    break;
                default:
                    break;
            }
        }

        @Override
        public void onMediaChange(@NonNull MetaItem metaItem) {
            String songName = metaItem.getTitle();      //歌名
            String author = metaItem.getAuthor();      //作者
            playListIndex = getPlayIndex(songName);
            Log.d(TAG, "onMediaChange: " + metaItem);
        }

        @Override
        public void onError(int code, @NonNull String info) {
            Log.e(TAG, "onError 播放出错:" + code + " ,错误信息为:" + info);
            // 真实错误码需要从 info 中解析   "track link failed code: 40006 description:"
            if (info.isEmpty()) {
                if (code == 200001) {
                    Log.d(TAG, "歌曲\"" + "" + "\"播放出错: " + code + "\nINFO:产品未通过酷我验收,仅支持获取奇数id资源\n");
                    if (!mAIUIPlayer.next()) {
                    }
                }
            }

        }

        @Override
        public void onPlayerRelease() {

        }
    };

    public MusicSkillComponent(Context context) {
        this.context = context;
        initSDK();
    }

    public synchronized static MusicSkillComponent getInstance(Context context) {
        if (musicSkillComponent == null) {
            musicSkillComponent = new MusicSkillComponent(context);
        }
        return musicSkillComponent;
    }

    public boolean previous() {
        if (mAIUIPlayer != null) {
            return mAIUIPlayer.previous();
        }
        return false;
    }

    public boolean next() {
        if (mAIUIPlayer != null) {
            return mAIUIPlayer.next();
        }
        return false;
    }

    public void pause() {
        if (mAIUIPlayer == null) {
            return;
        }
        if (mAIUIPlayer.getCurrentState() == PlayState.PLAYING) {
            mAIUIPlayer.pause();
        }
    }

    public void resume() {
        if (mAIUIPlayer == null) {
            return;
        }
        if (mAIUIPlayer.getCurrentState() == PlayState.PAUSED) {
            mAIUIPlayer.resume();
        }
    }

    public boolean play(@NonNull JSONObject object) {

//        Log.d(TAG, object.toString());

        try {

            JSONArray musics = object.getJSONArray("result");
            if (null != musics) {
                mPlayList = musics;
                playListIndex = 0;
            }
            return startPlayMusic();

        } catch (JSONException e) {
            e.printStackTrace();
        }

        return false;
    }

    public void initSDK() {
        //TODO 开发者需要实现生成sn的代码,参考:https://www.yuque.com/iflyaiui/zzoolv/tgftb5
        //注意事项1: sn每台设备需要唯一!!!!WakeupEngine的sn和AIUI的sn要一致
        //注意事项2: 获取的值要保持稳定,否则会重复授权,浪费授权量
        String serialNumber = "test";
        String appId = "c84e1ddb";
        String appKey = "7a1583c3190b83fe4d62573ee9cfbfc1";

        //TODO 设置酷我音乐SDK设置相关参数,appid和appkey请使用自己的进行开发,并且与aiui.cfg一致
        kuwoParams = "appId=" + appId + ",appKey=" + appKey + "," + "serialNumber=" + serialNumber + ",deviceModel=" + serialNumber + ",userId=" + serialNumber;


        if (null == kuwoRemote) {
            kuwoRemote = new KuwoMusicRemote(kuwoParams);
            // 酷我SDK日志开关 :true 打开,false 关闭
            kuwoRemote.setDebug(false);

            if (null != kuwoRemote) {
                Log.i(TAG, "KuwoRemote 初始化成功");
            }

        }

        if (null == mAIUIPlayer) {
            mAIUIPlayer = new AIUIPlayer(context, kuwoParams);
            // 播放前焦点占用设置
            mAIUIPlayer.setParameter("customAudioFocus", "true");
            // 回调信息设置
            mAIUIPlayer.addListener(mPlayListener);
            // AIUIPlayer SDK调试日志设置 :true 打开,false 关闭
            mAIUIPlayer.setDebug(false);
            // 初始化播放器
            mAIUIPlayer.initialize();
            Log.i(TAG, "AIUIPlayer 初始化成功");

        }
    }

    public void release() {
        mPlayList = null;
        if (null != mAIUIPlayer) {
            mAIUIPlayer.release();
            mAIUIPlayer = null;
        }
        if (null != kuwoRemote) {
            kuwoRemote.destroy();
            kuwoRemote = null;
        }
    }

    private void activate() {
//        if (isActivated) {
////            showToast("当前设备已激活酷我音乐");
//            return;
//        }
        kuwoRemote.active(() -> {
            Log.i(TAG, "激活成功");
            return null;
        }, (errCode, errInfo) -> {
            Log.i(TAG, "激活失败,错误码为:" + errCode + " ,信息为: " + errInfo);
            return null;
        });
    }

    private void login() {
        //酷我不强制用户登陆,开发者自己实现登陆代码,可参考下方酷狗音乐代码,改一下接口
//        Intent intent = new Intent(KuwoDemo.this, LoginActivity.class);
//        startActivityForResult(intent, 3);
    }

    private void logout() {
        kuwoRemote.logout(new Function0<Unit>() {
            @Override
            public Unit invoke() {
                Log.i(TAG, "酷我账号退出成功");
//                LoginBtn.setText("手机登陆");
                return null;
            }
        }, new Function2<Integer, String, Unit>() {
            @Override
            public Unit invoke(Integer errCode, String errInfo) {
                Log.i(TAG, "酷我账号退出失败,错误码为:" + errCode + " ,信息为: " + errInfo);
                return null;
            }
        });
    }

    public void stop() {
        if (mAIUIPlayer != null) {
            mAIUIPlayer.stop();
        }
    }

    private boolean startPlayMusic() {
        if (null == mPlayList || mPlayList.length() == 0) {
            Log.w(TAG, "播放列表为空");
            return false;
        }
        Log.i(TAG, "mPlayList is: " + mPlayList.toString());
        mAIUIPlayer.reset();
        return mAIUIPlayer.play(mPlayList, "musicX", "", false, playListIndex);
    }

    //构建虚假播放信息测试AIUIPlayer SDK播放是否可以正常调用
//    private void mockPlayMusic() {
//        try {
//            JSONArray musiclist = new JSONArray();
//            JSONObject music = new JSONObject();
//            music.put("source", "kuwo");
//            music.put("songname", "天地龙鳞");
//            music.put("itemid", "353833243");
//            musiclist.put(0, music);
//            mPlayList = musiclist;
//        } catch (JSONException e) {
//            e.printStackTrace();
//        }
//
//        if (null == mPlayList || mPlayList.length() == 0) {
////            showToast("播放列表为空");
//            return;
//        }
//        Log.i(TAG, "mPlayList is:\n" + mPlayList.toString());
//        mAIUIPlayer.reset();
//        mAIUIPlayer.play(mPlayList, "musicX", "", false, playListIndex);
//    }

    // 获取当前播放下标
    private int getPlayIndex(String songName) {
        if (mPlayList != null) {
            try {
                String playData = null;
                for (int i = 0; i < mPlayList.length(); i++) {
                    playData = mPlayList.getJSONObject(i).toString();
                    if (playData.contains(songName)) {
                        return i;
                    }
                }
            } catch (JSONException e) {
                e.printStackTrace();
                return 0;
            }
        }
        return 0;
    }
}		
		
		

29.1.2. 酷我音乐

29.1.2.1. 获取音乐URL

通过 itemId 获取 audio URL

			
    kuwoRemote.getAudioUrl("26383685", "128kmp3", new Function1<AudioUrl, Unit>() {
        @Override
        public Unit invoke(AudioUrl audioUrl) {
            Log.d(TAG, audioUrl.toString());
            return null;
        }
    }, new Function2<Integer, String, Unit>() {
        @Override
        public Unit invoke(Integer code, String msg) {
            Log.d(TAG, "Code: " + code + ", Msg: " + msg);
            return null;
        }
    });		
			
			

日志输出结果

			
AudioUrl(expiretime=, itemid=26383685, source=kuwo, audiopath=http://other.player.ri01.sycdn.kuwo.cn/6dcbdb125c72dfff3978fe29ab50fdd4/64eeef6f/resource/n2/27/48/2060696053.mp3)			
			
			

29.1.3. 控制技能

		
package cn.netkiller.skill;

import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.util.Log;

import com.alibaba.fastjson.JSONObject;

import java.text.NumberFormat;
import java.text.ParseException;

public class ControlSkillComponent {
    private static final String TAG = ControlSkillComponent.class.getName();
    private final Context context;
    private MusicSkillComponent musicSkillComponent = null;

    public ControlSkillComponent(Context context, JSONObject object) {
        this.context = context;
        musicSkillComponent = MusicSkillComponent.getInstance(context);
        Log.d(TAG, "控制技能: " + object);
        String semanticIntent = object.getString("intent");
        switch (semanticIntent) {
            case "PAUSE":
                musicSkillComponent.pause();
                break;
            case "CHOOSE_NEXT":
                musicSkillComponent.next();
                break;
            case "CHOOSE_PREVIOUS":
                musicSkillComponent.previous();
                break;
            case "VOLUME_MINUS":
                this.volume("VOLUME_MINUS");
                break;
            case "VOLUME_PLUS":
                this.volume("VOLUME_PLUS");
                break;
            case "VOLUME_MIN":
                this.volume("VOLUME_MIN");
                break;
            case "VOLUME_MAX":
                this.volume("VOLUME_MAX");
                break;
            case "MUTE":
                this.volume("MUTE");
                break;
            case "VOLUME_SET":

                JSONObject slots = (JSONObject) object.getJSONArray("slots").get(0);
                String stringPercent = slots.getString("value");
                try {
                    double doublePercent = (Double) NumberFormat.getPercentInstance().parse(stringPercent);
//                    int percent = floatPercent.intValue();
                    this.volume(doublePercent);
                    Log.d(TAG, String.valueOf(doublePercent));
                } catch (ParseException e) {
                    Log.e(TAG, e.toString());
                }
                break;
            case "SETTING_OPEN":
                Log.e(TAG, "设置无线网络");
                this.wifi();
                break;
            case "SHUTDOWN":
                break;
            case "RESET":
                break;
        }


    }

    public void wifi() {
        //        context.startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
        Intent intent = new Intent();
        intent.setAction("androad.network.wlan");
        intent.putExtra("message", "");
        context.sendBroadcast(intent);
    }

    private void volume(String control) {
        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
//        int minVolume = audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
        int minVolume = 10;
        int stepVolume = 5;
        int currentMusicVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        int currentTTSVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM);


        switch (control) {
            case "VOLUME_MINUS": //步进减小
                currentMusicVolume -= stepVolume;
                if (currentMusicVolume < minVolume) {
                    currentMusicVolume = minVolume;
                }
                currentTTSVolume -= stepVolume;
                if (currentTTSVolume < minVolume) {
                    currentTTSVolume = minVolume;
                }
                break;
            case "VOLUME_PLUS": //步进累加
                currentMusicVolume += stepVolume;
                if (currentMusicVolume >= maxVolume) {
                    currentMusicVolume = maxVolume;
                }
                currentTTSVolume += stepVolume;
                if (currentTTSVolume > maxVolume) {
                    currentTTSVolume = maxVolume;
                }
                break;

            case "VOLUME_MAX": // 最大
                currentMusicVolume = currentTTSVolume = maxVolume;

                break;
            case "VOLUME_MIN": //最小
                currentMusicVolume = currentTTSVolume = minVolume;

                break;
            case "MUTE": //静音
                currentMusicVolume = currentTTSVolume = minVolume;
                break;

        }
        audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentMusicVolume, AudioManager.FLAG_SHOW_UI);
        audioManager.setStreamVolume(AudioManager.STREAM_ALARM, currentTTSVolume, AudioManager.FLAG_PLAY_SOUND);
        Log.d(TAG, String.format("volume: currentMusicVolume=%s, currentTTSVolume=%s, maxVolume=%s", currentMusicVolume, currentTTSVolume, maxVolume));
    }

    private void volume(double percent) {
        if (percent < 0.3) {
            return;
        }
        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        int currentMusicVolume, currentTTSVolume;
        currentMusicVolume = currentTTSVolume = (int) (maxVolume * percent);
        audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentMusicVolume, AudioManager.FLAG_SHOW_UI);
        audioManager.setStreamVolume(AudioManager.STREAM_ALARM, currentTTSVolume, AudioManager.FLAG_PLAY_SOUND);
        Log.d(TAG, String.format("volume: currentMusicVolume=%s, currentTTSVolume=%s, maxVolume=%s", currentMusicVolume, currentTTSVolume, maxVolume));
    }
}		
		
		

29.1.4. 唤醒词

29.1.4.1. 手工唤醒

			
AiuiEngine.MSG_wakeup(EngineConstants.WAKEUPTYPE_VOICE);			
			
			

29.1.5. 汉字转拼音

		
implementation 'com.belerweb:pinyin4j:2.5.1'		
		
		
		
package cn.netkiller.ai.utils;

import android.util.Log;

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;

public class Pinyin {
    private static final String TAG = Pinyin.class.getName();

    public static String toPinyin(String hanzi) {
        char[] chars = hanzi.trim().toCharArray();
        String hanyupinyin = "";

        //输出格式设置
        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
        /**
         * 输出大小写设置
         *
         * LOWERCASE:输出小写
         * UPPERCASE:输出大写
         */
        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);

        /**
         * 输出音标设置
         *
         * WITH_TONE_MARK:直接用音标符(必须设置WITH_U_UNICODE,否则会抛出异常)
         * WITH_TONE_NUMBER:1-4数字表示音标
         * WITHOUT_TONE:没有音标
         */
//        defaultFormat.setToneType(HanyuPinyinToneType.WITH_TONE_MARK); //  必须设置WITH_U_UNICODE,否则会抛出异常
        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);

        /**
         * 特殊音标ü设置
         *
         * WITH_V:用v表示ü
         * WITH_U_AND_COLON:用"u:"表示ü
         * WITH_U_UNICODE:直接用ü
         */
//        defaultFormat.setVCharType(HanyuPinyinVCharType.WITH_U_UNICODE);
        defaultFormat.setVCharType(HanyuPinyinVCharType.WITH_V);

        // 中文的正则表达式
        String hanziRegex = "[\\u4E00-\\u9FA5]+";

        try {
            for (int i = 0; i < chars.length; i++) {
                // 判断为中文,则转换为汉语拼音
                if (String.valueOf(chars[i]).matches(hanziRegex)) {
                    hanyupinyin += PinyinHelper
                            .toHanyuPinyinStringArray(chars[i], defaultFormat)[0];
                } else {
                    // 不为中文,则不转换
                    hanyupinyin += chars[i];
                }
            }
        } catch (BadHanyuPinyinOutputFormatCombination e) {
            Log.e(TAG, "字符不能转成汉语拼音");
        }

        return hanyupinyin;
    }

}