当前位置:  编程技术>移动开发
本页文章导读:
    ▪spring中的引语相关解释        spring中的注解相关解释 Spring在Action上面注解@Scope("prototype")表示每次接收一个请求创建一个Action对象.@Scope("singleton")单例模式,表示很多请求公用一个Action ......
    ▪ dip组织的使用心得        dip单位的使用心得 举个例子区别px和dip:px就是像素,如果用px,就会用实际像素画,比个如吧,用画一条长度为240px的横线,在480宽的模拟器上看就是一半的屏宽,而在320宽的模拟器上看就是.........
    ▪ app 轻巧实现中文语音智能播报, 不必依赖本地引擎       app 轻松实现中文语音智能播报, 不必依赖本地引擎    Android系统从1.6版本开始就支持TTS(Text-To-Speech),也就是我们所说的语音合成,不过遗憾的是系统默认的TTS引擎:Pico TTS,并不支持中文.........

[1]spring中的引语相关解释
    来源: 互联网  发布时间: 2014-02-18
spring中的注解相关解释

Spring在Action上面注解@Scope("prototype")

表示每次接收一个请求创建一个Action对象.

@Scope("singleton")
单例模式,表示很多请求公用一个Action

    
[2] dip组织的使用心得
    来源: 互联网  发布时间: 2014-02-18
dip单位的使用心得

举个例子区别px和dip:
px就是像素,如果用px,就会用实际像素画,比个如吧,用画一条长度为240px的横线,在480宽的模拟器上看就是一半的屏宽,而在320宽的模拟器上看就是2/3的屏宽了。
而dip,就是把屏幕的高分成480分,宽分成320分。比如你做一条160dip的横线,无论你在320还480的模拟器上,都是一半屏的长度。

public static int dip2px(Context context, float dipValue){
                final float scale = context.getResources().getDisplayMetrics().density;
                return (int)(dipValue * scale + 0.5f);
        }
       
    public static int px2dip(Context context, float pxValue){
                final float scale = context.getResources().getDisplayMetrics().density;
                return (int)(pxValue / scale + 0.5f);
        }

 

 

总结:不管屏幕的分辨率是多少,都可以理解为:使用dip单位,都会把屏幕的高分成480分,宽分成320分,即(480x320 dip)。

只要你设置160dip,在任何分辨率的设备上都会占屏幕的一半。


    
[3] app 轻巧实现中文语音智能播报, 不必依赖本地引擎
    来源: 互联网  发布时间: 2014-02-18
app 轻松实现中文语音智能播报, 不必依赖本地引擎

 

 Android系统从1.6版本开始就支持TTS(Text-To-Speech),也就是我们所说的语音合成,不过遗憾的是系统默认的TTS引擎:Pico TTS,并不支持中文。

由此对于广大的炎黄子孙不得不安装我们自己的TTS引擎跟语言包,但中文数据量庞大, 一般语言包下载下来动辄几百M,甚至G级别. 对于我们android应用开发相当不现实, 不可能要求客户为了你的一个APP而安装如此庞大的中文引擎而不惜破费流量. 那么, 是否有其它方式呢? 

如果您经常使用google翻译 http://translate.google.cn/?hl=en, 你稍稍留意就会发现上面有个语音按钮, 语音效果还相当不错. 接下来, 我们谈谈怎么利用google来帮我们使app更加绘声绘影

我们结合目前实际项目来展开本

我所带领的团队目前正在开发的一个项目是移动餐厅, 涉及到餐饮订餐领域, 项目分为商家端与微信端,顾客通过微信点餐后,商家端语音提醒处理,顾客通过支付宝等方式买单,商家也会语音提醒,顾客呼叫服务员,服务员收到语音提醒, 接下来看看实现方式

首先, google翻译所使用的方式是将app下载到本地缓存为mp3文件, 然后播放mp3即可. 
思路很清晰, 找到google语音接口地址, 把需要播报的中文传进去, 返回来我们需要的mp3, 播放它.
接口地址:http://translate.google.cn/translate_tts?ie=UTF-8&q=xxx&tl=zh-CN&total=1&idx=0&textlen=8
只需要把xxx换成我们需要播报的中文即可,当然别忘了URLEncoder.encode(text) 转码
实际实现时, 我们碰到了几处问题, 比如多个语音文件如何使用单独线程边下载边按顺序播报.

我的思路是使用多线程设计模式中生产者消费者模式, 生产者负责mp3文件下载, 消费者发现队列中有mp3文件即进行播报, 播报完成后从队列中删除.

 

