Разобраться с 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
«Стэл» легко идут на контакт, так что, если вам нужны будут другие языки или языковые базы, с ними можно договориться.
Напомним, что наша команда занимается разработкой интеллектуального домашнего помощника Лекси. Лекси — настольное устройство с искусственным интеллектом и полностью голосовым интерфейсом для управления умным домом. Устройство может получать информацию в интернете, управлять бытовой техникой, сообщать новости из социальных сетей. Кстати, почитать об интересных размышлениях о будущем подобных домашних роботов вы можете в этой статье .
Технология распознавания речи, как вы уже могли догадаться, у нас от Стэла. При этом распознавание речи происходит полностью на борту устройства (обзор нашей собственной электроники можно посмотреть тут). Это дает нам ряд преимуществ по сравнению с конкурентными аналогами, например, увеличение скорости выдачи пользователю ответа, отсутствие активационной фразы и возможность работы без интернета.
Следите на нашим проектом в социальных сетях: Вконтакте и Фейсбуке.
Спасибо за внимание.
Комментарии (13)
zvyagaaa
30.06.2015 17:11Было бы здорово соорудить на API сервис для транскрибирования записей. Потому что на русском языке транскрибирования практически нет. А сервис, думаю, был бы востребован.
AndreWin
30.06.2015 18:25А что робот на первой картинке говорит?
SedovArtem
30.06.2015 18:28А ведь Паша всегда так внимательно расспрашивал о здоровье Ксении Федоровны
DemiurgeSerge
09.07.2015 10:35Первые три строчки статьи вызывают недоумение. А как же распознавание речи от ЦРТ (Центр речевых технологий)? А есть еще и другие компании… тоже Российские.
eeeeep
09.07.2015 11:20Большинство разработчиков используют то, что хорошо документировано и доступно. Поэтому мы и упомянули самые стандартные решения.
А кроме ЦРТ вы кого знаете?DemiurgeSerge
09.07.2015 13:32Вы думаете решения ЦРТ не документированы? Вместе с VoiceNavigator'ом идет пакет из 19 документов — часть из них я сам писал, когда работал в ЦРТ. Это полностью коммерческое и промышленное решение.
Что значит доступно? Можно купить или мало денег стоит? Кто хочет купить систему распознавания речи — тот покупает, это никогда не было проблемой.
Если Вы называете такие компании как Google и Яндекс, то почему забыли Nuance — мирового гиганта?
Российские компании тоже есть, не смог их сейчас загуглить за минуту, но еще 2-3 точно есть, я с ними общался, но названия из головы вылетели.
ChiefPilot
Зашёл посмотреть, что же там таки Бендер пытался сказать, а тут нету ничего! Вот как же так можно-то, а?! :)
Timon_Omsk
А ведь Паша всегда так внимательно расспрашивал о здоровье Ксении Федоровны,
Anisotropic
habrahabr.ru/company/speechpro/blog/210078
Отсюда картинка взята, тут же и описано это.
SedovArtem
Картинка была взята с гуглапоиска по картинкам.
Anisotropic
Ок. Я вам ссылку дал откуда она взялась в гуглопоиске :)