Pull to refresh
0
Rating

Бесплатное распознавание речи от российской компании Стэл

Lexy corporate blogProgrammingAPIMathematics
Когда возникает необходимость превратить звуковой файл с речью в текст, первыми на ум приходят решения Гугла и Яндекса. Но, кроме Яндекса, есть ещё одна отечественная компания — «Стэл» (http://speech.stel.ru/), API которой поддерживает «over 9000» и даже «очень очень много» запросов в день, а пробные ключи Stel раздает бесплатно.

image



Разобраться с API не так уж сложно, но на момент написания этой статьи мануал с сайта Стэла устарел и не работает, посему здесь будет представлен мануал с примерами на Python и Java. Пример на Java особенно актуален, если звуковой файл у вас имеется не в виде файла, а в виде массива байтов. Сразу стоит отметить, что Стэл работает только с wav-файлами с частотой дискретизации 8 кГц, размером сэмпла 16 бит, моно (один канал).

Ближе к делу: на сайте Стэла (http://speech.stel.ru/api_description) подробно описано, что и как (хоть на данный момент и немного устарело), посему, приводим сразу работающий (опять же, на данный момент) пример на питоне:

coding: utf-8
import httplib, json, base64

HOST = 'api.stel.ru:7071'
APIKEY = '***' # Place your API key here
MODEL = 'rus_gsm_ext'

WAV = base64.b64encode(open('test.wav', 'rb').read()) # demo audio file (WAV, 8000 HZ, 16-bit, mono)
con = httplib.HTTPConnection(HOST)

#Speech recognition
data = json.dumps({'apikey' : APIKEY, 'model': MODEL , 'wav' : WAV})
headers = {'Content-Type' : 'application/json', 'Accept': 'application/json', 'Content-Length' : '{0}'.format(len(data))}
con.request('POST', '/kwfind', data, headers)
resp = con.getresponse()

if resp.status == 200:
  print json.loads(resp.read()) # UTF-8 string with recognized text
else:
  print resp.reason


Как видно, тут на распознавание отправляется test.wav из рабочей директории скрипта. Аналогичный код на Java, а также код работающий с массивами байтов, приведены ниже. Во-первых, класс, который массивы байтов (без разметки) и файлы превращает в массивы байтов, соответствующие wav-файлу в указанном формате (нам будут нужны 8000 герц, 2 байта, 1 канал):

package ru.habrahabr.stel.example;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;

public class WaveFile
{
	private int INT_SIZE = 4;
	public final int NOT_SPECIFIED = -1;
	private int sampleSize = NOT_SPECIFIED;
	private long framesCount = NOT_SPECIFIED;
	private byte[] data = null;
	private AudioInputStream ais = null;
	private AudioFormat af = null;

	WaveFile(File file) throws UnsupportedAudioFileException, IOException
	{
		if(!file.exists())
		{
			throw new FileNotFoundException(file.getAbsolutePath());
		}
		
		ais = AudioSystem.getAudioInputStream(file); 
		af = ais.getFormat();
		framesCount = ais.getFrameLength();
		sampleSize = af.getSampleSizeInBits()/8;
		long dataLength = framesCount*af.getSampleSizeInBits()*af.getChannels()/8;
		data = new byte[(int) dataLength]; 
		ais.read(data);
	}
	
	WaveFile(int sampleSize, float sampleRate, int channels, int[] samples) throws Exception
	{
		if(sampleSize < INT_SIZE)
		{
			throw new Exception("sample size < int size");
		}
		
		this.sampleSize = sampleSize;
		this.af = new AudioFormat(sampleRate, sampleSize*8, channels, true, false);
		this.data = new byte[samples.length*sampleSize];
		
		for(int i=0; i < samples.length; i++)
		{
			setSampleInt(i, samples[i]);
		}
		
		framesCount = data.length / (sampleSize*af.getChannels());
		ais = new AudioInputStream(new ByteArrayInputStream(data), af, framesCount);
	}
	
	WaveFile(int sampleSize, float sampleRate, int channels, byte[] wave) throws Exception
	{
		this.sampleSize = sampleSize;
		this.af = new AudioFormat(sampleRate, sampleSize*8, channels, true, false);
		this.data = Arrays.copyOf(wave, wave.length);
		
		framesCount = data.length / (sampleSize*af.getChannels());
		ais = new AudioInputStream(new ByteArrayInputStream(data), af, framesCount);
	}
	
	public AudioFormat getAudioFormat()
	{
		return af;
	}
	
	public byte[] getData()
	{
		return Arrays.copyOf(data, data.length);
	}
	
	public byte[] getWave() throws Exception
	{
		ByteArrayOutputStream bts = new ByteArrayOutputStream();
		AudioSystem.write(new AudioInputStream(new ByteArrayInputStream(data), 
				af, framesCount), AudioFileFormat.Type.WAVE, bts);
		return bts.toByteArray();
	}
	
	public int getSampleSize()
	{
		return sampleSize;
	}
	
	public double getDurationTime()
	{
		return getFramesCount() / getAudioFormat().getFrameRate();
	}
	
	public long getFramesCount()
	{
		return framesCount;
	}
	
	public void saveFile(File file) throws IOException
	{
		AudioSystem.write( new AudioInputStream(new ByteArrayInputStream(data), 
				af, framesCount), AudioFileFormat.Type.WAVE, file);
	}
	
	public int getSampleInt(int sampleNumber)
	{
		
		if(sampleNumber < 0 || sampleNumber >= data.length/sampleSize)
		{
			throw new IllegalArgumentException(
					"sample number is can't be < 0 or >= data.length/"
							+ sampleSize);
		}
		
		byte[] sampleBytes = new byte[sampleSize];
		
		for(int i=0; i < sampleSize; i++)
		{
			sampleBytes[i] = data[sampleNumber * sampleSize + i];
		}
		
		int sample = ByteBuffer.wrap(sampleBytes)
				.order(ByteOrder.LITTLE_ENDIAN).getInt();
		
		return sample;
	}
	
	public void setSampleInt(int sampleNumber, int sampleValue)
	{
		byte[] sampleBytes = ByteBuffer.allocate(sampleSize).
				order(ByteOrder.LITTLE_ENDIAN).putInt(sampleValue).array();
		
		for(int i=0; i < sampleSize; i++)
		{
			data[sampleNumber * sampleSize + i] = sampleBytes[i];
		}
	}
}


Стоит отметить, что это немного дописанный класс, взятый с http://blog.eqlbin.ru/2011/02/wave-java.html. В общем-то все, что тут добавлено — это функция getWave(), возвращающая массив байтов, соответствующих файлу, построенному одним из конструкторов. А также конструктор, принимающий массив байтов обычного raw-файла. Отправлять Стэлу будем именно результат функции getWave(). Далее приведена функция, которая принимает WaveFile, открывает соединение со Стэлом, отправляет все что нужно, закрывает соединение и возвращает распознанную строку:

String getResponseOn(WaveFile wf)
{
	String res = new String();
	try
	{
		byte[] wav = wf.getWave();
		HttpConnection conn = new HttpConnection("api.stel.ru", 7071);
		conn.open();
		HttpState state = new HttpState();
		
		PostMethod post = new PostMethod();
		
		JSONObject data = new JSONObject();
		data.put("apikey", "***");	// Place your API key here
		data.put("model", "rus_gsm_ext");
		data.put("wav", new String(Base64.encodeBase64(wav)));
		
		post.setPath("/kwfind");
		post.setRequestHeader("Content-Type", "application/json");
		post.setRequestHeader("Accept", "application/json");
		post.setRequestHeader("Content-Length", ""+data.toJSONString().length());
		post.setRequestEntity(new StringRequestEntity(data.toJSONString(), "application/json", null));
		post.execute(state, conn);
		
		res = res + (String) ((JSONObject) new JSONParser().parse(post.getResponseBodyAsString())).get("text");
		conn.close();
	}
	catch(Exception e)
	{
		res = null;
	}
	return res;
}


Не забудьте, что надо заменить "***" на ваш ключ, а также, что WaveFile для getResponseOn создается с параметрами (2, (float) 8000, 1, (byte[]) raw), например:

String res1 = getResponseOn(new WaveFile(2, (float) 8000.0, 1, sound));
String res2 = getResponseOn(new WaveFile(new File("test.waw"))); //demo audio file (WAV, 8000 HZ, 16-bit, mono)


Кроме того, нужно отметить, что в getResponseOn(WaveFile wf) используется org.json.simple.JSONObject и org.json.simple.parser.JSONParser, которые зачастую приходится качать отдельно, например, отсюда: www.java2s.com/Code/Jar/j/Downloadjsonsimple111jar.htm

«Стэл» легко идут на контакт, так что, если вам нужны будут другие языки или языковые базы, с ними можно договориться.

Напомним, что наша команда занимается разработкой интеллектуального домашнего помощника Лекси. Лекси — настольное устройство с искусственным интеллектом и полностью голосовым интерфейсом для управления умным домом. Устройство может получать информацию в интернете, управлять бытовой техникой, сообщать новости из социальных сетей. Кстати, почитать об интересных размышлениях о будущем подобных домашних роботов вы можете в этой статье .

Технология распознавания речи, как вы уже могли догадаться, у нас от Стэла. При этом распознавание речи происходит полностью на борту устройства (обзор нашей собственной электроники можно посмотреть тут). Это дает нам ряд преимуществ по сравнению с конкурентными аналогами, например, увеличение скорости выдачи пользователю ответа, отсутствие активационной фразы и возможность работы без интернета.

Следите на нашим проектом в социальных сетях: Вконтакте и Фейсбуке.

image
Спасибо за внимание.
Tags:лексистэлраспознавание речизвукработа со звукомроботголосовой помощникумный домконтроллеряндексgoogleapi
Hubs: Lexy corporate blog Programming API Mathematics
Total votes 11: ↑7 and ↓4 +3
Views11.5K

Comments 13

Only those users with full accounts are able to leave comments. Log in, please.

Top of the last 24 hours

Information

Founded
Location
Россия
Website
lexybot.com
Employees
2–10 employees
Registered