这是个来自日常生活的需求。我个人喜欢在洗澡的时候带着手机随机播放歌曲,第一首往往还好,因为是自己选好播放的。但是后面往往不会每次都随机到我满意的歌曲,这就是个很尴尬的时候。全身湿透了,用手切歌太麻烦,于是就有了标题的需求——语音控音乐播放器。

这个需求就俩个目标:

  1. 解放双手,语音唤醒程序进行不同的操作
  2. 操作网易云音乐,获得下一首、上一首、播放、暂停的操作

第一个目标还算比较简单,市场上的产品如讯飞语音,百度语音等都有语音唤醒的SDK,直接接过来即可。因为讯飞好像要钱,我这里就直接使用的百度的语音唤醒SDK。

第二个目标就是控制第三方的播放器 ,这个点说难也不难,但是的确花费了我很多时间去寻找控制方法。下面是我找的几个解决方案,最后我还是使用了模拟线控的方式去做这件事。因为现在的音乐APP基本上都会支持线控,那么我只需要能够模拟出线控操作,就可以实现我的功能。

 http://www.cnblogs.com/xinye/archive/2012/06/06/music_info.html

http://blog.sina.com.cn/s/blog_14a226ae20102wthi.html

http://blog.csdn.net/qq_26440221/article/details/71512648

https://dev.mi.com/doc/p=6298/

https://segmentfault.com/a/1190000000713535

线控这里有个不大不小的坑,那就是模拟线控的时候需要两个操作,down和up,缺一不可。另,不同的音乐APP可能设置的线控按键位置不同,这个需要自己去针对修改。

百度语音唤醒

  1. 在百度语音平台创建应用,设置对应的服务,设置指定的安卓包名称
  2. 下载对应功能的SDk
  3. 添加SDK文件、so文件及配置
    1. bdasr_V3_20170801_60da871.jar Jar文件复制到工程libs中,添加引用
    2. 【app\src\main】中的assets文件夹和jniLibs文件夹复制到工程同目录;assets中的WakeUp.bin文件是语音唤醒词文件,需要根据自己的需要替换;可在http://yuyin.baidu.com/wake中自定义唤醒词并下载
    3. 设置AndroidManifest.xml 文件(可在http://yuyin.baidu.com/docs/asr/186中查看)
      1. 权限:
        <uses-permission android:name="android.permission.RECORD_AUDIO" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.READ_PHONE_STATE" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
      2. app_id,app_key,app_secret:在应用中可以查找到
                <!-- 请填写真实的APP_ID API_KEY SECRET_KEY-->
                <meta-data
                    android:name="com.baidu.speech.APP_ID"
                    android:value="你的APPID" />
                <!-- 再次重复!!填写APP_ID 时请检查在官网的该应用内是否设置了包名。否则会导致唤醒词及离线功能无法使用。
                 本demo的包名是com.baidu.speech.recognizerdemo,在build.gradle文件中查看。 -->
                <!-- 正式发布时,请替换成您自己的appId 本demo的appId会不定时下线 -->
                <meta-data
                    android:name="com.baidu.speech.API_KEY"
                    android:value="你的APIKEY" />
                <meta-data
                    android:name="com.baidu.speech.SECRET_KEY"
                    android:value="你的SECRETKEY" />
      3. service:
        <service android:name="com.baidu.speech.VoiceRecognitionService" android:exported="false" />
  4. 代码实例:
    1. 实例化EventManager并且添加监听
      	private EventManager wakeup;
      	protected Button btn;
      	protected Button stopBtn;
      	protected TextView txtLog;
      @Override
      	protected void onCreate(@Nullable Bundle savedInstanceState) {
      		super.onCreate(savedInstanceState);
      		setContentView(R.layout.activity2);
                      //界面控件
      		btn = (Button) findViewById(R.id.button4);
      		stopBtn = (Button) findViewById(R.id.button5);
      		txtLog = (TextView) findViewById(R.id.textView);
                      //实例化EventManager并且添加监听
      		wakeup = EventManagerFactory.create(this,"wp");
      		wakeup.registerListener(this);
      
      		btn.setOnClickListener(new View.OnClickListener() {
      
      			@Override
      			public void onClick(View v) {
                                      //开始监听唤醒词
      				start();
      			}
      		});
      		stopBtn.setOnClickListener(new View.OnClickListener() {
      
      			@Override
      			public void onClick(View v) {
                                     //停止监听
      				stop();
      			}
      		});
                      //android 6.0 以上需要动态申请权限
      		initPermission();
      	}
    2. 开启监听:
      	private void start() {
      		Map<String, Object> params = new LinkedHashMap<String, Object>();
      		params.put(SpeechConstant.ACCEPT_AUDIO_VOLUME, false);
      		params.put(SpeechConstant.WP_WORDS_FILE, "assets:///WakeUp.bin");
      		String json = null; // 这里可以替换成你需要测试的json
      		json = new JSONObject(params).toString();
      		wakeup.send(SpeechConstant.WAKEUP_START, json, null, 0, 0);
      		txtLog.append("canshu:"+json+"\n");
      	}
    3. 关闭监听:
      	private void stop() {
      		wakeup.send(SpeechConstant.WAKEUP_STOP, null, null, 0, 0); //
      	}
    4. 动态申请权限:
      	/**
      	 * android 6.0 以上需要动态申请权限
      	 */
      	private void initPermission() {
      		String permissions[] = {Manifest.permission.RECORD_AUDIO,
      				Manifest.permission.ACCESS_NETWORK_STATE,
      				Manifest.permission.INTERNET,
      				Manifest.permission.READ_PHONE_STATE,
      				Manifest.permission.WRITE_EXTERNAL_STORAGE
      		};
      
      		ArrayList<String> toApplyList = new ArrayList<String>();
      
      		for (String perm :permissions){
      			if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, perm)) {
      				toApplyList.add(perm);
      				//进入到这里代表没有权限.
      
      			}
      		}
      		String tmpList[] = new String[toApplyList.size()];
      		if (!toApplyList.isEmpty()){
      			ActivityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123);
      		}
      
      	}
    5. 最后,最重要的唤醒回调函数:
      	//   EventListener  回调方法
      	@Override
      	public void onEvent(String name, String params, byte[] data, int offset, int length) {
      		String logTxt = "name: " + name;
      		if (params != null && !params.isEmpty()) {
      			logTxt += " ;params :" + params;
      			try {
      				JSONObject json = new JSONObject(params);
      				if("wp.data".equals(name)){
      					String wakeWord = json.getString("word");
      					if (!wakeWord.isEmpty()&&wakeWord.equals("下一首"))
      					{
      						musicControl(KeyEvent.KEYCODE_HEADSETHOOK);
      						musicControl(KeyEvent.KEYCODE_HEADSETHOOK);
      					}
      					if (!wakeWord.isEmpty()&&wakeWord.equals("上一首"))
      					{
      						musicControl(KeyEvent.KEYCODE_HEADSETHOOK);
      						musicControl(KeyEvent.KEYCODE_HEADSETHOOK);
      						musicControl(KeyEvent.KEYCODE_HEADSETHOOK);
      					}
      					if (!wakeWord.isEmpty()&&wakeWord.equals("播放"))
      					{
      						musicControl(KeyEvent.KEYCODE_HEADSETHOOK);
      					}
      					if (!wakeWord.isEmpty()&&wakeWord.equals("暂停"))
      					{
      						musicControl(KeyEvent.KEYCODE_HEADSETHOOK);
      					}
      					if (!wakeWord.isEmpty()&&wakeWord.equals("百度一下"))
      					{
      						musicControl(KeyEvent.KEYCODE_HEADSETHOOK);
      					}
      				}
      			} catch (JSONException e) {
      				e.printStackTrace();
      			}
      		} else if (data != null) {
      			logTxt += " ;data length=" + data.length;
      		}
      		txtLog.append("Result:"+logTxt+"\n");
      	}
  5. 到此百度语音唤醒部分已经完成,剩下的就是根据不同的唤醒词去模拟不同的线控操作了

