Yandex слышит тебя, dude

в 13:32, , рубрики: <censored>, Блог компании Papa Buba Diop, разработка под iOS, Яндекс API

image
Неожиданно пришел приказ — написать приложение под iOS, использующее Yandex Speechkit для распознавания русской речи. Точнее, для распознавания коротких фраз на произвольную тему. Цель задания — сравнить успехи яндекс-двигателя с нашим, саровским движком.

Приказали — сделал следующие шаги.

  1. Зашел на yandex.ru в раздел распознавание речи
  2. Зарегистрировался и получил ключ, он же API_KEY
  3. Отправил письмо в yandex с просьбой активировать ключ

На вопрос, как будет использоваться ключ, я ответил, что выпускаю карточную игру Diablo 3-13, управляемую голосом.

Через два дня ключ активировали. Я поначалу нетерпеливо бил копытом, затем понял, что в yandex работают вдумчивые, синхронные сотрудники.
В своем приложении в дальнейшем я также отказался от асинхронных запросов к yandex.api.

Получив в свое распоряжение волшебный ключ, он же API_KEY, я скачал по указанной ссылке архив

YandexSpeechKit-2.1-ios.zip

В архиве содержится два проекта, демонстрирующих работу библиотеки.
Собрав оба примера, я заменил в тексте SAMPLE_API_KEY на свой и запустил приложения.
Оба не работают под Xcode 5.1.1, вылетают по какой-то внутренней ошибке, спрятанной глубоко в недрах библиотеки.

Пришлось загрузить актуальную SDK c github.

Cкачал по указанной выше ссылке архив

yandex-speechkit-ios-master.zip

собрал примеры, но ошибка не исчезла.

Я немедленно отправил письмо-диагностику в службу поддержки и, в ожидании ответа, написал очередную игрушку.
Прошло пару дней, ответа от службы не было.

Выложив игрушку в маркет, я решил написать собственное iOS-приложение, используя url-запросы к яндекс-сервису распознавания речи.

Ведь волшебный ключ можно использовать тот же.

Шаг первый — проверка командной строкой

В командной строке надо подавать wav файлы с заранее записанными фразами.

Запрос выглядит просто, как бамбук

   curl -v -4 "asr.yandex.net/asr_xml?key=e547b4f5-хрен-вам-ключ-97130fdbcd74&uuid=01ae13cb744628b58fb536d496daa177&topic=notes&lang=ru-RU" -H "Content-Type: audio/x-wav" --data-binary "@recordedFile.wav"
 

В комментарии запрос не нуждается, все сделано в точности с великолепной документацией на сайте yandex.
С первого раза запрос не прошел, поскольку вместо 32-цифрового uuid я подсунул udid своего iPhone. А в нем не только HEX.

Фразы типа

тридцать восемь попугаев
пойдем-ка покурим-ка
Владимир Сысоев
чо смотришь дубина давай помогай
кто не пашет тот сачок
Антон Субботин
Хабрахабр — полный улет

распознались великолепно, в исполнении разных дикторов.

image
К моему удовольствию, срамные слова яндекс беспощадно вырезает.

Шаг второй — собираем iOS-приложение, где записывается речь

Есть стандартный проект на сайте apple, в котором демонстрируется запись/проигрывание звука.
Скачиваем проект SpeakHere, запускаем — все в порядке. Уважаю я этих ребят из Купертино, пусть индусы. Код, конечно у них, хмм, но работает же.

Модифицируем файл SpeakHereController.mm

Заходим в функцию — (void)stopRecord и дописываем одну строчку

 - (void)stopRecord
{
   // здесь ничего не трогаем
...
  // btn_play.enabled = YES;
    
  // эту строчку добавляем
    [self yandexTool];
}

Понятно, что мы добавили вызов функции, обрабатывающей сформированный во время звукозаписи аудиофайл.
Изначально в проекте звук записывается в файл recordedFile.caff

 recordFilePath = (CFStringRef)[NSTemporaryDirectory() stringByAppendingPathComponent: @"recordedFile.caff"];

Яндекс не умеет работать с файлами такого типа, поэтому по всему файлу SpeakHereController.mm расширитель имени надо заменить на

 recordFilePath = (CFStringRef)[NSTemporaryDirectory() stringByAppendingPathComponent: @"recordedFile.wav"];

Кроме того в файле проекта AQRecorder.mm в теле функции void AQRecorder::StartRecord(CFStringRef inRecordFile) надо поменять параметр в строке

		OSStatus status = AudioFileCreateWithURL(url, kAudioFileCAFType, &mRecordFormat, kAudioFileFlags_EraseFile, &mRecordFile);

на

		OSStatus status = AudioFileCreateWithURL(url, kAudioFileWAVEType, &mRecordFormat, kAudioFileFlags_EraseFile, &mRecordFile);

И последнее — яндекс понимает звуковые файлы, записанные на частоте 16000. У Apple по умолчанию частота — 44100. Надо менять.

В файле проекта AQRecorder.mm в теле функции void AQRecorder::SetupAudioFormat(UInt32 inFormatID) добавляем строчку

   Float64 newRate = 16000;
   XThrowIfError(AudioSessionSetProperty(	kAudioSessionProperty_PreferredHardwareSampleRate,
                                          sizeof(newRate),
                                          &newRate), "couldn't set hardware sample rate");
 

Все, осталось вставить функцию запроса к серверу яндекс. В запросе будем аналогично командному запросу подавать файл recordedFile.wav
Привожу ниже текст функции yandexTool, простой как колея от трактора Беларусь.

-(void) yandexTool
{
    NSString *urltext_temp = [NSString stringWithFormat:@"https://asr.yandex.net/asr_xml?key=%@&uuid=%@&topic=queries&lang=ru-RU", API_KEY, API_UUID];
    NSString* urltext =
    [urltext_temp stringByAddingPercentEscapesUsingEncoding:
     NSUTF8StringEncoding];
    NSLog(@"url=%@", urltext);
    NSURL *url = [NSURL URLWithString: urltext];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setURL:url];
    [request setHTTPMethod:@"PUT"];
    [request setValue:@"audio/x-wav" forHTTPHeaderField:@"Content-Type"];
    NSString *filePath=[NSTemporaryDirectory() stringByAppendingPathComponent: @"recordedFile.wav"];
    NSData *myData = [NSData dataWithContentsOfFile:filePath];
    request.HTTPBody = myData;
 
   NSError *error;
    NSURLResponse *response;
    NSData *data2 = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
    NSString *responseString = [[NSString alloc] initWithData:data2 encoding:NSUTF8StringEncoding];
    NSLog(@"responseString=%@",responseString);
 //  Ответ возвращается в XML - разбор ответа сами сделаете, чай не маленькие
 //   NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data2];
 //   [parser setDelegate:self];
 //   [parser parse];
}
 

Свое приложение я еще немного модифицировал — добавил собственную морду и вывод распознанного текста.

Распознает, надо сказать обалденно хорошо.

image

Я, как обещал яндексу, вставил распозновалку в карточную игру King of Hearts, но задержка в 1-2 секунды при управлении голосом начинает раздражать через 5 минут после начала игры.

Тем не менее, ни одной ошибки распознавания названия игральной карты за время игры не произошло.
Браво, Яндекс!

Пока готовил публикацию, пришел ответ от тех.поддержки команды yandex, они просят прислать полные логи неработающих приложений.
Надо, наверное, ответить им.

Автор: PapaBubaDiop

Источник

* - обязательные к заполнению поля