这里只是功能实现(个人非android开发),可能有很多更好的实现方式,该功能的开发是之前看到过阿里的实时语音转文字的接口,当时就想把这个功能做到手机上,自己又是java开发,就百度了点基础的android知识做了个简单的实现。

主方法(手机端),主要任务采集声音,流形式发送到后台

package com.hht.myapplication;

import android.Manifest;
import android.text.ClipboardManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * 语音小助手
 * hht
 */
public class MainActivity extends AppCompatActivity {
    //接收转换的文字流
    DataInputStream dis;
    //音频流上传通道
    OutputStream ous;

    String serverIp = "101.201.XXX.XXX";
    int serverPort = 5555;

    private TextView realText;
    private TextView finalText;

    //音频相关
    AudioRecord audioRecord=null;
    int bufferSize=0;//最小缓冲区大小
    int sampleRateInHz = 16000;//采样率
    int channelConfig = AudioFormat.CHANNEL_IN_DEFAULT; //单声道
    int audioFormat = AudioFormat.ENCODING_PCM_16BIT; //量化位数
    private boolean isRecording = true;
    private Handler handler=null;


    private  String content ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        realText = (TextView) findViewById(R.id.textView2);
        finalText = (TextView) findViewById(R.id.textView);
        finalText.setMovementMethod(ScrollingMovementMethod.getInstance()) ;
        handler = new Handler();

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)  != PackageManager.PERMISSION_GRANTED)  {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO},1);
        } else {
            init();
        }
    }

    public void onClickCopy(View v) {
        // 从API11开始android推荐使用android.content.ClipboardManager
        // 为了兼容低版本我们这里使用旧版的android.text.ClipboardManager,虽然提示deprecated,但不影响使用。
        ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
        // 将文本内容放到系统剪贴板里。
        cm.setText(finalText.getText());
        Toast.makeText(this, "复制成功,可以发给朋友们了。", Toast.LENGTH_LONG).show();
    }


    public void init(){
        System.out.println("初始化录音");
        bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz,channelConfig, audioFormat)+1000;//计算最小缓冲区
        try{
            audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,sampleRateInHz,channelConfig, audioFormat, bufferSize);//创建AudioRecorder对象

            Runnable startRunnable = new Runnable(){
                @Override
                public void run() {
                    connect();
                    new sendDataThread().start();
                    new getDataThread().start();
                }
            };
            new Thread(startRunnable).start();
        }catch (Exception e){
            e.printStackTrace();
        }

        System.out.println("初始化录音成功");
    }


    public void connect(){
        try{
            Socket socket = new Socket(serverIp,serverPort);
            InputStream is=socket.getInputStream();
            ous = socket.getOutputStream();
            dis=new DataInputStream(is);
        }catch(IOException e){
            e.printStackTrace();
        }
    }




    //发送数据
    class sendDataThread extends Thread {
        public void run() {
            byte[] buffer = new byte[bufferSize];
            audioRecord.startRecording();//开始录音
            int r = 0;

            try {
                while (isRecording&&audioRecord.read(buffer,0,bufferSize)>0) {
                    ous.write(buffer);
                    ous.flush();
                }
                audioRecord.stop();//停止录音
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    //接收数据
    class getDataThread extends Thread {
        public void run() {
            while (true) {
                String msg;
                try {
                    msg = dis.readUTF();
                    if (!"status".equals(msg)&&!"".equals(msg)) {//status 为心跳检测
                        content=msg;
                        handler.post(runnableUi);

                    }
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    // 构建Runnable对象,在runnable中更新界面
    Runnable   runnableUi=new  Runnable(){
        @Override
        public void run() {
            //更新界面
            realText.setText(content);
            finalText.append(content);
            //跳转到底部
            int offset=finalText.getLineCount()*finalText.getLineHeight();
            if(finalText.getLineCount()>15 && offset>finalText.getHeight()){
                finalText.scrollTo(0,offset-finalText.getHeight());
            }
        }
    };

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case 1: {
                if (grantResults.length > 0  && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // 权限被用户同意,可以去放肆了。
                    init();
                } else {
                    // 权限被用户拒绝了,洗洗睡吧。
                }
                return;
            }
        }
    }
}

服务器端,接收流信息上传阿里获取实时转换结果,这里应该有自己的控制策略,我这里只做了实现,策略没有哦

这部分代码是接收和处理客户端请求

package com.hmkx.freezingapi.rest.jkjweb;

import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hmkx.freezingapi.util.ali.RealtimeAsrDemo;

/**
 * 语音转换服务 监听
 * 
 * @author hht
 * @since 2018-06-12
 */
public class AudioChangeServerThread extends Thread {

	private static Logger log = LoggerFactory.getLogger(AudioChangeServerThread.class);
	static int cCount = 0;//当前连接数
	static int maxCount = 10;//最大连接数
	static ServerSocket server = null;
	static List<DataOutputStream>socketsOut = new ArrayList<DataOutputStream>();
	static List<Socket>sockets = new ArrayList<Socket>();
	public static boolean runStatus = false;//标记状态 关闭后不再接受连接
	
	
	public void run(){
		runStatus = true;
		if(server==null){//语音转换服务监听
			try {
				server = new ServerSocket(5555);
			} catch (IOException e) {		
				e.printStackTrace();
			}
			while (runStatus) {
				//每接受一个连接,清理半关闭的连接
				validateSocket();
				if(cCount<=maxCount){
					try {
						Socket temp = server.accept();
						sockets.add(temp);
						OutputStream os=temp.getOutputStream();
						DataOutputStream dos=new DataOutputStream(os);
						InputStream ins = temp.getInputStream();
						RealtimeAsrDemo lun = new RealtimeAsrDemo(ins,dos);
						log.error("audio change start ....");
						new Thread(lun).start();  
						cCount++;
					} catch (IOException e) {
						log.error("has connected io exception");
					}
					log.error("has connected "+cCount);
				}
			}
		}
	}
	
	/**
	 * 清楚已经断开的连接
	 * @param i
	 */
	public static void removeSocket(int i){
		Aclose(sockets.get(i));
		Aclose(socketsOut.get(i));
		sockets.remove(i);
		socketsOut.remove(i);
		cCount--;
	}
	/**
	 * 清楚已经断开的连接
	 * @param i
	 */
	public void validateSocket(){
		for(int i=0;i<socketsOut.size();i++){
			try {
				DataOutputStream temp = socketsOut.get(i);
				temp.writeUTF("status");
				temp.flush();
			} catch (Exception e) {
				removeSocket(i);
			}
		}
	}
	
	
	/**
	 * 连接关闭方法
	 * @param o
	 */
	public static  void Aclose(Object...o){
		for(int i=0;i<o.length;i++){
			if(o[i] instanceof Closeable){
				try {
					Closeable c = (Closeable)o[i];
					c.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(o[i] instanceof ServerSocket){
				try {
					ServerSocket c = (ServerSocket)o[i];
					c.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(o[i] instanceof Socket){
				try {
					Socket c = (Socket)o[i];
					c.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
}

下面这部分代码是处理流和阿里云服务接口的交互,用到阿里的包这个自行引入,(最近科大讯飞也提供了类似的接口,貌似效果更好)可以替换这部分逻辑就可以

package com.hmkx.freezingapi.util.ali;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.idst.nls.realtime.NlsClient;
import com.alibaba.idst.nls.realtime.NlsFuture;
import com.alibaba.idst.nls.realtime.event.NlsEvent;
import com.alibaba.idst.nls.realtime.event.NlsListener;
import com.alibaba.idst.nls.realtime.protocol.NlsRequest;
import com.alibaba.idst.nls.realtime.protocol.NlsResponse;
import com.hmkx.freezingapi.rest.jkjweb.AudioChangeServerThread;

/**
 * 语音实时转文字工具类
 * 
 * @author hht
 *
 */
public class RealtimeAsrDemo implements NlsListener,Runnable{
	protected NlsClient client = new NlsClient();
	protected static final String asrSC = "pcm";

	static Logger logger = LoggerFactory.getLogger(RealtimeAsrDemo.class);
	public String appKey = "XXX";
	protected String ak_id = "XXX";
	protected String ak_secret = "XXX";
	private InputStream fis = null;
	private DataOutputStream dos = null;
	

	
	public RealtimeAsrDemo(InputStream fis, DataOutputStream dos) {
		this.fis = fis;
		this.dos = dos;
	}

	public void shutDown() {
		logger.error("close NLS client manually!");
		client.close();
		logger.error("demo done");
	}

	public void init() {
		logger.error("init Nls client...");
		client.init();
	}

	public void process() {
		logger.error("open audio file...");
		if (fis != null) {
			logger.error("create NLS future");
			process(fis);
			logger.error("calling NLS service end");
		}
	}
	

	public void process(InputStream ins) {
		try {
			NlsRequest req = buildRequest();
			NlsFuture future = client.createNlsFuture(req, this);
			logger.error("call NLS service");
			byte[] b = new byte[8000];
			int len = 0;
			while (AudioChangeServerThread.runStatus && (len = ins.read(b)) > 0 ) {
				future.sendVoice(b, 0, len);
			}
			logger.error("send finish signal!");
			future.sendFinishSignal();

			logger.error("main thread enter waiting .");
			future.await(100000);

		} catch (Exception e) {
			StringWriter sw = new StringWriter();
			e.printStackTrace(new PrintWriter(sw));
			logger.error(sw.toString());
		}
	}
	
	
	


	protected NlsRequest buildRequest() {
		NlsRequest req = new NlsRequest();
		req.setAppkey(appKey);
		req.setFormat(asrSC);
		req.setResponseMode("streaming");
		req.setSampleRate(16000);
		// 用户根据[热词文档](~~49179~~) 设置自定义热词。
		// 通过设置VocabularyId调用热词。
		// req.setVocabularyId("");
		// 设置关键词库ID 使用时请修改为自定义的词库ID
		// req.setKeyWordListId("c1391f1c1f1b4002936893c6d97592f3");
		// the id and the id secret
		req.authorize(ak_id, ak_secret);
		return req;

	}

	@Override
	public void onMessageReceived(NlsEvent e) {
		NlsResponse response = e.getResponse();
		response.getFinish();
		if (response.result != null) {
			logger.error(response.getResult().toString());
			if(response.getResult().getStatus_code()==0&&!"".equals(response.getText())){
				String content = response.getText();
				logger.error(content);
				try {
					dos.writeUTF(content);
					dos.flush();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
		} else {
			logger.error(JSON.toJSONString(response));
		}
	}

	@Override
	public void onOperationFailed(NlsEvent e) {
		logger.error("status code is {}, on operation failed: {}"+ e.getResponse().getStatusCode()+e.getErrorMessage());

	}

	@Override
	public void onChannelClosed(NlsEvent e) {
		logger.error("on websocket closed.");
	}

	@Override
	public void run() {
		init();
		process();
		shutDown();
	}


}

这样就可以了,先启动服务端,然后启动app就可以实现语音是说转文字了哦

补充代码

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.hht.myapplication.MainActivity">

    <TextView
        android:text=""
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/textView2"
        android:maxLines="5"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true" />

    <TextView
        android:text=""
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textIsSelectable="true"
        android:focusable="true"
        android:layout_centerVertical="true"
        android:layout_alignParentStart="true"
        android:scrollbars="vertical"
        android:maxLines="15"
        android:fadeScrollbars="false"
        android:id="@+id/textView" />

    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickCopy"
        android:text="复制上面的文本内容"
        android:id="@+id/button"
        android:layout_marginBottom="12dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentEnd="true"
        android:layout_marginEnd="7dp" />

</RelativeLayout>