模拟线控

  1. 先看下网易云音乐是如何使用线控的,对应的KeyEvent就是KEYCODE_HEADSETHOOK,所以我们只需要模拟这一个按键就可以了。模拟双击操作的时候,需要在KeyEvent中添加eventTime。
  2. 模拟按下,因为需要双击,这里就直接将时间添加进去:
    		Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
    		//模拟单击
    		//KeyEvent keyEvent = new KeyEvent (KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_HEADSETHOOK) ;
                    //模拟双击操作需要添加evnetTime
    		long eventTime = SystemClock.uptimeMillis();
    		KeyEvent keyEvent = new KeyEvent(eventTime,eventTime,KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_HEADSETHOOK,0,0);
    		intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
    		sendBroadcast(intent);
  3. 模拟抬起也是一样的操作,改变一个ACTIONKEY就可以了
  4. 模拟线控方法:
    	private void musicControl(int eventKey)
    	{
    		Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
    		//模拟单击
    		//KeyEvent keyEvent = new KeyEvent (KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_HEADSETHOOK) ;
    		//模拟双击操作需要添加evnetTime
    		long eventTime = SystemClock.uptimeMillis();
    		KeyEvent keyEvent = new KeyEvent(eventTime,eventTime,KeyEvent.ACTION_DOWN,eventKey,0,0);
    		intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
    		sendBroadcast(intent);
    
    		Intent intent2 = new Intent(Intent.ACTION_MEDIA_BUTTON);
    		//模拟单击
            //KeyEvent keyEvent2 = new KeyEvent (KeyEvent.ACTION_UP,KeyEvent.KEYCODE_HEADSETHOOK) ;
    		//模拟双击操作需要添加evnetTime
    		KeyEvent keyEvent2 = new KeyEvent(eventTime,eventTime,KeyEvent.ACTION_UP,eventKey,0,0);
    		intent2.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent2);
    		sendBroadcast(intent2);
    	}

     

到此,两个部分都已经完成,打包测试。

在完成功能以后,我突然想到手机在同时播放音乐的同时去监听唤醒词,会不会因为本身音乐的干扰而识别不出。实际上是有影响的,想要提高唤醒成功率,只能人为的靠近手机去呐喊了W( ̄_ ̄)W