import java.net.URLEncoder;

import android.media.MediaPlayer;
import android.util.Log;

import com.gitom.framwork.util.FileUtil;
import com.gitom.framwork.util.HttpDownloader;

public class PlayerHelper {
	
	private VoiceQueue queue = new VoiceQueue();
	private VoicePlayer player = new VoicePlayer(queue);

	/**
	 * 播放器处理监听状态,等待queue中队列新数据
	 */
	public void start() {
		Thread tc = new Thread(player);
		tc.start();
	}

	/**
	 * 播放声音,可由线程压入
	 * 
	 * @param text
	 */
	public void play(String text) {
		VoiceDownloader downloader = new VoiceDownloader(queue);
		downloader.setSource(text);
		Thread tp = new Thread(downloader);
		tp.start();
	}
	
	public void setPlayEnabled(boolean playEnabled) {
		player.setPlayEnabled(playEnabled);
		
		if(playEnabled) {
			start();
		}
	}
	
}

//声音对象
class VoiceItem {
	private String text;

	public VoiceItem(String text) {
		this.text = text;
	}

	public String getText() {
		return text;
	}

	public String toString() {
		return "Voice :" + text;
	}
}

// 共享栈空间
class VoiceQueue {
	VoiceItem sm[] = new VoiceItem[6];
	int index = 0;

	/**
	 * 
	 * @param m
	 *            元素
	 * @return 没有返回值
	 */

