Когда возникает необходимость превратить звуковой файл с речью в текст, первыми на ум приходят решения Гугла и Яндекса. Но, кроме Яндекса, есть ещё одна отечественная компания — «Стэл» (http://speech.stel.ru/), API которой поддерживает «over 9000» и даже «очень очень много» запросов в день, а пробные ключи Stel раздает бесплатно.
Разобраться с 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
«Стэл» легко идут на контакт, так что, если вам нужны будут другие языки или языковые базы, с ними можно договориться.
Напомним, что наша команда занимается разработкой интеллектуального домашнего помощника Лекси. Лекси — настольное устройство с искусственным интеллектом и полностью голосовым интерфейсом для управления умным домом. Устройство может получать информацию в интернете, управлять бытовой техникой, сообщать новости из социальных сетей. Кстати, почитать об интересных размышлениях о будущем подобных домашних роботов вы можете в этой статье .
Технология распознавания речи, как вы уже могли догадаться, у нас от Стела. При этом распознавание речи происходит полностью на борту устройства (обзор нашей собственной электроники можно посмотреть тут). Это даем нам ряд преимуществ по сравнению с конкурентными аналогами, например, увеличение скорости выдачи пользователю ответа, отсутствие активационной фразы и возможность прохождения работы без интернета.
Следите на нашим проектом в социальных: Вконтакте и Фейсбуке.
Спасибо за внимание.
ссылка на оригинал статьи http://habrahabr.ru/post/260683/
Добавить комментарий