	public synchronized void push(VoiceItem m) {
		try {
			while (index == sm.length) {
				System.out.println("!!!!!!!!!超过最大堆数量,执行等待,再压入!!!!!!!!!");
				this.wait();
			}
			this.notify();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (IllegalMonitorStateException e) {
			e.printStackTrace();
		}

		sm[index] = m;
		index++;
	}

	/**
	 * 
	 * @param b
	 *            true 表示显示,false 表示隐藏
	 * @return 没有返回值
	 */
	public synchronized VoiceItem pop() {
		try {
			while (index == 0) {
				System.out.println("!!!!!!!!!消费光了!!!!!!!!!");
				this.wait();
			}
			this.notify();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (IllegalMonitorStateException e) {
			e.printStackTrace();
		}
		index--;
		return sm[index];
	}
}

class VoiceDownloader implements Runnable {
	private String text;

	private VoiceQueue ss = new VoiceQueue();

	public VoiceDownloader(VoiceQueue ss) {
		this.ss = ss;
	}

	public void setSource(String string) {
		this.text = string;
	}
	
	/**
	 * show 生产进程.
	 */
	public void run() {
		while (true) {
			if (text != null) {
				final String fileName = text + ".mp3";
				HttpDownloader lo = new HttpDownloader();
				StringBuilder url = new StringBuilder();
				url.append("http://translate.google.cn/translate_tts?ie=UTF-8&q=");
				url.append(URLEncoder.encode(text));
				url.append("&tl=zh-CN&total=1&idx=0&textlen=8");
				lo.downFile(url.toString(), SoundUtils.dir, fileName);

				VoiceItem voice = new VoiceItem(text);

				System.out.println("准备播放:" + text);
				ss.push(voice);

				text = null;
			}
			
			// 在上面一行进行测试是不妥的,对index的访问应该在原子操作里,因为可能在push之后此输出之前又消费了,会产生输出混乱
			try {
				Thread.sleep((int) (Math.random() * 500));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

class VoicePlayer implements Runnable {
	private MediaPlayer mp = new MediaPlayer();
	private VoiceQueue ss = new VoiceQueue();
	private boolean playEnabled = true;

	public VoicePlayer(VoiceQueue ss) {
		this.ss = ss;
	}
	
	public void setPlayEnabled(boolean playEnabled) {
		this.playEnabled  = playEnabled;
	}

	/**
	 * show 消费进程.
	 */
	public void run() {
		while (playEnabled) {
			if (mp.isPlaying()) {
				// 有文件正在播放,则等待,至播放器状态空闲 继续播放, 播放器本身是异步播放
				try {
					Thread.sleep((int) (Math.random() * 1000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				continue;
			}

			VoiceItem m = ss.pop();

			final String fileName = m.getText() + ".mp3";
			try {
				boolean fileExist = FileUtil.isFileExist(SoundUtils.dir
						+ fileName);
				if (fileExist) {
					mp.stop();
					mp.reset();
					mp.setDataSource(FileUtil.getSDPATH() + SoundUtils.dir
							+ fileName);
					mp.prepare();
					mp.start();
				}
			} catch (Exception e) {
				Log.e("mediaPlayer", "error", e);
			}

			System.out.println("播放了:---------" + m.getText());
			// 同上 在上面一行进行测试也是不妥的,对index的访问应该在原子操作里,因为可能在pop之后此输出之前又生产了,会产生输出混乱
			try {
				Thread.sleep((int) (Math.random() * 1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

 
下载后保存至本地缓存中



 

 

然后通过一个静态类实现调用

import java.util.HashMap;
import java.util.Map;

import android.content.Context;

import com.gitom.framwork.xst.db.helper.AppHelper;
import com.gitom.framwork.xst.db.helper.NetworkHelper;

public class SoundUtils {

	private static PlayerHelper player = new PlayerHelper();

	private static Map<String, String> map = new HashMap<String, String>();
	static {
		map.put("号", "浩");
		map.put("x1", "1份");
		map.put("x2", "2份");
		map.put("x3", "3份");
		map.put("x4", "4份");
		map.put("x5", "5份");
		map.put("x6", "6份");
		map.put("x7", "7份");
		map.put("x8", "8份");
		map.put("x9", "9份");
		map.put("\n", "");

		player.start();
	}

	public static String dir = "catering/";

	public static void play(Context context, String argText) {
		if (!AppHelper.getInstance().isVoiceEnabled(context)) {
			return;
		}

		int status = NetworkHelper.getInstance().NetworkConnectStatus();
		if (status != 2) {
			// 仅 WIFI 下语音提示
		}

		final String text = replace(argText);

		player.play(text);
	}
	
	public static void setPlayEnabled(boolean playEnabled) {
		player.setPlayEnabled(playEnabled);
	}

	public static String replace(String argText) {
		String result = argText.replace(" ", "").toLowerCase().trim();
		for (String key : map.keySet()) {
			result = result.replace(key, map.get(key));
		}
		return result;
	}
}

 

上述代码中map并无特殊用途, 只是google在翻译时有时候可能偶尔单个字符不太准确 我们进行字符替换用, 或者特殊字符替换成语音用途字符

值得指出的是, google在线播报确实是一款强大的语音引擎, 帮我们省去了不少麻烦, 减小app体积, 当然有缺点所在, 比如下载需要网络, 上面的代码可以看出, 我们下载一次以后如果下次继续同样的播报内容不会再下载, 使用缓存即可, 当然mp3一般不会大,几十K对用户影响不大, 也可以设置为wifi下才启用.

 

以上为android代码, ios大体类似, 只是语法稍修改即可, 希望本文能帮助到大家, 让我们的app更加强大具有吸引力.


最后欢迎体验移动餐厅,如果有项目难题或项目需求, 可以和我探讨 QQ:285264911
http://app.gitom.com/mobileapp/list/12

 

 

 

 

 

 

 

 

 


    
最新技术文章:
▪Android开发之登录验证实例教程
▪Android开发之注册登录方法示例
▪Android获取手机SIM卡运营商信息的方法
▪Android实现将已发送的短信写入短信数据库的...
▪Android发送短信功能代码
Web服务器/前端 iis7站长之家
▪Android中GPS定位的用法实例
▪Android实现退出时关闭所有Activity的方法
▪Android实现文件的分割和组装
▪Android录音应用实例教程
▪Android双击返回键退出程序的实现方法
▪Android实现侦听电池状态显示、电量及充电动...
▪Android获取当前已连接的wifi信号强度的方法
▪Android实现动态显示或隐藏密码输入框的内容
▪根据USER-AGENT判断手机类型并跳转到相应的app...
▪Android Touch事件分发过程详解
▪Android中实现为TextView添加多个可点击的文本
▪Android程序设计之AIDL实例详解
▪Android显式启动与隐式启动Activity的区别介绍
▪Android按钮单击事件的四种常用写法总结
▪Android消息处理机制Looper和Handler详解
▪Android实现Back功能代码片段总结
▪Android实用的代码片段 常用代码总结
▪Android实现弹出键盘的方法
▪Android中通过view方式获取当前Activity的屏幕截...
▪Android提高之自定义Menu(TabMenu)实现方法
▪Android提高之多方向抽屉实现方法
▪Android提高之MediaPlayer播放网络音频的实现方法...
▪Android提高之MediaPlayer播放网络视频的实现方法...
▪Android提高之手游转电视游戏的模拟操控
 


站内导航:


特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

©2012-2021,,E-mail:www_#163.com(请将#改为@)

浙ICP备11055608号-3